{"record":{"id":"b801a8de590a8eba","repo":"can1357/oh-my-pi","slug":"security-scan-authentication-provider-mismatch","errorCode":null,"errorMessage":"Security scan authentication provider mismatch","messagePattern":"Security scan authentication provider mismatch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/auth.ts","lineNumber":86,"sourceCode":"\tassertSecurityIdentityMatches(account, resolution);\n\tif (!resolution.ok) throw new Error(\"The pinned security OAuth credential could not be resolved\");\n\treturn resolution;\n}\n\n/**\n * Build a request credential resolver pinned to one durable OAuth row.\n *\n * Initial resolution and refresh both target the same row. The auth driver's\n * final sibling-rotation step returns `undefined`, so an unavailable account\n * fails the scan rather than crossing an account/workspace boundary.\n */\nexport function createExactSecurityOAuthResolver(\n\toptions: ExactSecurityOAuthOptions,\n): NonNullable<AgentOptions[\"getApiKey\"]> {\n\tconst { account, authStorage } = options;\n\treturn model => {\n\t\tif (model.provider !== account.provider) {\n\t\t\tthrow new Error(\"Security scan authentication provider mismatch\");\n\t\t}\n\t\tconst resolver: ApiKeyResolver = async context => {\n\t\t\tif (context.lastChance) return undefined;\n\t\t\tconst resolution = await resolveExactSecurityOAuthAccess(authStorage, account, {\n\t\t\t\tforceRefresh: context.error !== undefined,\n\t\t\t\tsignal: context.signal,\n\t\t\t});\n\t\t\treturn resolution.accessToken;\n\t\t};\n\t\treturn resolver;\n\t};\n}\n","sourceCodeStart":68,"sourceCodeEnd":99,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/auth.ts#L68-L99","documentation":"createExactSecurityOAuthResolver() returns a getApiKey hook that throws immediately if the requested model's provider does not match the pinned SecurityAccountRef provider. It guarantees the security scan never uses credentials for one provider to authenticate requests against another.","triggerScenarios":"An agent run configured with an exact security OAuth resolver for provider X encounters a model whose provider is Y (e.g. pinned an openai-codex account but the run selects an anthropic model), so the model=>... guard throws.","commonSituations":"Model misconfiguration in the scan session (wrong default model/provider); multi-provider model lists where a non-pinned provider model gets picked; copy-pasting a resolver config across providers.","solutions":["Make the scan session use a model whose provider matches account.provider","Recreate the resolver with a SecurityAccountRef for the provider the model actually uses","Fix the pinned account provider value (e.g. \"openai-codex\" vs \"openai\" mixups)"],"exampleFix":"// before\ncreateExactSecurityOAuthResolver({ account: { provider: \"openai-codex\", credentialId }, authStorage })\n// used with an anthropic model -> mismatch\n// after\ncreateExactSecurityOAuthResolver({ account: { provider: model.provider, credentialId }, authStorage })","handlingStrategy":"validation","validationCode":"if (model.provider !== account.provider) {\n  throw new Error(`Scan requires a ${account.provider} model, got ${model.provider}`);\n}","typeGuard":"const matchesAccount = (model: { provider: string }, account: { provider: string }): boolean =>\n  model.provider === account.provider;","tryCatchPattern":"try {\n  await runScan(session);\n} catch (err) {\n  if (err.message === \"Security scan authentication provider mismatch\") {\n    // rebuild resolver/agent with a model matching account.provider\n  } else throw err;\n}","preventionTips":["Pin the model together with the account so both share one provider","Validate session default model provider against the pinned account at setup","Avoid multi-provider model lists in security scans"],"tags":["oauth","configuration","provider-mismatch"],"backgroundTag":"provider-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}