From dd68dc9783e9c57c00fafb58fbc791d6b939e6a1 Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Mon, 8 Jun 2026 15:52:09 -0500 Subject: [PATCH 1/5] fix: gate install success on doctor's auth-pattern security checks The installer ran build/typecheck self-correction but never ran doctor's authPatterns checks, so an insecure GET sign-out (SIGNOUT_GET_HANDLER) could pass the build and ship as a "successful" install while `workos doctor` immediately flagged it. - Add src/lib/validation/security-checks.ts: runs doctor's security subset (GET sign-out, client-leaked/in-source API keys, ungitignored .env, mixed env) against the install dir with no network. - Wire into agent-runner: the self-correction loop now feeds security findings back to the agent, and a final gate throws on error-severity findings that survive retries (success: false, non-zero exit, commit step skipped). - Point the SIGNOUT_GET_HANDLER finding at a live docs URL (old /docs/authkit/sign-out 404s). --- src/doctor/checks/auth-patterns.ts | 2 +- src/lib/agent-runner.ts | 55 +++++++-- src/lib/validation/security-checks.spec.ts | 135 +++++++++++++++++++++ src/lib/validation/security-checks.ts | 122 +++++++++++++++++++ 4 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 src/lib/validation/security-checks.spec.ts create mode 100644 src/lib/validation/security-checks.ts diff --git a/src/doctor/checks/auth-patterns.ts b/src/doctor/checks/auth-patterns.ts index dfa19649..7c6e7872 100644 --- a/src/doctor/checks/auth-patterns.ts +++ b/src/doctor/checks/auth-patterns.ts @@ -136,7 +136,7 @@ function checkSignoutGetHandler(ctx: CheckContext): AuthPatternFinding[] { filePath: relative(ctx.installDir, route), remediation: 'Convert to a POST server action. GET routes with side effects are vulnerable to CSRF and will be triggered by Next.js link prefetching.', - docsUrl: 'https://workos.com/docs/authkit/sign-out', + docsUrl: 'https://workos.com/docs/authkit/nextjs#ending-the-session', }); } } diff --git a/src/lib/agent-runner.ts b/src/lib/agent-runner.ts index 41fef21e..9d063eaf 100644 --- a/src/lib/agent-runner.ts +++ b/src/lib/agent-runner.ts @@ -1,6 +1,12 @@ import { getReference } from '@workos/skills'; import { SPINNER_MESSAGE, type FrameworkConfig } from './framework-config.js'; import { validateInstallation, quickCheckValidateAndFormat } from './validation/index.js'; +import { + runInstallSecurityChecks, + securityFindingsToIssues, + formatSecurityFindingsForAgent, + formatBlockingSecurityError, +} from './validation/security-checks.js'; import type { InstallerOptions } from '../utils/types.js'; import { ensurePackageIsInstalled, @@ -114,11 +120,25 @@ export async function runAgentInstaller(config: FrameworkConfig, options: Instal options, ); + const integration = config.metadata.integration; + const retryConfig: RetryConfig | undefined = options.noValidate ? undefined : { maxRetries: options.maxRetries ?? 2, - validateAndFormat: quickCheckValidateAndFormat, + // Self-correction combines two layers: build/typecheck (existing) AND the + // security subset of doctor's auth-pattern checks. The latter is what was + // missing — it's why an insecure GET sign-out could pass the build and + // ship as a "successful" install. Only error-severity security findings + // force a retry; any accompanying warnings ride along in the prompt. + validateAndFormat: async (workingDirectory: string) => { + const quickPrompt = await quickCheckValidateAndFormat(workingDirectory); + const security = await runInstallSecurityChecks(integration, workingDirectory); + if (quickPrompt === null && security.blocking.length === 0) return null; + return [quickPrompt, formatSecurityFindingsForAgent(security.findings)] + .filter((p): p is string => Boolean(p)) + .join('\n\n'); + }, }; // Run agent with retry support — agent gets correction prompts on validation failure @@ -157,21 +177,42 @@ export async function runAgentInstaller(config: FrameworkConfig, options: Instal // Run full validation after agent (with retries) completes // Quick checks already ran inside the retry loop — skip build if (!options.noValidate) { - options.emitter?.emit('validation:start', { framework: config.metadata.integration }); + options.emitter?.emit('validation:start', { framework: integration }); - const validationResult = await validateInstallation(config.metadata.integration, options.installDir, { + const validationResult = await validateInstallation(integration, options.installDir, { runBuild: false, }); - if (validationResult.issues.length > 0) { - options.emitter?.emit('validation:issues', { issues: validationResult.issues }); + // Run doctor's security subset as the final gate. Its absence here is the + // install-validate ↔ doctor gap: install reported success while `workos + // doctor` immediately found a SIGNOUT_GET_HANDLER hole. + const security = await runInstallSecurityChecks(integration, options.installDir); + const allIssues = [...validationResult.issues, ...securityFindingsToIssues(security.findings)]; + + if (allIssues.length > 0) { + options.emitter?.emit('validation:issues', { issues: allIssues }); } options.emitter?.emit('validation:complete', { - passed: validationResult.passed, - issueCount: validationResult.issues.length, + passed: validationResult.passed && security.blocking.length === 0, + issueCount: allIssues.length, durationMs: validationResult.durationMs, }); + + // Block success: an error-severity security finding that survived the + // self-correction retries fails the install rather than shipping silently. + // Throwing routes through the state machine's error state (success: false, + // non-zero exit) and skips the commit/PR steps, leaving the insecure code + // uncommitted for the user to inspect. + if (security.blocking.length > 0) { + analytics.capture(INSTALLER_INTERACTION_EVENT_NAME, { + action: 'security gate blocked install', + integration, + codes: security.blocking.map((f) => f.code).join(','), + }); + await analytics.shutdown('error'); + throw new Error(formatBlockingSecurityError(security.blocking)); + } } // Build environment variables from WorkOS credentials diff --git a/src/lib/validation/security-checks.spec.ts b/src/lib/validation/security-checks.spec.ts new file mode 100644 index 00000000..844bb8d6 --- /dev/null +++ b/src/lib/validation/security-checks.spec.ts @@ -0,0 +1,135 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { + runInstallSecurityChecks, + securityFindingsToIssues, + formatSecurityFindingsForAgent, + formatBlockingSecurityError, +} from './security-checks.js'; +import type { AuthPatternFinding } from '../../doctor/types.js'; + +function createTempDir(): string { + return mkdtempSync(join(tmpdir(), 'install-security-')); +} + +function writeFixtureFile(dir: string, relativePath: string, content: string) { + const fullPath = join(dir, relativePath); + mkdirSync(join(fullPath, '..'), { recursive: true }); + writeFileSync(fullPath, content); +} + +describe('runInstallSecurityChecks', () => { + let testDir: string; + + beforeEach(() => { + testDir = createTempDir(); + }); + + afterEach(() => { + rmSync(testDir, { recursive: true, force: true }); + }); + + it('flags a GET sign-out route for Next.js as a blocking finding', async () => { + writeFixtureFile(testDir, 'app/auth/signout/route.ts', 'export async function GET() { return signOut(); }'); + + const { findings, blocking } = await runInstallSecurityChecks('nextjs', testDir); + + const signout = findings.find((f) => f.code === 'SIGNOUT_GET_HANDLER'); + expect(signout).toBeDefined(); + expect(blocking.map((f) => f.code)).toContain('SIGNOUT_GET_HANDLER'); + }); + + it('returns no blocking findings for a clean POST sign-out install', async () => { + writeFixtureFile(testDir, 'app/auth/actions.ts', "'use server';\nexport async function signOutAction() { await signOut(); }"); + + const { blocking } = await runInstallSecurityChecks('nextjs', testDir); + + expect(blocking).toEqual([]); + }); + + it('treats a hardcoded API key in source as blocking (framework-agnostic)', async () => { + writeFixtureFile(testDir, 'app/page.tsx', 'const key = "sk_test_abcdefghij1234567";'); + + const { blocking } = await runInstallSecurityChecks('nextjs', testDir); + + expect(blocking.map((f) => f.code)).toContain('API_KEY_IN_SOURCE'); + }); + + it('reports warning-severity findings without blocking', async () => { + // .env.local present but not gitignored -> ENV_FILE_NOT_GITIGNORED (warning) + writeFixtureFile(testDir, '.env.local', 'WORKOS_CLIENT_ID=client_test\n'); + + const { findings, blocking } = await runInstallSecurityChecks('nextjs', testDir); + + expect(findings.map((f) => f.code)).toContain('ENV_FILE_NOT_GITIGNORED'); + expect(blocking).toEqual([]); + }); + + it('still runs cross-framework checks for an unknown integration', async () => { + writeFixtureFile(testDir, 'src/app.ts', 'const key = "sk_live_abcdefghij1234567";'); + + const { blocking } = await runInstallSecurityChecks('some-backend', testDir); + + expect(blocking.map((f) => f.code)).toContain('API_KEY_IN_SOURCE'); + }); +}); + +describe('securityFindingsToIssues', () => { + it('maps findings to pattern issues, folding filePath into the message', () => { + const findings: AuthPatternFinding[] = [ + { + code: 'SIGNOUT_GET_HANDLER', + severity: 'error', + message: 'Signout uses GET', + filePath: 'app/auth/signout/route.ts', + remediation: 'Use a POST server action.', + }, + ]; + + const issues = securityFindingsToIssues(findings); + + expect(issues).toEqual([ + { + type: 'pattern', + severity: 'error', + message: 'Signout uses GET (app/auth/signout/route.ts)', + hint: 'Use a POST server action.', + }, + ]); + }); +}); + +describe('formatSecurityFindingsForAgent', () => { + it('returns an empty string when there are no findings', () => { + expect(formatSecurityFindingsForAgent([])).toBe(''); + }); + + it('includes the message, location, and remediation for each finding', () => { + const prompt = formatSecurityFindingsForAgent([ + { + code: 'SIGNOUT_GET_HANDLER', + severity: 'error', + message: 'Signout uses GET', + filePath: 'app/auth/signout/route.ts', + remediation: 'Use a POST server action.', + }, + ]); + + expect(prompt).toContain('Signout uses GET'); + expect(prompt).toContain('app/auth/signout/route.ts'); + expect(prompt).toContain('Use a POST server action.'); + }); +}); + +describe('formatBlockingSecurityError', () => { + it('lists each blocking finding by code', () => { + const message = formatBlockingSecurityError([ + { code: 'SIGNOUT_GET_HANDLER', severity: 'error', message: 'Signout uses GET' }, + ]); + + expect(message).toContain('SIGNOUT_GET_HANDLER'); + expect(message).toContain('workos doctor'); + }); +}); diff --git a/src/lib/validation/security-checks.ts b/src/lib/validation/security-checks.ts new file mode 100644 index 00000000..5cef486a --- /dev/null +++ b/src/lib/validation/security-checks.ts @@ -0,0 +1,122 @@ +import { checkAuthPatterns } from '../../doctor/checks/auth-patterns.js'; +import type { AuthPatternFinding, FrameworkInfo, EnvironmentInfo, SdkInfo } from '../../doctor/types.js'; +import type { ValidationIssue } from './types.js'; + +/** + * The "security subset" of `workos doctor`'s auth-pattern checks that the + * installer enforces. These are the patterns that are *unsafe* or *leak secrets* + * (an unsafe GET sign-out, an API key in client env/source, an ungitignored + * .env). Completeness checks (missing middleware/callback/provider) are + * intentionally excluded here — `validateInstallation` already covers those, and + * they carry higher false-positive risk than we want gating install success. + * + * Keep in sync with the codes emitted by `src/doctor/checks/auth-patterns.ts`. + */ +const SECURITY_FINDING_CODES = new Set([ + 'SIGNOUT_GET_HANDLER', + 'SIGNOUT_LINK_PREFETCH', + 'API_KEY_LEAKED_TO_CLIENT', + 'API_KEY_IN_SOURCE', + 'ENV_FILE_NOT_GITIGNORED', + 'MIXED_ENVIRONMENT', +]); + +/** Map an installer integration id to the framework name doctor's checks expect. */ +const INTEGRATION_FRAMEWORK_NAME: Record = { + nextjs: 'Next.js', + 'react-router': 'React Router', + 'tanstack-start': 'TanStack Start', +}; + +export interface SecurityCheckResult { + /** All security-class findings for this install (errors + warnings). */ + findings: AuthPatternFinding[]; + /** + * Error-severity findings that must block a successful install. Empty when the + * install is secure; a non-empty list means install should not report success. + */ + blocking: AuthPatternFinding[]; +} + +/** + * Run the security subset of doctor's auth-pattern checks against an install + * directory. Pure file inspection — no network — so it is safe to call both + * inside the installer's self-correction loop and as the final pre-success gate. + * + * This closes the install-validate ↔ doctor gap: previously install could report + * `success: true` while `workos doctor` immediately found a security hole, + * because neither the retry loop nor `validateInstallation` ran these checks. + */ +export async function runInstallSecurityChecks(integration: string, installDir: string): Promise { + const framework: FrameworkInfo = { + name: INTEGRATION_FRAMEWORK_NAME[integration] ?? null, + version: null, + }; + // checkAuthPatterns loads .env files itself; these structs only satisfy the + // shape its non-Next.js checks read from. + const environment: EnvironmentInfo = { + apiKeyConfigured: false, + apiKeyType: null, + clientId: null, + redirectUri: null, + cookieDomain: null, + baseUrl: null, + }; + const sdk: SdkInfo = { + name: null, + version: null, + latest: null, + outdated: false, + isAuthKit: false, + language: 'javascript', + }; + + const result = await checkAuthPatterns({ installDir }, framework, environment, sdk); + const findings = result.findings.filter((f) => SECURITY_FINDING_CODES.has(f.code)); + const blocking = findings.filter((f) => f.severity === 'error'); + return { findings, blocking }; +} + +/** Convert security findings into ValidationIssues for the emitter/report surfaces. */ +export function securityFindingsToIssues(findings: AuthPatternFinding[]): ValidationIssue[] { + return findings.map((f) => ({ + type: 'pattern', + severity: f.severity, + message: f.filePath ? `${f.message} (${f.filePath})` : f.message, + hint: f.remediation, + })); +} + +/** + * Build an agent correction prompt from security findings so the installer's + * self-correction loop fixes them before declaring success. Returns an empty + * string when there is nothing to correct. + */ +export function formatSecurityFindingsForAgent(findings: AuthPatternFinding[]): string { + if (findings.length === 0) return ''; + const lines = findings.map((f) => { + const loc = f.filePath ? ` in ${f.filePath}` : ''; + const fix = f.remediation ? ` Fix: ${f.remediation}` : ''; + return `- [${f.severity}] ${f.message}${loc}.${fix}`; + }); + return `Security checks found issues that must be fixed:\n\n${lines.join('\n')}\n\nApply the fixes above, then make sure the project still builds.`; +} + +/** + * Build the error message thrown when error-severity security findings survive + * the installer's retries — the message that turns a silent insecure "success" + * into a visible failure. + */ +export function formatBlockingSecurityError(blocking: AuthPatternFinding[]): string { + const lines = blocking.map((f) => { + const loc = f.filePath ? ` (${f.filePath})` : ''; + return ` • ${f.code}: ${f.message}${loc}`; + }); + return [ + 'Installation produced insecure code that could not be auto-corrected:', + '', + ...lines, + '', + 'Fix the issues above (or run `workos doctor` for details and remediation) and re-run the installer.', + ].join('\n'); +} From 5c47e8e17ebad876afea31e613b4f4a2a5d5beb3 Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Mon, 8 Jun 2026 15:59:22 -0500 Subject: [PATCH 2/5] chore(deps): bump @workos/skills to 0.6.1 Picks up the Next.js sign-out POST server-action guidance (workos/skills#33), so the installer agent no longer scaffolds an unsafe GET sign-out route. --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 48b38753..fcab1b9b 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@workos-inc/node": "^8.7.0", "@workos/migrations": "^2.0.0", "@workos/openapi-spec": "^0.1.0", - "@workos/skills": "0.6.0", + "@workos/skills": "0.6.1", "chalk": "^5.6.2", "diff": "^8.0.3", "fast-glob": "^3.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24b29e2e..6d7fc250 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^0.1.0 version: 0.1.0 '@workos/skills': - specifier: 0.6.0 - version: 0.6.0 + specifier: 0.6.1 + version: 0.6.1 chalk: specifier: ^5.6.2 version: 5.6.2 @@ -1520,8 +1520,8 @@ packages: '@workos/openapi-spec@0.1.0': resolution: {integrity: sha512-pVw8SPFsva6UR8Z2ovE1DC4Aq1fRdPXywT8JP7yN1G8Y02+/kDb6KgFXmpKiv76XinUXDOAmU9tr/G73UI8mHw==} - '@workos/skills@0.6.0': - resolution: {integrity: sha512-BhXpbAyIWRsd8hm97zAGdlfQ+bbjJpgBtmPZckVny5+3mXOSVFqIxY+vwa5mSqkd2N2PYTd/Q+CD7Or0vWE28Q==} + '@workos/skills@0.6.1': + resolution: {integrity: sha512-2YovBOFM1u3any7JVMIrwLOomSXT/Ndft1+QAl8YalDowWxXqBqiyebebZvm47jb6BI6rIR04svG2xI5c5rcjA==} ansi-escapes@7.3.0: resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} @@ -3735,7 +3735,7 @@ snapshots: '@workos/openapi-spec@0.1.0': {} - '@workos/skills@0.6.0': + '@workos/skills@0.6.1': dependencies: yaml: 2.8.3 From 0c0db33d45c199c62a8d921776ba41fb5c78d43a Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Mon, 8 Jun 2026 16:02:11 -0500 Subject: [PATCH 3/5] chore: formatting --- src/lib/validation/security-checks.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/validation/security-checks.spec.ts b/src/lib/validation/security-checks.spec.ts index 844bb8d6..25d12e1f 100644 --- a/src/lib/validation/security-checks.spec.ts +++ b/src/lib/validation/security-checks.spec.ts @@ -42,7 +42,11 @@ describe('runInstallSecurityChecks', () => { }); it('returns no blocking findings for a clean POST sign-out install', async () => { - writeFixtureFile(testDir, 'app/auth/actions.ts', "'use server';\nexport async function signOutAction() { await signOut(); }"); + writeFixtureFile( + testDir, + 'app/auth/actions.ts', + "'use server';\nexport async function signOutAction() { await signOut(); }", + ); const { blocking } = await runInstallSecurityChecks('nextjs', testDir); From e7ef21bf03c0d28495810b339d24292dcf660004 Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Mon, 8 Jun 2026 16:03:57 -0500 Subject: [PATCH 4/5] test: cover API_KEY_LEAKED_TO_CLIENT blocking path; clarify warning comment Addresses Greptile review on #175: - Add a unit test for the API_KEY_LEAKED_TO_CLIENT blocking code (a NEXT_PUBLIC_-prefixed secret in .env.local), mirroring the API_KEY_IN_SOURCE test, so a regression in the prefix/key detection is caught. - Correct the self-correction comment: warning findings ride along only when a retry is already triggered by an error or build failure; they are otherwise surfaced in the final validation report. --- src/lib/agent-runner.ts | 4 +++- src/lib/validation/security-checks.spec.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/agent-runner.ts b/src/lib/agent-runner.ts index 9d063eaf..f62868dd 100644 --- a/src/lib/agent-runner.ts +++ b/src/lib/agent-runner.ts @@ -130,7 +130,9 @@ export async function runAgentInstaller(config: FrameworkConfig, options: Instal // security subset of doctor's auth-pattern checks. The latter is what was // missing — it's why an insecure GET sign-out could pass the build and // ship as a "successful" install. Only error-severity security findings - // force a retry; any accompanying warnings ride along in the prompt. + // force a retry; warning findings ride along in the prompt only when a + // retry is already triggered by an error or a build failure (warnings are + // still surfaced in the final validation report regardless). validateAndFormat: async (workingDirectory: string) => { const quickPrompt = await quickCheckValidateAndFormat(workingDirectory); const security = await runInstallSecurityChecks(integration, workingDirectory); diff --git a/src/lib/validation/security-checks.spec.ts b/src/lib/validation/security-checks.spec.ts index 25d12e1f..c88e1e87 100644 --- a/src/lib/validation/security-checks.spec.ts +++ b/src/lib/validation/security-checks.spec.ts @@ -61,6 +61,15 @@ describe('runInstallSecurityChecks', () => { expect(blocking.map((f) => f.code)).toContain('API_KEY_IN_SOURCE'); }); + it('treats a client-exposed secret API key as blocking', async () => { + // NEXT_PUBLIC_ prefix ships the secret to the browser bundle. + writeFixtureFile(testDir, '.env.local', 'NEXT_PUBLIC_WORKOS_API_KEY=sk_live_abcdefghij1234567\n'); + + const { blocking } = await runInstallSecurityChecks('nextjs', testDir); + + expect(blocking.map((f) => f.code)).toContain('API_KEY_LEAKED_TO_CLIENT'); + }); + it('reports warning-severity findings without blocking', async () => { // .env.local present but not gitignored -> ENV_FILE_NOT_GITIGNORED (warning) writeFixtureFile(testDir, '.env.local', 'WORKOS_CLIENT_ID=client_test\n'); From efa8d32faf6790ac659b83355d390e82dcef20e8 Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Mon, 8 Jun 2026 16:26:02 -0500 Subject: [PATCH 5/5] fix: emit retry-summary telemetry only after the security gate passes Addresses Greptile review on #175: - Move the 'agent retry summary' (passed_after_retry: true) capture below the security gate so a blocked install no longer emits a contradictory pass-after-retry event alongside 'security gate blocked install'. - Use obviously-fake fixture key strings in the spec (still satisfy the detection regex) so secrets scanners and the no-hardcoded-secrets rule stay quiet. --- src/lib/agent-runner.ts | 23 ++++++++++++---------- src/lib/validation/security-checks.spec.ts | 6 +++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/lib/agent-runner.ts b/src/lib/agent-runner.ts index f62868dd..dfdc6e85 100644 --- a/src/lib/agent-runner.ts +++ b/src/lib/agent-runner.ts @@ -166,16 +166,6 @@ export async function runAgentInstaller(config: FrameworkConfig, options: Instal throw new Error(message); } - // Track retry metrics - if (agentResult.retryCount !== undefined && agentResult.retryCount > 0) { - analytics.capture(INSTALLER_INTERACTION_EVENT_NAME, { - action: 'agent retry summary', - retry_count: agentResult.retryCount, - max_retries: options.maxRetries ?? 2, - passed_after_retry: true, - }); - } - // Run full validation after agent (with retries) completes // Quick checks already ran inside the retry loop — skip build if (!options.noValidate) { @@ -217,6 +207,19 @@ export async function runAgentInstaller(config: FrameworkConfig, options: Instal } } + // Track retry metrics AFTER the security gate. `passed_after_retry` must + // reflect a genuinely successful install, not just an exhausted retry loop — + // emitting it before the gate could pair a "passed after retry" event with a + // "security gate blocked install" failure for the same run. + if (agentResult.retryCount !== undefined && agentResult.retryCount > 0) { + analytics.capture(INSTALLER_INTERACTION_EVENT_NAME, { + action: 'agent retry summary', + retry_count: agentResult.retryCount, + max_retries: options.maxRetries ?? 2, + passed_after_retry: true, + }); + } + // Build environment variables from WorkOS credentials const envVars = config.environment.getEnvVars(apiKey, clientId); diff --git a/src/lib/validation/security-checks.spec.ts b/src/lib/validation/security-checks.spec.ts index c88e1e87..08dc5bd7 100644 --- a/src/lib/validation/security-checks.spec.ts +++ b/src/lib/validation/security-checks.spec.ts @@ -54,7 +54,7 @@ describe('runInstallSecurityChecks', () => { }); it('treats a hardcoded API key in source as blocking (framework-agnostic)', async () => { - writeFixtureFile(testDir, 'app/page.tsx', 'const key = "sk_test_abcdefghij1234567";'); + writeFixtureFile(testDir, 'app/page.tsx', 'const key = "sk_test_FIXTUREKEYFORTESTING1";'); const { blocking } = await runInstallSecurityChecks('nextjs', testDir); @@ -63,7 +63,7 @@ describe('runInstallSecurityChecks', () => { it('treats a client-exposed secret API key as blocking', async () => { // NEXT_PUBLIC_ prefix ships the secret to the browser bundle. - writeFixtureFile(testDir, '.env.local', 'NEXT_PUBLIC_WORKOS_API_KEY=sk_live_abcdefghij1234567\n'); + writeFixtureFile(testDir, '.env.local', 'NEXT_PUBLIC_WORKOS_API_KEY=sk_live_FIXTUREKEYFORTESTING1\n'); const { blocking } = await runInstallSecurityChecks('nextjs', testDir); @@ -81,7 +81,7 @@ describe('runInstallSecurityChecks', () => { }); it('still runs cross-framework checks for an unknown integration', async () => { - writeFixtureFile(testDir, 'src/app.ts', 'const key = "sk_live_abcdefghij1234567";'); + writeFixtureFile(testDir, 'src/app.ts', 'const key = "sk_live_FIXTUREKEYFORTESTING1";'); const { blocking } = await runInstallSecurityChecks('some-backend', testDir);