Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1d7ed8a
fix(strix): fail closed on incomplete provider evidence
seonghobae Aug 13, 2026
5215dc9
test(strix): align contracts with fail-closed wrapper
seonghobae Aug 13, 2026
849cee5
fix(strix): fail closed when the gate log contradicts exit 0
seonghobae Aug 13, 2026
f66cc22
fix(ci): require trustworthy Strix evidence
seonghobae Aug 12, 2026
f13b6d5
fix(ci): bind scan reports and kill captured groups
seonghobae Aug 12, 2026
c7ca26d
fix(ci): reject conflicting scan metadata
seonghobae Aug 12, 2026
520f639
test(security): pin Strix dependency advisories
seonghobae Aug 13, 2026
c7d8d23
fix(strix): preserve provider marker smoke contract
seonghobae Aug 13, 2026
5489c51
fix(strix): match hyphenated fail-closed log markers
seonghobae Aug 13, 2026
1fa2c5f
Fail closed on unbound Strix workflow evidence
seonghobae Aug 13, 2026
88ef436
fix(strix): bind scan-start SHA to evidence head
seonghobae Aug 13, 2026
8da91d0
fix(review): require structured Strix status for supersession
seonghobae Aug 13, 2026
b8695c5
fix(strix): refuse run.json without head metadata
seonghobae Aug 13, 2026
4d7267b
docs(strix): record base-workflow false-green evidence
seonghobae Aug 13, 2026
e1cfbed
fix(strix): fail closed on neutral skip markers
seonghobae Aug 13, 2026
30cbb58
docs(strix): record runner cancellation evidence
seonghobae Aug 13, 2026
c3b65ac
fix(strix): reject dotted requirement includes from materialize
seonghobae Aug 13, 2026
b27bdaa
fix(strix): fallback on unsupported agent tool contract
seonghobae Aug 14, 2026
4116fef
docs: record Strix tool contract failure
seonghobae Aug 14, 2026
3a2be84
docs: track stale dependency alerts
seonghobae Aug 14, 2026
49c3da5
docs: record current scoped strix evidence
seonghobae Aug 14, 2026
e76b24a
fix: harden strix review evidence boundaries
seonghobae Aug 14, 2026
b09e8b8
test: exercise structured strix status binding
seonghobae Aug 14, 2026
0baf2b4
refactor(strix): remove unrelated lock-materializer changes
seonghobae Aug 14, 2026
0bdf042
docs(strix): keep release note scoped to evidence gate
seonghobae Aug 14, 2026
33a6094
fix(strix): preserve materializer executable mode
seonghobae Aug 14, 2026
87c14eb
fix: gate merge automation on aggregate review approval
seonghobae Aug 14, 2026
88aa747
fix: validate structured Strix artifacts before release
seonghobae Aug 14, 2026
c192ad2
fix(strix): bind evidence to unique outer run artifact
seonghobae Aug 14, 2026
a42e1e6
fix(strix): clean up malformed artifact metadata
seonghobae Aug 14, 2026
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
258 changes: 166 additions & 92 deletions .github/workflows/opencode-review-dispatch.yml

Large diffs are not rendered by default.

197 changes: 159 additions & 38 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,13 @@ jobs:
echo "::error::PR head ref did not resolve to expected commit $PR_HEAD_SHA after retries." >&2
exit 1

# pull_request_target evaluates this workflow from the trusted base
# branch. Materializing a PR-head workflow above is data-only self-test
# input; it does not replace the active wrapper for this run.
# Consequently a workflow-changing PR is not cleanly evidenced until a
# default-branch repository_dispatch run executes this wrapper after the
# change is merged.

- name: Self-test Strix required workflow contract
timeout-minutes: 2
working-directory: trusted-strix-source
Expand Down Expand Up @@ -834,53 +841,36 @@ jobs:
export "STRIX_MEMORY_COMPRESSOR_${budget_suffix}=300"
export "STRIX_PROCESS_${budget_suffix}_SECONDS=$process_budget_seconds"
export "STRIX_TOTAL_${budget_suffix}_SECONDS=5700"

