Skip to content

fix(security): strip PM_CLI_BRIDGE_PASSWORD from child process env - #20

Merged
bscott merged 1 commit into
masterfrom
fix/scrub-bridge-password-from-child-env
Aug 8, 2026
Merged

fix(security): strip PM_CLI_BRIDGE_PASSWORD from child process env#20
bscott merged 1 commit into
masterfrom
fix/scrub-bridge-password-from-child-env

Conversation

@bscott

@bscott bscott commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Blocks the v0.2.6 release.

The leak

MailWatchCmd.executeCommand built its child environment as append(os.Environ(), ...). Since #14 added PM_CLI_BRIDGE_PASSWORD, that variable was inherited by every command mail watch --exec runs, and by anything those commands shell out to. A third-party triage script gets the mail password.

Reproduced against a real child process before the fix:

child sees: PM_CLI_BRIDGE_PASSWORD=[bridge-secret-abc123]

Worth being precise about provenance: the env-var credential path (#14) and this spawn site were each fine in isolation. The exposure exists only once both are present, which is first true in 0.2.6 — so this is introduced by the unreleased version, not a pre-existing issue, and is fixed before the release ships rather than disclosed as a caveat.

The fix

Adds config.ScrubSecrets(env []string) []string, which removes pm-cli credential variables from an environment slice, and routes the single exec.Command site through it. The secret list lives beside the constant it names, so a future credential variable is one entry.

Two deliberate details:

  • Exact-name matching, not prefix. PM_CLI_BRIDGE_PASSWORD_BACKUP is unrelated and survives.
  • Bare entries with no = are dropped. Go permits them, and dropping is the safe direction.

The input slice is not mutated.

Tests

Eight cases in internal/config/scrub_secrets_test.go, including two that spawn a real sh child:

  • TestScrubbedEnvironNotVisibleToChild — the child reads an empty value through the scrubbed environment.
  • TestUnscrubbedEnvironWouldLeak — pins the regression by confirming os.Environ() really does propagate the credential, so the scrubbing above stays load-bearing.

Also

SECURITY.md documented only the keyring and never mentioned PM_CLI_BRIDGE_PASSWORD — a credential mechanism absent from the security policy. Adds a subsection covering the tradeoff honestly: readable by same-user processes, visible in /proc/<pid>/environ, and now excluded from spawned children.

gofmt, go vet, go test ./... all clean.

🤖 Generated with Claude Code

mail watch --exec built its child environment with
append(os.Environ(), ...), so the Bridge password supplied via
PM_CLI_BRIDGE_PASSWORD was inherited by the user-supplied command and by
everything that command shelled out to. Any third-party triage script
could read the mail credential.

The env-var credential path and this spawn site were each fine on their
own; the exposure only exists once both are present, which is first true
in 0.2.6. It is therefore introduced by the unreleased version rather
than pre-existing, and is fixed before the release goes out.

Adds config.ScrubSecrets, which removes pm-cli credential variables from
an environment slice, and routes the exec site through it. The secret
list lives next to the variable it names, so a future credential
variable is covered by adding one entry.

Matching on the exact variable name rather than a prefix keeps unrelated
names such as PM_CLI_BRIDGE_PASSWORD_BACKUP intact; bare entries with no
"=" are dropped, since that is the safe direction.

Tested with a real child process: the scrubbed environment exposes
nothing, while the unscrubbed one reproduces the leak.

Also documents the environment-variable credential path in SECURITY.md,
which described only the keyring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bscott
bscott merged commit 34f275b into master Aug 8, 2026
1 check passed
@bscott
bscott deleted the fix/scrub-bridge-password-from-child-env branch August 8, 2026 23:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant