diff --git a/src/main/runtime/terminal-credential-prompt-detection.test.ts b/src/main/runtime/terminal-credential-prompt-detection.test.ts index f1bdd6e803c..88b58f4b907 100644 --- a/src/main/runtime/terminal-credential-prompt-detection.test.ts +++ b/src/main/runtime/terminal-credential-prompt-detection.test.ts @@ -603,6 +603,17 @@ const LEGITIMATE_AGENT_SCREENS: readonly (readonly [string, string[]])[] = [ ], // No caret, and the bottom row DOES lead with an action phrase, so the only thing keeping this // from reading as a live prompt is that `candidate.login` is a property access. + [ + 'source comment leading with an action phrase, corroborated by the comment above it', + [ + ' // See the authentication guide for details', + ' // Sign in with the provider console to continue' + ] + ], + [ + 'source rows where a .credential access is the only would-be credential noun', + [' const owner = candidate.credential', ' Enter the code below to finish linking'] + ], [ 'source rows where a .login access is the only would-be auth verb', [ diff --git a/src/main/runtime/terminal-credential-prompt-detection.ts b/src/main/runtime/terminal-credential-prompt-detection.ts index bac539c7ff6..c336399882b 100644 --- a/src/main/runtime/terminal-credential-prompt-detection.ts +++ b/src/main/runtime/terminal-credential-prompt-detection.ts @@ -40,11 +40,15 @@ const MAX_CREDENTIAL_LINE_LENGTH = 512 // segment before it is handled by the prefix rules, not here — a variable-length prefix inside // a noun alternation makes this whole pattern quadratic on long lines, and it runs per retained // tail line at streaming rate. -const CREDENTIAL_NOUN_SOURCE = +export const CREDENTIAL_NOUN_SOURCE = 'password|passphrase|api[ _-]?keys?|access[ _-]tokens?|auth(?:orization)?[ _-]tokens?|bearer tokens?|personal access tokens?|secret keys?|client secrets?|one[- ]time (?:code|password)|otp|verification codes?|authentication codes?|security codes?|2fa codes?|device codes?|credentials?' -const CREDENTIAL_NOUN_RE = new RegExp(CREDENTIAL_NOUN_SOURCE, 'gi') -const CREDENTIAL_NOUN_ANYWHERE_RE = new RegExp(CREDENTIAL_NOUN_SOURCE, 'i') +// Why the lookbehind: `candidate.credential`, `settings.apiKey` and `config.password` are property +// accesses. The noun is what corroborates an otherwise-inert bottom row, so an identifier reading +// as the noun is enough on its own to refuse a screen that is only printing source. Only `.` is +// excluded, not all of `\w` — the env-var form (`OPENAI_API_KEY`) is a real ask. +const CREDENTIAL_NOUN_RE = new RegExp(`(?` are // property accesses, not auth wording, and `\b` treats `.` as a boundary. Agents print this // repo's own source constantly. -const AUTH_VERB_RE = - /(?