Menu

#398 feat(rules): step up bare env/printenv/export/declare to AUTH pre-execution

closed
nobody
None
2026-08-24
2026-08-17
Anonymous
No

Originally created by: QY-25123

Pull Request

Slice

  • Repo: doberman-core
  • Feature / Slice: ad-hoc — environment-dump detection (found live during a Doberman-guarded session, not from the roadmap)
  • Plan reference: n/a (level-7 additive engine change per CONTRIBUTING.md's ladder — new raise-only rule, no existing risk classification modified)

What this PR does

DestructiveCommandRule lists env in its transparent-wrapper set, for the legitimate env FOO=bar real_command idiom. But a bare env invocation with no trailing command strips down to an empty token list and is silently skipped (if not tokens: continue) before any rule ever sees it. printenv/export/declare -x had no dedicated check at all. All of these read the process environment locally — a common carrier for API keys and tokens — with no pre-execution gate, only a same-turn output scan after the command had already run and the secret had already reached the agent's context.

This was found live: an env call in a Claude-Code-hooked session printed a real API key straight into the conversation. The PostToolUse output scan flagged it after the fact (sensitive_secret_access), but the value had already been read by that point.

Adds _is_environment_dump_segment(), checked on the raw parsed segment before wrapper-stripping (so it sees env/printenv before _argv_from_tokens would otherwise erase them). Recognizes:

  • bare env (with its no-op/unset flags: -i, -0, -u VAR, --unset=VAR)
  • printenv (any form, including with a specific variable name — reading the process environment is the concern, not just a full dump)
  • export / export -p
  • declare -x / typeset -x with no named variable
  • the PowerShell Env: drive listing (Get-ChildItem Env: / gci env: / dir env: / ls Env:)

All step up to AUTH pre-execution via a new reason code, environment_dump_command — mirroring how sensitive_secret_access already treats a local read of a secret file (.env, id_rsa, …): AUTH, not BLOCK, since there's no external destination yet. Legitimate uses are unaffected: env FOO=bar real_command (env as a wrapper) and export FOO=bar / declare -x FOO=bar (setting one specific variable) still PASS.

Known, documented limits (see the new README "Known limitations" bullet): bare POSIX set and Windows cmd.exe bare set are deliberately out of scope — both list shell variables/functions the same way, but set is also extremely common as set -e/set -euo pipefail in ordinary scripts, so flagging it bare would carry a high false-positive/alert-fatigue cost for a comparatively rare abuse vector (consistent with this project's existing precedent of relaxing a check that fired on the vast majority of benign calls). A literal trailing backslash (dir env:\) isn't in the rule's Windows-path trigger list, so POSIX shlex parsing fails on it first and it falls back to the generic opaque_command AUTH instead — still fails upward, just under a different reason code; the no-backslash form (dir env:) is unaffected.

Tests added (run in CI)

  • tests/unit/test_rule_commands.py: 16 new parametrized cases confirming AUTH + environment_dump_command for every recognized dump form (bare env, flag variants, sudo env, all printenv forms, export/export -p, declare -x/typeset -x, all four PowerShell Env: listing spellings), 7 cases confirming legitimate non-dump usage still PASS (env-as-wrapper, setting a named variable, a similarly-named file), one case for the env FOO=bar (no trailing command) edge case per POSIX env semantics, and one confirming the explanation never echoes the raw command text.

Also ran (not new, but relevant to this change area): pytest tests/unit/test_objective_guardrail.py (the full rule-combination integration suite CONTRIBUTING.md points to for "Policy / engine rules" changes) — passes unchanged.

Public-release safety (doberman-core only)

  • [x] Contains nothing from the "not allowed" list: no enterprise/hosted code, no proprietary detection, no customer data, no secrets, no commercial-license code
  • [x] Core still builds/tests/runs with NO enterprise package installed

Security checklist

  • [x] Fails closed on error / uncertainty
  • [x] No secret, full file, or unredacted prompt logged or committed
  • [x] Any guardrail/learning change is raise-only (no silent loosening) — purely additive AUTH step-up on a new predicate, never lowers an existing verdict
  • [x] Every BLOCK/AUTH carries reason codes + a human explanation
  • [x] doberman-core does not import doberman_enterprise

Edge cases covered / Deviations from plan / Risks introduced

Covered: sudo env (wrapper-through-wrapper), env with only no-op/unset flags and no command, PowerShell drive-listing spelled 4 different ways, declare -r FOO (no -x, must not trigger), a file literally named env-notes.txt passed to ls (must not false-positive on the PowerShell drive check).

Deviation from a fully generic fix: bare set (POSIX and cmd.exe) is intentionally NOT covered, for the alert-fatigue reason above — documented as a known limitation rather than silently scoped out.

One design choice worth a maintainer's eye: printenv <SPECIFIC_VAR> (a targeted read, not a full dump) is treated the same as bare printenv — I judged "reads the process environment" as the operative risk rather than "dumps everything," but this is a slightly broader net than a minimal fix would draw, and I'm open to narrowing it if that's not the intended scope for this rule.

Risk: none identified — this only adds a new AUTH step-up path on a narrow, verb-based predicate; it cannot lower any existing verdict, and the new check runs before, not instead of, the existing per-segment classification, so no existing branch's behavior changes.

AI assistance disclosure

This change (the finding, the fix, the tests, and this PR description) was developed with Claude Code, with human review and direction throughout. The gap was found live during a Doberman-guarded coding session (an env call printed a real secret with no pre-execution check), then verified against the codebase, fixed, and checked locally (ruff, ruff format --check, lint-imports, the targeted and related pytest suites) before opening this PR. Full CI (Ubuntu 3.11/3.12/3.13, Windows, secret-scan, package-smoke-test) is green on the originating fork PR: https://github.com/QY-25123/Doberman-Core/pull/1

Related

Tickets: #399
Tickets: #401
Tickets: #455

Discussion

  • Anonymous

    Anonymous - 2026-08-19

    Originally posted by: fu351

    Heads-up, and nothing wrong with the change itself: a stack of PRs landed on main today, so this one picked up a merge conflict (when several land at once, CHANGELOG.md is almost always the culprit). Your env-dump step-up is good and I want it in, so a rebase is all it needs: git fetch && git rebase origin/main, resolve the small conflict, then force-push. Ping me once it is green and I will take the merge. Thanks for riding out the churn.

     
  • Anonymous

    Anonymous - 2026-08-19

    Originally posted by: QY-25123

    Rebased onto current main and resolved the CHANGELOG conflict — CI is green across the board. Ready for you to take the merge whenever.

     
  • Anonymous

    Anonymous - 2026-08-24

    Ticket changed by: fu351

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-08-24

    Originally posted by: fu351

    Merged, thanks @QY-25123! The root-cause fix is what makes this one good: bare env was being erased by the transparent-wrapper list before any rule could see it, and checking the raw segment before wrapper-stripping fixes the actual mechanism instead of patching a symptom, with the pipeline and && cases covered for free by the existing segmentation. Forget the rebase I asked for earlier, the conflict kept growing on my side of the fence, so I landed it through [#455] with your commits untouched, keeping main's newer README wording for the Codex installer paragraph plus your new limitations bullet.

     

    Related

    Tickets: #455


Log in to post a comment.