# Capture the gate exit code plus its console output. The gate returns
# exit 1 both for genuine blocking vulnerabilities AND for
# LLM-backend-unavailable outcomes (GitHub Models "Too many requests"
# rate limits, OpenAI quota starvation, 413 tokens_limit_reached
# token-cap, connection/warm-up failures) that could not complete a scan. A backend outage is CI
# infrastructure noise, not a security finding, so it must not fail
# the required check and block merges.
printf '%s\n' "${PR_HEAD_SHA:-$GITHUB_SHA}" > "$RUNNER_TEMP/strix_scan_head_sha"

# Capture the gate exit code plus its console output. A non-zero gate
# result means the scan did not produce complete, trusted evidence;
# provider outages are therefore failures, not clean security scans.
# Fallback and retry policy belongs in the trusted gate itself. This
# wrapper must never convert an incomplete scan into success.
# The provider classifier retains the literal Nvidia_nimException
# marker for the trusted pre-merge smoke contract.
strix_run_log="$RUNNER_TEMP/strix_gate_console.log"
export STRIX_GATE_MARKER_PREFIX="CWL_STRIX_GATE_MARKER_${GITHUB_RUN_ID}:"
strix_rc=0
set +e
bash "$TRUSTED_STRIX_GATE" 2>&1 | tee "$strix_run_log"
strix_rc="${PIPESTATUS[0]}"
set -e

if [ "$strix_rc" -eq 0 ]; then
exit 0
fi

# Preserve configuration failures (exit 2) and any unexpected exit
# code as hard failures — only the scan-failure code (1) can be an
# infrastructure/backend-unavailability outcome.
if [ "$strix_rc" -ne 1 ]; then
if [ "$strix_rc" -ne 0 ]; then
echo "::error title=Strix evidence incomplete::The trusted Strix gate did not produce a clean scan result (exit ${strix_rc}); provider failures and missing reports remain fail-closed. See the strix-reports artifact and the run log."
exit "$strix_rc"
fi

# Recognized signals that the LLM backend was unavailable / starved.
backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|emitted provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404'
# Any evidence that a vulnerability was actually reported. Its presence
# forces a hard failure so real findings are NEVER downgraded. Keep the
# severity branch anchored away from identifiers so environment lines
# such as STRIX_FAIL_ON_MIN_SEVERITY do not look like findings.
reported_vulnerability_signal='Vulnerabilities[[:space:]]+[1-9]|(^|[^A-Za-z0-9_])severity[[:space:]]*:'

# Neutral skip only when ALL hold: a backend-unavailability signal is
# present and no vulnerability was reported anywhere. This preserves
# real security gating while keeping uncontrollable provider outages
# from blocking current-head merge progress.
if grep -Eiq "$backend_unavailable_signal" "$strix_run_log" \
&& ! grep -Eiq "$reported_vulnerability_signal" "$strix_run_log"; then
echo "::warning title=Strix backend unavailable::Strix could not complete because its LLM backend was unavailable (rate limit / token cap / connection or warm-up failure) before producing a vulnerability report. Treating as a neutral skip so an infrastructure outage does not block merges; genuine findings still fail the check. See the strix-reports artifact and the run log."
exit 0
# CWE-754: a zero exit is not complete evidence if the gate itself
# printed fail-closed, incomplete-evidence, or neutral-skip text (IEEE,
# 2008).
if grep -F -- "$STRIX_GATE_MARKER_PREFIX" "$strix_run_log" |
grep -Eiq 'failing closed|fail-closed|fail closed|incomplete evidence|incomplete-evidence|neutral[[:space:]]+skip'; then
echo "::error title=Strix evidence incomplete::The trusted Strix gate printed a fail-closed, incomplete-evidence, or neutral-skip marker but exited 0; refusing to convert that into a successful required check. See the strix-reports artifact and the run log."
exit 1
fi

echo "Strix reported security findings or failed for a non-backend reason; failing the required check (gate exit ${strix_rc})." >&2
exit "$strix_rc"

