mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
* Suppress Git Credential Manager OAuth popup on git clone (fixes #7652)
Orca's git runner disables the interactive credential prompt on every git
call that goes through gitExecFileAsync/gitStreamStdout, but the two raw
'git clone' spawns (desktop repos:clone and the runtime clone path) passed
no env, so they inherited process.env with no guard. On Windows a clone
that needs GitHub auth then makes Git Credential Manager pop its
'Connect to GitHub' OAuth window, and in a network-restricted intranet the
browser/device flow never completes while git's credential retry re-pops it.
Apply nonInteractiveGitEnv() to both clone spawns so the prompt is
suppressed (GCM_INTERACTIVE=never, credential.interactive=false,
GIT_TERMINAL_PROMPT=0). The credential *helper* is kept, so cached-token
clones for private repos still work; only the interactive fallback popup is
disabled and the clone fails fast with a clear error instead.
* Suppress GCM OAuth popup in agent terminals and setup hooks too (#7652)
The clone-spawn fix stopped Orca's own managed git from popping Git
Credential Manager, but git run in terminals and setup scripts inherited
process.env with no guard. That is the more likely source of the reported
loop: agents are told to run 'git pull --rebase'/'git fetch'/retry 'git
push' (preamble + conflict/push-failure prompts), and each retry re-pops
GCM's 'Connect to GitHub' window in a network-restricted intranet.
Apply the credential-prompt guard to:
- setup/archive/hook scripts (hooks.ts non-WSL exec env), which run
unattended on worktree create/archive.
- the shared PTY host env (buildPtyHostEnv), via a small
applyTerminalGitCredentialPromptGuard helper. Agent terminals are
guarded unconditionally (they cannot dismiss a GUI popup); user
terminals are guarded by default via the new
terminalSuppressGitCredentialPrompt setting so power users can opt out.
The credential helper is kept, so cached gh auth still works; only the
interactive fallback prompt is disabled. Verified end-to-end in a real
Orca terminal (GIT_TERMINAL_PROMPT=0 + GCM_INTERACTIVE=never by default;
absent when the opt-out is set).
* Scope user-terminal credential guard to Windows, add settings toggle, forward guard into WSL (#7652)
* Retrigger PR checks (Actions dropped the synchronize dispatch for 57e7ce249)
* Keep shell locale out of the terminal/hook credential guard (#7652 review fix)
* Fix Fable review findings: guard WSL hook branch, wire settings search, catalog keyword keys, sparse-env askpass, one-shot agent classification (#7652)
* fix(terminal): harden Git credential popup guard
* test(pty): cover SSH credential guard setting
* fix(git): guard remote clones and setup runners
* fix(git): scope credential guards to unattended work
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import { addWslEnvKeys } from './wsl-env'
|
|
|
|
const GIT_CONFIG_WSLENV_KEY_RE = /^GIT_CONFIG_(?:COUNT|KEY_\d+|VALUE_\d+)$/
|
|
const GIT_CONFIG_INDEXED_KEY_RE = /^GIT_CONFIG_(?:KEY|VALUE)_(\d+)$/
|
|
|
|
/** Merge an indexed-config protocol as one atomic environment value. */
|
|
export function mergeGitConfigEnvProtocol(
|
|
baseEnv: NodeJS.ProcessEnv,
|
|
overrideEnv: NodeJS.ProcessEnv | undefined
|
|
): NodeJS.ProcessEnv {
|
|
const next = { ...baseEnv, ...overrideEnv }
|
|
if (!overrideEnv || !Object.keys(overrideEnv).some((key) => GIT_CONFIG_WSLENV_KEY_RE.test(key))) {
|
|
return next
|
|
}
|
|
|
|
// Why: COUNT and its indexed pairs form one protocol; retaining lower-priority
|
|
// indices behind a smaller override count makes an otherwise valid env ambiguous.
|
|
for (const key of Object.keys(next)) {
|
|
if (GIT_CONFIG_WSLENV_KEY_RE.test(key)) {
|
|
delete next[key]
|
|
}
|
|
}
|
|
for (const [key, value] of Object.entries(overrideEnv)) {
|
|
if (GIT_CONFIG_WSLENV_KEY_RE.test(key)) {
|
|
next[key] = value
|
|
}
|
|
}
|
|
return next
|
|
}
|
|
|
|
/** Return the safe append position for Git's indexed-config environment protocol. */
|
|
export function readValidGitConfigEnvCount(env: NodeJS.ProcessEnv): number | null {
|
|
const rawCount = env.GIT_CONFIG_COUNT
|
|
const indexedKeys = Object.keys(env).filter((key) => GIT_CONFIG_INDEXED_KEY_RE.test(key))
|
|
if (rawCount === undefined) {
|
|
return indexedKeys.length === 0 ? 0 : null
|
|
}
|
|
if (!/^(?:0|[1-9]\d*)$/.test(rawCount)) {
|
|
return null
|
|
}
|
|
|
|
const count = Number(rawCount)
|
|
if (!Number.isSafeInteger(count) || indexedKeys.length !== count * 2) {
|
|
return null
|
|
}
|
|
for (let index = 0; index < count; index++) {
|
|
if (
|
|
typeof env[`GIT_CONFIG_KEY_${index}`] !== 'string' ||
|
|
typeof env[`GIT_CONFIG_VALUE_${index}`] !== 'string'
|
|
) {
|
|
return null
|
|
}
|
|
}
|
|
const hasDanglingIndex = indexedKeys.some((key) => {
|
|
const match = key.match(GIT_CONFIG_INDEXED_KEY_RE)
|
|
return !match || String(Number(match[1])) !== match[1] || Number(match[1]) >= count
|
|
})
|
|
return hasDanglingIndex ? null : count
|
|
}
|
|
|
|
/** Compose indexed Git config without clobbering caller-provided entries. */
|
|
export function appendGitConfigEnv(
|
|
env: NodeJS.ProcessEnv,
|
|
entries: readonly (readonly [key: string, value: string])[]
|
|
): NodeJS.ProcessEnv {
|
|
const next = { ...env }
|
|
const base = readValidGitConfigEnvCount(env)
|
|
if (base === null) {
|
|
// Why: ambiguous protocol state may contain caller data at any index, so
|
|
// scalar guards are safer than overwriting it with Orca-owned entries.
|
|
return next
|
|
}
|
|
entries.forEach(([key, value], index) => {
|
|
next[`GIT_CONFIG_KEY_${base + index}`] = key
|
|
next[`GIT_CONFIG_VALUE_${base + index}`] = value
|
|
})
|
|
next.GIT_CONFIG_COUNT = String(base + entries.length)
|
|
return next
|
|
}
|
|
|
|
/**
|
|
* Disable interactive Git credential UI while preserving cached credentials
|
|
* and caller-provided askpass programs.
|
|
*/
|
|
export function gitCredentialPromptGuardEnv(
|
|
env: NodeJS.ProcessEnv,
|
|
platform: NodeJS.Platform = process.platform
|
|
): NodeJS.ProcessEnv {
|
|
const next = appendGitConfigEnv(
|
|
{
|
|
...env,
|
|
GIT_TERMINAL_PROMPT: '0',
|
|
GIT_ASKPASS: env.GIT_ASKPASS ?? '',
|
|
SSH_ASKPASS: env.SSH_ASKPASS ?? '',
|
|
// Why: GCM can ignore terminal/askpass guards and open its own GUI.
|
|
GCM_INTERACTIVE: 'never'
|
|
},
|
|
// Why: keep the helper so cached credentials continue to work; disable
|
|
// only its interactive fallback.
|
|
[
|
|
['credential.interactive', 'false'],
|
|
['credential.guiPrompt', 'false']
|
|
]
|
|
)
|
|
if (platform === 'win32') {
|
|
// Why: wsl.exe imports only variables registered in WSLENV. Indexed Git
|
|
// config must cross as a complete set or Git rejects the count.
|
|
const configKeys =
|
|
readValidGitConfigEnvCount(next) === null
|
|
? []
|
|
: Object.keys(next).filter((key) => GIT_CONFIG_WSLENV_KEY_RE.test(key))
|
|
addWslEnvKeys(next, ['GIT_TERMINAL_PROMPT', 'GCM_INTERACTIVE', ...configKeys])
|
|
}
|
|
return next
|
|
}
|