diff --git a/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts b/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts index 54cffd3877d..5a4b9e0edf6 100644 --- a/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts +++ b/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts @@ -202,6 +202,26 @@ describe("exe.dev sandbox provider plugin", () => { expect(validateSshPrivateKey(VALID_RSA_PEM)).toBeNull(); }); + it("accepts UUID-like secret reference values from the save-time schema stage", async () => { + process.env.EXE_API_KEY = "host-key"; + + const result = await plugin.definition.onEnvironmentValidateConfig?.({ + driverKey: "exe-dev", + config: { + apiKey: "api-key", + sshPrivateKey: "11111111-1111-4111-8111-111111111111", + }, + }); + + expect(result).toMatchObject({ + ok: true, + normalizedConfig: { + sshPrivateKey: "11111111-1111-4111-8111-111111111111", + }, + }); + expect(result?.errors ?? []).toEqual([]); + }); + it("treats empty / whitespace-only input as valid (falls back to on-host key)", () => { expect(validateSshPrivateKey("")).toBeNull(); expect(validateSshPrivateKey(" \n\n ")).toBeNull(); diff --git a/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts b/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts index 4cb43bdc46b..86d47aeef2e 100644 --- a/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts +++ b/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts @@ -69,6 +69,7 @@ const MAX_VM_RECORD_DEPTH = 4; const EXE_DEV_SSH_ONBOARDING_MARKER = "Please complete registration by running: ssh exe.dev"; const EXE_DEV_SSH_EMAIL_PROMPT = "Please enter your email address:"; const EXE_DEV_SSH_INVALID_KEY_FORMAT = /Load key [^\n]*invalid format/i; +const UUID_SECRET_REF_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; // exe.dev's `--setup-script` runs at VM init as the unprivileged `exedev` user, which // has passwordless sudo. The Paperclip sandbox callback bridge is a Node script, so @@ -140,6 +141,10 @@ function isValidUrl(value: string): boolean { } } +function isSecretRef(value: string): boolean { + return UUID_SECRET_REF_RE.test(value); +} + // Catch the SSH-key paste failure modes we've seen in the wild (wrong file, // PPK export, truncated paste) before the user pays the cost of provisioning a // VM and getting a cryptic SSH error. Inline parse — no `ssh-keygen` dependency @@ -758,7 +763,7 @@ const plugin = definePlugin({ ) { errors.push("strictHostKeyChecking cannot be empty."); } - if (config.sshPrivateKey) { + if (config.sshPrivateKey && !isSecretRef(config.sshPrivateKey)) { const sshKeyError = validateSshPrivateKey(config.sshPrivateKey); if (sshKeyError) errors.push(sshKeyError); }