Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/workflows/sandbox-log-redaction-quality-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Sandbox Log Redaction Quality CI

on:
pull_request:
branches: [main]
paths:
- ".github/workflows/sandbox-log-redaction-quality-ci.yml"
- "ARCHITECTURE.md"
- "CHANGELOG.md"
- "docs/doctoring/sandbox-log-redaction.md"
- "scripts/ci/redact_sensitive_log.py"
- "scripts/ci/sandboxed_verify.py"
- "scripts/ci/sandboxed_web_e2e.py"
- "tests/test_atomic_json_redaction.py"
- "tests/test_command_wrapper_redaction.py"
- "tests/test_opencode_security_boundaries.py"
- "tests/test_sandboxed_verify.py"
- "tests/test_sandboxed_web_e2e.py"
- "tests/test_sandboxed_log_redaction_regression.py"

permissions:
contents: read

concurrency:
group: sandbox-log-redaction-quality-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
exact-head-redaction-contract:
name: Exact-head sandbox redaction contract
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout exact source revision
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"

- name: Install exact hash-verified test dependencies
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_NO_INPUT: "1"
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
cat >"${RUNNER_TEMP}/sandbox-redaction-quality-requirements.txt" <<'EOF'
coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f
iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760
packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e
pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c
EOF
python -m pip install \
--only-binary=:all: \
--require-hashes \
-r "${RUNNER_TEMP}/sandbox-redaction-quality-requirements.txt"

- name: Verify fail-closed sandbox redaction contract
env:
STRIX_TEST_PROCESS_TIMEOUT_SECONDS: "3"
STRIX_TEST_FAKE_SLEEP_SECONDS: "5"
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}"
python -m coverage run --branch -m pytest \
tests/test_atomic_json_redaction.py \
tests/test_command_wrapper_redaction.py \
tests/test_opencode_security_boundaries.py \
tests/test_sandboxed_verify.py \
tests/test_sandboxed_web_e2e.py \
tests/test_sandboxed_log_redaction_regression.py \
-q
python -m coverage report \
--include='scripts/ci/redact_sensitive_log.py,scripts/ci/sandboxed_verify.py,scripts/ci/sandboxed_web_e2e.py' \
--fail-under=100
python - <<'PY'
import ast
from pathlib import Path

missing = []
for filename in (
"scripts/ci/redact_sensitive_log.py",
"scripts/ci/sandboxed_verify.py",
"scripts/ci/sandboxed_web_e2e.py",
):
tree = ast.parse(Path(filename).read_text(encoding="utf-8"), filename=filename)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
if not node.name.startswith("_") and ast.get_docstring(node) is None:
missing.append(f"{filename}:{node.lineno}:{node.name}")
if missing:
raise SystemExit("public docstrings missing: " + ", ".join(missing))
PY
python -m pytest tests -q
bash scripts/ci/test_strix_quick_gate.sh
python -m compileall -q \
scripts/ci/redact_sensitive_log.py \
scripts/ci/sandboxed_verify.py \
scripts/ci/sandboxed_web_e2e.py \
tests/test_atomic_json_redaction.py \
tests/test_command_wrapper_redaction.py \
tests/test_opencode_security_boundaries.py \
tests/test_sandboxed_verify.py \
tests/test_sandboxed_web_e2e.py \
tests/test_sandboxed_log_redaction_regression.py
git diff --exit-code
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md).
Conflict-scope roots fail closed when the immediate parent directory is a symbolic link.
Downloaded Actions job logs keep per-line RFC 3339 runner timestamps (`Z` or `time-numoffset`, SPACE or HTAB); `redact_sensitive_log` skips them inside JSON spans and does not treat `[INFO]` as an array opener. See [`docs/doctoring/sandbox-log-redaction.md`](docs/doctoring/sandbox-log-redaction.md).
39 changes: 38 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,30 @@ sequenceDiagram
PR->>RW: pull_request_target on trusted base
RW->>OC: bounded evidence + NVIDIA NIM / OpenCode
OC->>SV: PoC command in isolated copy
SV-->>OC: redacted stdout/stderr + command metadata
SV-->>OC: layout-preserving redacted stdout/stderr + command metadata
OC-->>PR: APPROVE or request changes
MS->>PR: merge only on current-head approval + green checks
```

## Sandbox evidence redaction

```mermaid
flowchart TD
Cap["Captured stdout / stderr / service tail"]
Span["Bounded JSON span rewriter"]
Line["Line-oriented fallback"]
Pub["CI / review evidence"]

