mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-07-09 16:09:13 +00:00
fix: stop the docker UI from filtering out its own injected API key (403 "Invalid credentials" on self-host) (#6516)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
def90269c4
commit
4c869489c0
3 changed files with 62 additions and 8 deletions
|
|
@ -28,8 +28,11 @@ VITE_ENABLE_2FA_NOTIFICATIONS="${VITE_ENABLE_2FA_NOTIFICATIONS:-false}"
|
|||
# 1. Environment variable (from .env file or docker-compose environment),
|
||||
# but only if it's not a placeholder/sentinel value. Both the Dockerfile
|
||||
# default (__SKYVERN_API_KEY_PLACEHOLDER__) and the .env.example default
|
||||
# (YOUR_API_KEY) can leak through if users skip configuration. Keep this
|
||||
# filter list aligned with the frontend's PLACEHOLDER_VALUES.
|
||||
# (YOUR_API_KEY) can leak through if users skip configuration. Keep these
|
||||
# sentinels aligned with the frontend's placeholder check in
|
||||
# skyvern-frontend/src/util/env.ts (which matches the Docker sentinel by
|
||||
# its _PLACEHOLDER__ suffix — the sed below rewrites every occurrence of
|
||||
# the full sentinel in the bundle, so the frontend must never spell it out).
|
||||
# 2. Generated credentials file from the backend first-run setup
|
||||
# (volume-mounted at /app/.skyvern/credentials.toml in docker-compose)
|
||||
VITE_SKYVERN_API_KEY="${VITE_SKYVERN_API_KEY:-}"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
async function loadEnv(apiKey: string | null = null, streamingMode = "") {
|
||||
|
|
@ -44,6 +47,53 @@ describe("getCredentialParam", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("getRuntimeApiKey", () => {
|
||||
// Built via join so this test file never contains the sentinel verbatim
|
||||
// either — entrypoint-skyvernui.sh sed-replaces every occurrence in built
|
||||
// assets, and the source guard below must stay meaningful.
|
||||
const dockerSentinel = ["__SKYVERN_API_KEY", "_PLACEHOLDER__"].join("");
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.resetModules();
|
||||
window.sessionStorage.clear();
|
||||
});
|
||||
|
||||
it("returns the build-time API key", async () => {
|
||||
const { getRuntimeApiKey } = await loadEnv("eyJhbGciOiJIUzI1NiJ9.real.key");
|
||||
|
||||
expect(getRuntimeApiKey()).toBe("eyJhbGciOiJIUzI1NiJ9.real.key");
|
||||
});
|
||||
|
||||
it("treats the .env.example placeholder as missing", async () => {
|
||||
const { getRuntimeApiKey } = await loadEnv("YOUR_API_KEY");
|
||||
|
||||
expect(getRuntimeApiKey()).toBe(null);
|
||||
});
|
||||
|
||||
it("treats an un-replaced docker placeholder as missing", async () => {
|
||||
const { getRuntimeApiKey } = await loadEnv(dockerSentinel);
|
||||
|
||||
expect(getRuntimeApiKey()).toBe(null);
|
||||
});
|
||||
|
||||
// Regression guard for the v1.0.34–v1.0.40 docker auth outage:
|
||||
// entrypoint-skyvernui.sh sed-replaces EVERY occurrence of the docker
|
||||
// sentinel in the built bundle with the real API key. When the full
|
||||
// sentinel appeared verbatim in this module (inside the placeholder
|
||||
// deny-list), the real key replaced it there too, so the key became a
|
||||
// member of its own deny-list and the UI sent no x-api-key header at all
|
||||
// (403 "Invalid credentials" on every request).
|
||||
it("never spells out the full docker placeholder sentinel in module source", async () => {
|
||||
const source = await readFile(
|
||||
path.join(process.cwd(), "src/util/env.ts"),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
expect(source.includes(dockerSentinel)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("browserStreamingMode", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
|
|
|
|||
|
|
@ -80,12 +80,13 @@ function getRuntimeApiKey(): string | null {
|
|||
|
||||
// Treat placeholder / example values as missing so they are never sent
|
||||
// as real credentials (e.g. from .env.example or an un-replaced Docker placeholder).
|
||||
const PLACEHOLDER_VALUES = [
|
||||
"YOUR_API_KEY",
|
||||
"__SKYVERN_API_KEY_PLACEHOLDER__",
|
||||
];
|
||||
runtimeApiKey =
|
||||
candidate && PLACEHOLDER_VALUES.includes(candidate) ? null : candidate;
|
||||
// The Docker sentinel is matched by its suffix on purpose: the prebuilt-image
|
||||
// entrypoint sed-replaces every occurrence of the full sentinel in the bundle
|
||||
// with the real API key, so spelling it out here would put the real key in its
|
||||
// own deny-list and strip the x-api-key header from every request.
|
||||
const isPlaceholder =
|
||||
candidate === "YOUR_API_KEY" || candidate?.endsWith("_PLACEHOLDER__");
|
||||
runtimeApiKey = candidate && isPlaceholder ? null : candidate;
|
||||
return runtimeApiKey;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue