Goal
Route delegated-command output captured by basectl workspace test and basectl workspace setup through Base's existing subprocess redaction before it reaches stdout, stderr, or the stable JSON report.
Background
Release review on 2026-10-01 against Base 951b16d60597e4c6bf67a9701c55739f962ccaaa.
Base already owns a redaction helper for captured child output, base_setup.process.redact_command_output, which masks NAME=<secret> assignments and scheme://user:password@host URL credentials. #2396 hardened that path so redaction survives read boundaries.
The workspace fan-out commands bypass it. workspace_test.execute_workspace_test_target captures the delegated basectl test run with subprocess.run(capture_output=True) and stores the raw text on WorkspaceTestResult. print_workspace_test_result echoes it verbatim, and workspace_test_project_to_json copies it verbatim into the stdout/stderr keys of the schema_version: 1 report. workspace_setup relays captured setup output the same way.
Reproduced locally with a two-file throwaway workspace whose project test command prints secret-shaped lines:
# demo/run-tests.sh
echo "GITHUB_TOKEN=ghp_LEAKEDSECRET0123456789"
echo "cloning https://alice:ghp_URLSECRET9876@github.com/acme/private.git"
exit 1
basectl workspace test --workspace <ws> --manifest <ws>/workspace.yaml --format json emitted:
"stdout": "GITHUB_TOKEN=ghp_LEAKEDSECRET0123456789\ncloning https://alice:ghp_URLSECRET9876@github.com/acme/private.git\n",
The same two lines passed through redact_command_output return:
GITHUB_TOKEN=[REDACTED]
cloning https://[REDACTED]@github.com/acme/private.git
This matters because the delegated command is project-owned and arbitrary: a failing test, a build script, or a tool that echoes its environment can place a credential in Base's own machine-readable output. That report is explicitly designed for CI consumption, so the value typically lands in a CI log, an artifact, or a pasted issue comment. workspace update and workspace clone have a narrower version of the same shape: git_pull_detail and clone_detail fold raw Git stdout/stderr into the stable detail field.
No secret was exposed outside the local throwaway workspace while reproducing this.
Scope
- Apply the existing subprocess redaction to child output captured by
workspace test and workspace setup before printing it and before serializing it into JSON.
- Use the same helper rather than a second redaction implementation, so
base_setup and base_projects cannot drift.
- Audit the remaining workspace fan-out paths that fold child output into stable fields (
workspace_update.git_pull_detail, workspace_update.format_git_pull_debug_output, workspace_clone_command.clone_detail) and apply redaction where child output reaches user or machine-readable output.
- Keep the redacted text readable: redaction should mask values, not drop lines or reorder output.
Acceptance Criteria
Validation
Build the throwaway workspace above, run basectl workspace test --format json and the text form, and assert the token does not appear. Then run:
BASE_CLI_SOURCE_DIR=../base-cli/lib/python \
PYTHONPATH=../base-cli/lib/python:lib/python:cli/python \
python -m pytest cli/python/base_projects/tests cli/python/base_setup/tests/test_artifacts.py
bats cli/bash/commands/basectl/tests/workspace.bats
basectl test base
Non-Goals
Do not add a new redaction engine, do not suppress or truncate child output, and do not change the workspace test or workspace update JSON schema shape beyond the value of already-defined string fields.
Project Fields
- Status: Ready
- Priority: P1
- Area: Workspace
- Initiative: Contract Hardening
- Size: M
- Milestone: v1.10.0
- Category: security
Agent Assignment
Assignee: codeforester. Implementation-ready; use an issue-backed worktree and reuse base_setup.process.redact_command_output rather than adding a parallel implementation.
Related: #2432 publishes the workspace test JSON schema and should land after this redaction fix so the schema documents redacted values.
Goal
Route delegated-command output captured by
basectl workspace testandbasectl workspace setupthrough Base's existing subprocess redaction before it reaches stdout, stderr, or the stable JSON report.Background
Release review on 2026-10-01 against Base
951b16d60597e4c6bf67a9701c55739f962ccaaa.Base already owns a redaction helper for captured child output,
base_setup.process.redact_command_output, which masksNAME=<secret>assignments andscheme://user:password@hostURL credentials. #2396 hardened that path so redaction survives read boundaries.The workspace fan-out commands bypass it.
workspace_test.execute_workspace_test_targetcaptures the delegatedbasectl testrun withsubprocess.run(capture_output=True)and stores the raw text onWorkspaceTestResult.print_workspace_test_resultechoes it verbatim, andworkspace_test_project_to_jsoncopies it verbatim into thestdout/stderrkeys of theschema_version: 1report.workspace_setuprelays captured setup output the same way.Reproduced locally with a two-file throwaway workspace whose project test command prints secret-shaped lines:
basectl workspace test --workspace <ws> --manifest <ws>/workspace.yaml --format jsonemitted:The same two lines passed through
redact_command_outputreturn:This matters because the delegated command is project-owned and arbitrary: a failing test, a build script, or a tool that echoes its environment can place a credential in Base's own machine-readable output. That report is explicitly designed for CI consumption, so the value typically lands in a CI log, an artifact, or a pasted issue comment.
workspace updateandworkspace clonehave a narrower version of the same shape:git_pull_detailandclone_detailfold raw Git stdout/stderr into the stabledetailfield.No secret was exposed outside the local throwaway workspace while reproducing this.
Scope
workspace testandworkspace setupbefore printing it and before serializing it into JSON.base_setupandbase_projectscannot drift.workspace_update.git_pull_detail,workspace_update.format_git_pull_debug_output,workspace_clone_command.clone_detail) and apply redaction where child output reaches user or machine-readable output.Acceptance Criteria
basectl workspace test --format jsonmasksNAME=<secret>assignments and URL credentials in thestdoutandstderrkeys.basectl workspace testtext output masks the same values on stdout and stderr.basectl workspace setupmasks the same values in relayed setup output.workspace updateandworkspace clonedetailfields carry redacted child output.Validation
Build the throwaway workspace above, run
basectl workspace test --format jsonand the text form, and assert the token does not appear. Then run:BASE_CLI_SOURCE_DIR=../base-cli/lib/python \ PYTHONPATH=../base-cli/lib/python:lib/python:cli/python \ python -m pytest cli/python/base_projects/tests cli/python/base_setup/tests/test_artifacts.py bats cli/bash/commands/basectl/tests/workspace.bats basectl test baseNon-Goals
Do not add a new redaction engine, do not suppress or truncate child output, and do not change the
workspace testorworkspace updateJSON schema shape beyond the value of already-defined string fields.Project Fields
Agent Assignment
Assignee: codeforester. Implementation-ready; use an issue-backed worktree and reuse
base_setup.process.redact_command_outputrather than adding a parallel implementation.Related: #2432 publishes the
workspace testJSON schema and should land after this redaction fix so the schema documents redacted values.