Cap --> Span
Span -->|"complete JSON span"| Pub
Span -->|"no complete span"| Line
Line --> Pub
```

Operators reading a pretty-printed job log should still see the original
layout, duplicate keys, and scalar categories. Only credential leaves are
replaced. See [`docs/doctoring/sandbox-log-redaction.md`](docs/doctoring/sandbox-log-redaction.md).

## Trust boundaries

- Required review workflows execute **base-branch** scripts. A PR that edits
Expand All @@ -84,6 +103,22 @@ sequenceDiagram
- Logs and review receipts redact credential shapes (tokens, bearer values,
known provider prefixes). They do not mask operational PII that the
control plane must process.
- Raw JSON evidence is rewritten as source spans before any line split.
Duplicate member names keep order and count because RFC 8259 §4 treats
receiver behavior as unpredictable, while ECMA-404 / ISO/IEC 21778 leave
uniqueness to the processor (Bray, 2017; Ecma International, 2017;
International Organization for Standardization, 2017). A dictionary
collapse would drop the first secret of a duplicate `token` pair. A
failed opener is scored only until the next plausible start, so
`##[group]` and prose `[timeout]` cannot erase a later complete object.
Downloaded Actions job logs prefix every line with an RFC 3339
runner timestamp. The span parser skips `Z` and `time-numoffset`
prefixes plus a following SPACE or HTAB the same way it skips JSON
whitespace, so a pretty-printed password object remains one span. A `[` opens an array
only when the next significant token can start a JSON value
(`true` / `false` / `null` / number / string / container / `]`), so
line-start `[INFO]` diagnostics stay visible (Klyne & Newman, 2002;
Bray, 2017).
- LLM and scheduled agents bind `NVIDIA_NIM_API_KEY` (env may be
`NVIDIA_API_KEY`). They never use `COPILOT_GITHUB_TOKEN`. Existing
review-agent key schemes stay unchanged.
Expand All @@ -104,6 +139,8 @@ tests pin workflow structure and governance prose so drift fails closed.
— Project #1 operation.
- [`PR_GOVERNANCE_AUDIT.md`](PR_GOVERNANCE_AUDIT.md) — live review/merge
contract.
- [`docs/doctoring/sandbox-log-redaction.md`](docs/doctoring/sandbox-log-redaction.md)
— atomic JSON evidence redaction, RFC 8259 / ECMA-404 / ISO/IEC 21778.
- [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md)
— current increment's repair-worker decision and APA 7th citations.
- [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md)
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Treat GitHub Actions runner timestamps as line metadata inside raw JSON spans, and treat `[` as an array opener only when the next significant token can start a JSON value, so a downloaded pretty-printed password dump keeps `##[group]` / status text instead of fail-closing the entire job log to `[REDACTED]`.
- Skip RFC 3339 `time-numoffset` prefixes (`+00:00`, `-07:00`) and a following space or HTAB the same way `Z` timestamps are skipped, so a collector that emits offsets or tab-separated job logs cannot fail-close a later pretty-printed password object.
- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.
- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367).
- Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics.
Expand All @@ -39,6 +41,10 @@ Semantic Versioning where the repository publishes a release.

### Security

- Preserve raw JSON layout atomically during sandbox log redaction so command wrappers cannot leak secrets through pretty-printed dumps.
- Cite RFC 8259, ECMA-404, and ISO/IEC 21778 for duplicate JSON member handling, pin the sandbox redaction quality-gate operator prose, and keep architecture drift on the same exact-head quality path.
- Stop treating GitHub Actions `##[group]` markers and prose `[timeout]` brackets as JSON array starts, so a later pretty-printed password object is rewritten in place instead of erasing the whole job log.

- Reject `.github/` and `scripts/ci/` from review-thread-derived autofix path authority so an untrusted inline reviewer cannot authorize the write-capable repair agent to modify workflows, CODEOWNERS, actions, scheduler code, or CI helpers that govern its own control plane.
- Require the model-write snapshot and exact-path allowlist to remain outside the pull-request worktree, checking both absolute and resolved locations so repository-local controls and outside-looking symlinks resolving into the repository fail closed before they can authorize or verify model changes.
- Snapshot the complete pre-model worktree for ordinary and conflict repair and reject every model-caused created, deleted, modified, mode-changed, retargeted, ignored, dangling, directory-backed, external-link, metadata-race, or out-of-scope path before staging or push.
Expand All @@ -60,4 +66,5 @@ Semantic Versioning where the repository publishes a release.
- Added DiskSage operational documentation for the hourly RCA loop, bounded retry cadence, permission model, standalone and MSA reuse, verification, rollback, and APA 7 references.
- Added fast-mlsirm operational documentation for the hourly RCA loop, psychometric scientific gates, Rust ownership, bounded retry cadence, credential isolation, modular reuse, rollback, and APA 7 references.
- Documented the ordinary and conflict repair write-scope parity, ignored-path and symlink inventory, Git-control-file denial, hook suppression, explicit push destination, RED/GREEN evidence, operator response, and local-versus-protected evidence boundary.
- Documented the review-authentication boundary that excludes autonomous writer control-plane paths from review-derived file authority, its test-first Strix security evidence, exact-head coverage contract, and rollback prohibition.
- Documented the review-authentication boundary that excludes autonomous writer control-plane paths from review-derived file authority, its test-first Strix security evidence, exact-head coverage contract, and rollback prohibition.
- Recorded RFC 8259 unpredictable duplicate-name behavior, ECMA-404 / ISO/IEC 21778 syntax neutrality, and the operator next step: treat `[REDACTED]` as evidence suppression, then rerun the exact-head sandbox redaction quality job after any layout-preserving change.
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ repeatable compile command.
- **Review output must go through the Python normalizer** (`scripts/ci/opencode_review_normalize_output.py`)
— it escapes `<`, `>`, `&` when embedding JSON in HTML comments to prevent Markdown-comment
breakout. Do not reintroduce bash fast-path extraction.
- **Downloaded Actions job logs keep per-line RFC 3339 runner timestamps.** `redact_sensitive_log`
skips `Z` and `time-numoffset` prefixes plus SPACE or HTAB inside JSON spans and opens `[` only
for a real JSON value, so `##[group]` and `[INFO]` diagnostics are not fail-closed to
`[REDACTED]`. See `docs/doctoring/sandbox-log-redaction.md`.
- **Cloudflare changes are dry-run by default**; nothing is deleted unless `prune = true` is set
explicitly. PRs never see the Cloudflare API token.
- **Org-wide binding conventions** (permissive licenses only — verify SPDX before adding anything;
Expand Down
Loading
Loading