Skip to content
Draft
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
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (
Conflict-scope roots fail closed when the immediate parent directory is a symbolic link.
OriginWeave hourly NVIDIA NIM repair is a thin caller at minute 10. See [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md).
nonnest2 hourly NVIDIA NIM repair is a thin caller at minute 16. See [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md).

Downloaded Actions job logs keep per-line RFC 3339 runner timestamps (`Z` or `time-numoffset`, SPACE or HTAB). `gh run view --log-failed` also prefixes `job<HTAB>step<HTAB>` (`UNKNOWN STEP` when unassociated). `redact_sensitive_log` skips both 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).

The materialization contract is also covered by [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md).
42 changes: 41 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,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 @@ -117,6 +136,25 @@ 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. `gh run view --log-failed` also prefixes
`job<HTAB>step<HTAB>` (`UNKNOWN STEP` when unassociated) before that
timestamp. The span parser skips those collector prefixes plus `Z`
and `time-numoffset` and 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 @@ -143,6 +181,8 @@ trusted `uv` exporter is downloaded from the literal GitHub Releases URL for
— bot/agent exact-head review and merge procedure.
- [`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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Semantic Versioning where the repository publishes a release.
- Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109).
- Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109).
- Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories.
- 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.
- Skip the `gh run view --log-failed` `job<HTAB>step<HTAB>` prefix (`UNKNOWN STEP` when unassociated) before that timestamp so the actual failed-check collector excerpt keeps `##[group]` / status text instead of fail-closing a pretty-printed password dump.
- 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).
- 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.
- 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 @@ -54,6 +58,10 @@ Semantic Versioning where the repository publishes a release.
### Security

- Keep the Quarantine Sandbox Runtime caller read-only and model-secret-free, grant only job-scoped OIDC to the reusable scheduler, and preserve the product boundary in which the sandbox returns artifact-analysis evidence while hosts retain WAF/IDS, admission, final verdict, incident, and retention authority.
- 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 @@ -80,6 +88,9 @@ Semantic Versioning where the repository publishes a release.
- 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.

- 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.


- Added an organization-owned reusable exact-artifact SBOM attestation boundary that validates inert six-file wheel/sdist evidence, binds CycloneDX 1.7 predicates to exact SHA-256 subjects, signs through least-privilege GitHub artifact attestations, and exports online and offline verification bundles.
- Hardened exact-artifact SBOM verification with strict finite RFC 8259 JSON, integer CycloneDX document versions, deterministic UUIDv5 subject identities, exact filename properties and single SHA-256 root bindings, environment-only shell input transfer, pinned Ubuntu 24.04 quality runners, and checksum-sealed beginner-readable offline evidence. The decision record now cites Bray (2017) so NaN and Infinity cannot be treated as sealed SBOM numbers.
- Recorded the org control-plane architecture, including exact-artifact SBOM attestation, so agents reconstruct the signing trust boundary from the repo instead of private memory.
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ 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, and the `gh run view --log-failed`
`job<HTAB>step<HTAB>` collector prefix (`UNKNOWN STEP` when unassociated), inside JSON spans.
It 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