fix(pwsh): close the remaining review threads
- scrubbedParentEnv folds case before the DSH_ prefix check (Windows env
names are case-insensitive; a parent dsh_* entry read back as \*
in the child) and the service spec pins the lowercase probe.
- The acp.snapshot.ts pwsh probe follows resolvePwshPath() like the package
suites, so a Windows host with only an install-location pwsh still runs
the scenario.
- pwsh-tool-turn is re-recorded around [Console]::Out.Write('PWSH_OK'):
the fixture carries no platform newline, so one recording replays on
Windows and POSIX alike (record + refresh; replay-verified keyless).
- The pwsh-local Known Limitations bullet drops the self-defeating no-op
advice: & { } is scoped to param(...), using/#requires scripts run from a
file (both languages, pairing re-recorded).
- The capability-seams graph moves ctx.bashEnv ownership to bash-env and
lists pwsh-local/tool-pwsh on the ctx.bash seam (source updated,
docs regenerated).
- The tool-bash presenter fixture retires the stale 'command aborted'
literal for the shipped 'tool call aborted' message.
This commit is contained in:
@@ -45,7 +45,10 @@ export const SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i
|
||||
* `HOME`, locale, and proxy variables survive, so child CLIs run normally;
|
||||
* harness identity never leaks implicitly (a deliberately forwarded
|
||||
* credential or current `DSH_*` fact goes through the spec's explicit `env`,
|
||||
* which merges after this scrub). Exported as a plain function so spawners
|
||||
* which merges after this scrub). Both scrubs match case-insensitively:
|
||||
* Windows environment names are case-insensitive, so a parent `dsh_*` entry
|
||||
* would otherwise survive and read back as `$env:DSH_*` in the child;
|
||||
* deliberate lowercase `dsh_*` names on POSIX are implausible. Exported as a plain function so spawners
|
||||
* that cannot route through the service (node-pty backends, SDK-managed
|
||||
* transports) share the one scrub definition.
|
||||
* @returns a fresh environment object safe to hand to a child spawn.
|
||||
@@ -53,7 +56,7 @@ export const SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i
|
||||
export function scrubbedParentEnv(): Record<string, string> {
|
||||
const env: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value !== undefined && !SENSITIVE_ENV_PATTERN.test(key) && !key.startsWith(DSH_ENV_PREFIX)) env[key] = value
|
||||
if (value !== undefined && !SENSITIVE_ENV_PATTERN.test(key) && !key.toUpperCase().startsWith(DSH_ENV_PREFIX)) env[key] = value
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
@@ -52,20 +52,23 @@ describe('SubprocessService seam', () => {
|
||||
await expect(ctx.plugin(SecondService)).rejects.toThrow(/service "subprocess" has been registered/)
|
||||
})
|
||||
|
||||
it('scrubbedParentEnv drops credential-shaped and DSH_ names but keeps PATH', () => {
|
||||
it('scrubbedParentEnv drops credential-shaped and DSH_ names (case-insensitively) but keeps PATH', () => {
|
||||
process.env.DSH_SCRUB_PROBE = 'stale'
|
||||
process.env.dsh_scrub_probe_lower = 'stale'
|
||||
process.env.SCRUB_PROBE_TOKEN = 'secret'
|
||||
process.env.SCRUB_PROBE_PASSWORD = 'secret'
|
||||
process.env.SCRUB_PROBE_PLAIN = 'visible'
|
||||
try {
|
||||
const env = scrubbedParentEnv()
|
||||
expect(env.DSH_SCRUB_PROBE).toBeUndefined()
|
||||
expect(env.dsh_scrub_probe_lower).toBeUndefined()
|
||||
expect(env.SCRUB_PROBE_TOKEN).toBeUndefined()
|
||||
expect(env.SCRUB_PROBE_PASSWORD).toBeUndefined()
|
||||
expect(env.SCRUB_PROBE_PLAIN).toBe('visible')
|
||||
expect(env.PATH).toBeDefined()
|
||||
} finally {
|
||||
delete process.env.DSH_SCRUB_PROBE
|
||||
delete process.env.dsh_scrub_probe_lower
|
||||
delete process.env.SCRUB_PROBE_TOKEN
|
||||
delete process.env.SCRUB_PROBE_PASSWORD
|
||||
delete process.env.SCRUB_PROBE_PLAIN
|
||||
|
||||
Reference in New Issue
Block a user