Repository navigation
Replies: 3 comments
Confirmed — and the half tuple also forwards the value it was meant to protectReproduced from source and by experiment on The classifier is name-only. export const SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i // :47
...
if (value !== undefined && !SENSITIVE_ENV_PATTERN.test(key)
&& !key.toUpperCase().startsWith(DSH_ENV_PREFIX)) env[key] = value // :69
Reproduction. A parent carrying the ordinary
Row 3 is the documented contract working as designed ("a deliberately supplied entry survives, because explicit env layers merge after the scrub"), and it is also the escape hatch for anyone carrying a local patch: re-supplying the missing The second consequence: the value half still reaches the child. Fix shape. Decide group-structured names atomically: when any member of a
A fix has one home and no consumer to chase: the doc comment at |
|
Follow-up with a tested patch. @deniskern's diagnosis and @argszero's confirmation cover the Shape of the fixTreat the family as a group. Skip it in the per-name loop, decide it once, and drop it whole export const GIT_CONFIG_ENV_FAMILY = /^GIT_CONFIG_(?:COUNT|KEY_\d+|VALUE_\d+)$/u
/** A config value carrying a credential, in the shapes Git itself documents. */
const GIT_CONFIG_CREDENTIAL_VALUE =
/AUTHORIZATION|BEARER|PRIVATE[_-]?KEY|PASSWORD|SECRET|TOKEN|API[_-]?KEY|:\/\/[^/\s]*:[^/\s]*@/iu
/** Config names that exist to carry an authorization header. */
const GIT_CONFIG_CREDENTIAL_NAME = /(?:^|\.)extraheader$/iu
export function gitConfigEnvFamily(source: NodeJS.ProcessEnv): Record<string, string> | null {
if (!Object.keys(source).some((name) => GIT_CONFIG_ENV_FAMILY.test(name))) return null
const count = source.GIT_CONFIG_COUNT
if (count === undefined || !/^(?:0|[1-9]\d*)$/u.test(count)) return null
const total = Number(count)
if (!Number.isSafeInteger(total)) return null
// Exactly one KEY and one VALUE per pair: a stray index makes Git reject the
// whole set, so an unexpected member drops the group instead of forwarding it.
const members = Object.keys(source).filter((name) => GIT_CONFIG_ENV_FAMILY.test(name))
if (members.length !== total * 2 + 1) return null
const forwarded: Record<string, string> = { GIT_CONFIG_COUNT: String(total) }
for (let index = 0; index < total; index += 1) {
const name = source[`GIT_CONFIG_KEY_${index}`]
const value = source[`GIT_CONFIG_VALUE_${index}`]
if (name === undefined || value === undefined) return null
if (GIT_CONFIG_CREDENTIAL_NAME.test(name) || GIT_CONFIG_CREDENTIAL_VALUE.test(value)) return null
forwarded[`GIT_CONFIG_KEY_${index}`] = name
forwarded[`GIT_CONFIG_VALUE_${index}`] = value
}
return forwarded
}In if (value !== undefined && !GIT_CONFIG_ENV_FAMILY.test(key) && !SENSITIVE_ENV_PATTERN.test(key) && !key.toUpperCase().startsWith(DSH_ENV_PREFIX)) env[key] = value
…
const gitConfig = gitConfigEnvFamily(process.env)
if (gitConfig !== null) Object.assign(env, gitConfig)Why drop the group rather than repair itMeasured against git 2.56.0 on macOS, real repository, child environment taken from
Keeping Tradeoff worth stating: the value-side check is a heuristic too, so a legitimate value that Verification
The existing ScopeTwo files: I am running this locally as a patched Patch — applies to
|
|
Still reproducible on Real-world trigger for us: the Orca agent launcher injects We're carrying a local patch that exempts only |
Uh oh!
There was an error while loading. Please reload this page.
Version:
@deepseek-ai/dsh@0.2.0-rc.2(macOS, npm global install)What happens
scrubbedParentEnv()inpackages/subprocess/subprocessremoves every env name matching/KEY|PASSWORD|SECRET|TOKEN/i. Git's indexed config protocol usesGIT_CONFIG_COUNT,GIT_CONFIG_KEY_<n>andGIT_CONFIG_VALUE_<n>. The scrub dropsGIT_CONFIG_KEY_<n>(it containsKEY) but keepsGIT_CONFIG_COUNTandGIT_CONFIG_VALUE_<n>, so everygitcall in a dsh child fails:This hits anyone whose parent process exports Git config through the environment (IDEs, agent hosts and CI runners do this, e.g. for
safe.directory). The function's own doc comment promises that child CLIs run normally.Repro (against the installed package)
Output:
Suggested fix
Treat the Git config tuple as one unit. Either exempt
^GIT_CONFIG_(COUNT|KEY_\d+|VALUE_\d+)$from the credential filter (the key names are Git config names, not secrets), or drop the whole tuple whenever any part of it is dropped. The same scrub is shared bydsh-subprocess-local,dsh-mcp-client,dsh-web-app,dsh-host-open-in-appanddsh-plugin-manager, so one fix there covers every spawner.Our current workaround: we remove
GIT_CONFIG_COUNTand everyGIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_nbefore launchingdsh, so Git never sees half a tuple.All reactions