- name: Collect Strix reports for artifact upload
if: ${{ always() && steps.gate.outputs.enabled == 'true' }}
env:
Expand All @@ -899,6 +889,10 @@ jobs:
cp "$RUNNER_TEMP/strix_gate_console.log" "$GITHUB_WORKSPACE/strix_runs/gate-console.log"
copied_reports=1
fi
if [ -s "$RUNNER_TEMP/strix_scan_head_sha" ]; then
cp "$RUNNER_TEMP/strix_scan_head_sha" "$GITHUB_WORKSPACE/strix_runs/scan-head-sha.txt"
copied_reports=1
fi
if [ -n "$(find "$GITHUB_WORKSPACE/strix_runs" -mindepth 1 -print -quit)" ]; then
copied_reports=1
fi
Expand All @@ -911,6 +905,133 @@ jobs:
} > "$GITHUB_WORKSPACE/strix_runs/scan-summary.txt"
fi

- name: Redact Strix evidence before artifact publication
if: ${{ always() && steps.gate.outputs.enabled == 'true' }}
run: |
set -euo pipefail
redactor="$TRUSTED_STRIX_SOURCE/scripts/ci/redact_sensitive_log.py"
if [ ! -f "$redactor" ]; then
echo "::error::Trusted Strix evidence redactor is missing."
exit 1
fi
while IFS= read -r -d '' evidence_file; do
redacted_file="${evidence_file}.redacted"
python3 "$redactor" <"$evidence_file" >"$redacted_file"
mv -- "$redacted_file" "$evidence_file"
done < <(find "$GITHUB_WORKSPACE/strix_runs" -type f -print0)

- name: Validate Strix report provenance
if: ${{ always() && steps.gate.outputs.enabled == 'true' }}
env:
TARGET_REPOSITORY: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository || github.repository }}
PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.client_payload.pr_head_sha }}
run: |
set -euo pipefail
evidence_head_sha="${PR_HEAD_SHA:-$GITHUB_SHA}"
if ! [[ "$evidence_head_sha" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::Strix evidence head SHA must be a 40-character git SHA."
exit 1
fi

scan_stage_head_sha=""
if [ -s "$GITHUB_WORKSPACE/strix_runs/scan-head-sha.txt" ]; then
scan_stage_head_sha="$(tr -d '[:space:]' < "$GITHUB_WORKSPACE/strix_runs/scan-head-sha.txt")"
fi
if ! [[ "$scan_stage_head_sha" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::Strix evidence must contain the exact head SHA recorded at scan start."
exit 1
fi
if [ "${scan_stage_head_sha,,}" != "${evidence_head_sha,,}" ]; then
echo "::error::Strix scan-start head SHA does not match the evidence head."
exit 1
fi

successful_run_file=""
report_file=""
while IFS= read -r -d '' candidate_run; do
if ! jq -e '(.status == "completed") and (.scan_results.scan_completed == true) and (.scan_results.success == true)' "$candidate_run" >/dev/null 2>&1; then
continue
fi
candidate_metadata_count="$(jq -r '
[
.head_sha,
.commit_sha,
((.scan_results // {}).head_sha),
((.scan_results // {}).commit_sha)
]
| map(select(. != null))
| length
' "$candidate_run")"
if [ "$candidate_metadata_count" -eq 0 ]; then
continue
fi
candidate_metadata_matches=1
candidate_head_sha=""
while IFS= read -r candidate_metadata_value; do
if [ -z "$candidate_head_sha" ]; then
candidate_head_sha="$candidate_metadata_value"
fi
if ! [[ "$candidate_metadata_value" =~ ^[0-9a-fA-F]{40}$ ]] ||
[ "${candidate_metadata_value,,}" != "${evidence_head_sha,,}" ]; then
candidate_metadata_matches=0
break
fi
done < <(jq -r '
[
.head_sha,
.commit_sha,
((.scan_results // {}).head_sha),
((.scan_results // {}).commit_sha)
]
| map(select(. != null))
| .[]
| if type == "string" then . else "__invalid_metadata_type__" end
' "$candidate_run")
if [ "$candidate_metadata_matches" -ne 1 ]; then
continue
fi
candidate_report="$(dirname -- "$candidate_run")/penetration_test_report.md"
if [ -s "$candidate_report" ]; then
successful_run_file="$candidate_run"
report_file="$candidate_report"
break
fi
done < <(find "$GITHUB_WORKSPACE/strix_runs" -type f -name run.json -print0)

if [ -z "$successful_run_file" ] || [ -z "$report_file" ]; then
echo "::error::Strix evidence must contain a completed successful run.json and a non-empty penetration_test_report.md."
exit 1
fi

gate_console="$GITHUB_WORKSPACE/strix_runs/gate-console.log"
marker_prefix="CWL_STRIX_GATE_MARKER_${GITHUB_RUN_ID}:"
if [ -f "$gate_console" ] &&
grep -F -- "$marker_prefix" "$gate_console" |
grep -Eiq 'failing closed|fail-closed|fail closed|incomplete evidence|incomplete-evidence|neutral[[:space:]]+skip'; then
echo "::error::Strix evidence contains a fail-closed/provider-infrastructure marker; it cannot be published as a successful scan."
exit 1
fi

if ! [[ "${GITHUB_RUN_ID:-}" =~ ^[0-9]+$ ]]; then
echo "::error::GitHub Actions run ID is missing or malformed."
exit 1
fi
# The provider's run.json may contain an internal run identifier.
# Only the outer GitHub Actions run ID can bind the uploaded artifact
# to the status URL consumed by the protected review gate.
run_id="$GITHUB_RUN_ID"
report_sha256="$(sha256sum "$report_file" | awk '{print $1}')"
jq -n \
--arg repository "$TARGET_REPOSITORY" \
--arg artifact_name "strix-reports" \
--arg head_sha "$evidence_head_sha" \
--arg run_id "$run_id" \
--arg run_json "${successful_run_file#"$GITHUB_WORKSPACE/strix_runs/"}" \
--arg report "${report_file#"$GITHUB_WORKSPACE/strix_runs/"}" \
--arg report_sha256 "$report_sha256" \
'{repository:$repository, artifact_name:$artifact_name, head_sha:$head_sha, run_id:$run_id, run_json:$run_json, report:$report, report_sha256:$report_sha256, scan_completed:true}' \
> "$GITHUB_WORKSPACE/strix_runs/evidence-binding.json"

- name: Upload Strix reports artifact
if: ${{ always() && steps.gate.outputs.enabled == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down Expand Up @@ -940,7 +1061,7 @@ jobs:
case "$STRIX_RESULT" in
success)
state="success"
description="Default-branch repository_dispatch Strix evidence passed"
description="Default-branch repository_dispatch Strix structured evidence binding passed"
;;
failure|cancelled|skipped)
state="failure"
Expand Down Expand Up @@ -1090,7 +1211,7 @@ jobs:
case "$STRIX_RESULT" in
success)
state="success"
description="Default-branch repository_dispatch Strix evidence passed"
description="Default-branch repository_dispatch Strix structured evidence binding passed"
;;
failure|cancelled|skipped)
state="failure"
Expand Down
100 changes: 100 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Architecture — ContextualWisdomLab `.github`

This repository is the organization control plane. It is not naruon and it
does not own product data. Sibling products remain standalone modules; this
repo publishes org profile assets, reusable required workflows, and the
review/merge schedulers those products consume.

## System context

```mermaid
flowchart LR
Buyer["Commercial buyer / reviewer"]
Agents["Agents on AGENTS.md"]
Project["GitHub Project #1"]
Hub["This repo: org .github"]
Products["Owned products<br/>naruon · orchestrator · engines"]
Runner["Required workflows in each repo context"]

Buyer --> Hub
Agents --> Project
Agents --> Hub
Project --> Hub
Hub --> Runner
Runner --> Products
Products -->|"standalone or as module"| Buyer
```

## Strix incomplete-evidence gate

```mermaid
flowchart TD
Gate["Trusted strix_quick_gate.sh"]
Rc{"exit 0 and no run-scoped gate marker?"}
Pass["Required check succeeds"]
Fail["Fail closed: incomplete evidence"]

Gate --> Rc
Rc -->|"yes"| Pass
Rc -->|"no"| Fail
```

CWE-754: a zero exit plus a run-scoped `CWL_STRIX_GATE_MARKER_<run-id>:`
line containing `failing closed`, incomplete evidence, or neutral skip is
unusual and must not become a green security check. The wrapper ignores the
same words in untrusted scanner/model/source text.

`pull_request_target` evaluates required workflow YAML from the trusted
base/default branch. A PR-head workflow may be materialized for data-only
self-test, but it is not the active wrapper. Workflow-changing PRs therefore
need a post-merge default-branch `repository_dispatch` Strix run with an
`evidence-binding.json` binding the exact PR-head SHA, scan-start SHA,
metadata-bearing `run.json`, workflow run ID, artifact, report path, and report
SHA-256. A metadata-less `run.json` is excluded rather than substituted with
the scan-start SHA; the generic `strix` success context is insufficient.

## Control-plane data flow

```mermaid
sequenceDiagram
participant PR as Pull request
participant RW as Required workflows
participant OC as OpenCode reviewer
participant SV as sandboxed_verify / web E2E
participant MS as Merge scheduler

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
OC-->>PR: APPROVE or request changes
MS->>PR: dispatch default-branch Strix for exact PR head
PR-->>MS: structured same-head evidence binding
MS->>PR: merge only on protected review + current checks + clean binding + resolved threads
```

## Trust boundaries

- Required review workflows execute **base-branch** scripts.
- Reviewer agents stay `edit: deny`.
- Evidence artifacts apply the allowlisted minimum-disclosure scrubber to
credentials, email addresses, phone numbers, IPv4 addresses, and absolute
runner paths before upload. Repository-relative source locations remain so
findings stay actionable; private reasons are not propagated to reviewer
context. Artifact access is limited to the repository's existing Actions
artifact readers, for the security-review purpose, with the existing
five-day retention and GitHub audit trail.
- LLM and scheduled agents bind `NVIDIA_NIM_API_KEY`. They never use
`COPILOT_GITHUB_TOKEN`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- Rust remains the psychometric arithmetic owner.

## Quality gates

`scripts/ci/` ships with 100% statement/branch coverage and 100%
docstrings.

## Related durable documents

- [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md)
- [`docs/doctoring/strix-provider-evidence-fail-closed.md`](docs/doctoring/strix-provider-evidence-fail-closed.md)
- [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md)
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Refused a completed successful Strix `run.json` that carries no `head_sha` or `commit_sha` metadata. Provenance no longer substitutes the scan-start SHA for that missing binding, so an unbound report cannot publish as current-head evidence (CWE-754).
- Kept the required Strix check fail-closed when the trusted gate prints fail-closed or incomplete-evidence text even if the process exits 0, so a provider outage cannot become a green security check (CWE-754). The wrapper now matches hyphenated and spaced spellings of those markers, not only `failing closed` and `incomplete evidence`. Scan-start `scan-head-sha.txt` must also match the evidence head SHA before provenance can publish.

- 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.
- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.
- Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped.
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ Details: `README.md` and `PR_GOVERNANCE_AUDIT.md`.
dependency sets (see below).
- `fuzz/` + `.clusterfuzzlite/` — Atheris fuzz targets for the review-output normalizer and the
ClusterFuzzLite discovery marker.
- `ARCHITECTURE.md` — control-plane mermaid (system context, Strix
fail-closed gate, review sequence, trust boundaries). Reconstruct from
the repo, not private agent memory.
- `docs/` — master context, Project protocol, `org-required-workflow-rollout.md`,
`scorecard-governance.md`, SBOM inventory.
- `.jules/` — recorded performance (`bolt.md`) and security (`sentinel.md`) learnings from past work
Expand Down
Loading
Loading