Skip to content

Redact bounded env/shell wrapper command operands #907

Description

@seonghobae

Outcome

Sandbox command evidence redacts credentials inside supported wrapper operands without creating unbounded recursive parsing or masking benign command arguments.

Current defect

Draft #906 contains the direct Docker/Podman login -p, -p=, and --password= repair, but the current command-text path tokenizes only the outer command. Direct reproductions remain:

  • env -S 'docker login -p <opaque>';
  • sh -c 'podman login -p <opaque>';
  • nested env → shell -c combinations.

The opaque credential survives because the wrapper operand is treated as ordinary data. Blindly calling the text and argv redactors recursively would instead create attacker-controlled recursion/CPU risk.

Required repair

  • recognize only exact wrapper program basenames and exact option/operand positions:
    • env -S, env --split-string, and the long --split-string=... spelling;
    • sh, bash, dash, ksh, or zsh command strings selected by an exact/combined -c option;
  • preserve the public APIs through private depth/context helpers and compile the literal matcher once at the public entry point;
  • use MAX_COMMAND_WRAPPER_DEPTH = 4, MAX_COMMAND_INPUT_BYTES = 65_536, MAX_COMMAND_TOKENS = 4_096, and MAX_COMMAND_WORK = 262_144; share one UTF-8 byte/token/cumulative-work budget across the complete wrapper tree;
  • at the depth bound, redact the entire remaining nested operand; at a root byte/token/work limit, fail closed for the complete public evidence;
  • fail closed on malformed quoting/tokenization without publishing the raw nested operand or parser diagnostic;
  • preserve executable argv, shell=False, environment, exit, timeout, process-group, and cleanup behavior;
  • preserve benign env -u/--unset, env -C/--chdir, assignments, Docker publish-port -p 8080:80, and SSH port -p 22 diagnostics;
  • keep the direct Docker/Podman fix and its runtime-synthesized fixtures.

Security design constraints

  • Python shlex is not a semantic parser for either full shell -c input or GNU env -S; it may be used only after a linear quote-state scanner accepts a documented grammar subset.
  • For every supported wrapper, backslashes, line breaks, an unquoted $, backtick, or #, and unclosed/unsupported quoting fail closed. For shell operands, unquoted control/operators such as ;, &&, ||, pipes, redirection, substitution, heredoc, parentheses, and glob/meta syntax also fail closed. Expansion inside double quotes is unsupported and fails closed.
  • GNU env -S / --split-string has its own escape, comment, expansion, and trailing-argv behavior. Unsupported #, backslash forms (including \c), ${...}, or any trailing argv that could combine with the split operand fail closed for the complete command evidence.
  • Shell -c following $0/positional argv must be preserved only under an explicitly proven mapping; otherwise the complete command evidence fails closed. A compound command such as sh -c 'echo ok; docker login -p <opaque>' must never be treated as one simple shlex command.
  • Share one precompiled sensitive-value context and the single cumulative budget across every nested wrapper. Depth-only recursion, repeated full rescans, and repeated unbounded shlex.join expansion are forbidden.
  • Malformed syntax, unsupported grammar, depth exhaustion, or budget exhaustion returns bounded redaction without raw text or exception details.
  • Container-login detection is exact: the normalized program is Docker/Podman and command[1] == "login"; never infer the subcommand using membership elsewhere in argv.
  • Fix and pin the current option false positives:
    • docker login --password-stdin registry preserves the registry because the option consumes stdin, not the following argv element;
    • --password-stdin=... remains conservatively redacted because it is not the valid no-argument form;
    • docker run --name login -p 8080:80 image and docker run login -p 8080:80 image preserve the publish port because an unrelated login argument is not the login subcommand.
  • Preserve env -u/--unset/-C/--chdir values, ordinary assignments, Docker publish ports, and SSH ports.
  • Cross-boundary fixtures cover JSON command fields containing wrappers and shell operands containing JSON; unsupported compositions fail closed.

Acceptance

  • fail-first tests cover env -S, env --split-string, long = form, every listed shell basename and combined -c selector, env → shell nesting, malformed inner/outer quoting, compound shell syntax, env escapes/expansion/trailing argv, and a fixed ten-level nesting tree;
  • boundary tests cover input UTF-8 bytes, total parsed tokens, cumulative scan work, maximum depth, and each limit plus one;
  • negative tests prove benign env options/assignments, --password-stdin registry, unrelated login arguments, Docker publish ports, and SSH ports are byte-visible;
  • direct Docker/Podman -p, -p=, and --password= credential cases remain redacted;
  • all credential-shaped fixtures are synthesized at runtime; no fixed credential-shaped literal is committed;
  • Sandbox Log Redaction Quality CI passes at the exact head with 100% statement/branch coverage for the three owned production modules;
  • full suite, compile/docstring checks, Secret Scan over the full PR range, security/supply-chain gates, current source review, independent non-author approval, and protected-main stdout/stderr/timeout/service-tail canaries pass;
  • Bound sandbox subprocess output and long-running service logs #766 remains the separate total-output/service-file resource-safety residual; this issue must not claim end-to-end output-memory closure;
  • fix(sandbox): replay redaction after fixture-history contamination #906 remains Draft until this issue and the separate atomic-JSON boundary are either repaired or explicitly removed from its acceptance scope.

Related: #906, #908, #766.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions