Skip to content
Closed
8 changes: 4 additions & 4 deletions .github/workflows/codeql-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}

- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:${{ matrix.language }}"
upload: false
Expand Down Expand Up @@ -197,13 +197,13 @@ jobs:
ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }}

- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:${{ matrix.language }}-merge"
upload: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ jobs:
if: always() && hashFiles('bandit-results.sarif') != ''
# The explicit gate below still fails on every Medium+ Bandit result.
continue-on-error: true
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: bandit-results.sarif
category: bandit
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sast-semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
- name: Upload Semgrep SARIF to code scanning
if: always() && hashFiles('semgrep-results.sarif') != ''
continue-on-error: true
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: semgrep-results.sarif
category: semgrep
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/scheduled-security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ jobs:
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Perform CodeQL Analysis
continue-on-error: true
uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:${{ matrix.language }}-scheduled"

Expand Down Expand Up @@ -131,7 +131,7 @@ jobs:
- name: Upload Trivy SARIF to code scanning
if: always() && hashFiles('trivy-results.sarif') != ''
continue-on-error: true
uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: trivy-results.sarif
category: trivy-fs-scheduled
2 changes: 1 addition & 1 deletion .github/workflows/scorecard-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,6 @@ jobs:
# Scorecard posture is preserved in its SARIF-generation log; an
# installation upload quota outage must not fail the default branch.
continue-on-error: true
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: results.sarif
2 changes: 1 addition & 1 deletion .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
- name: Upload gitleaks SARIF to code scanning
if: always() && hashFiles('gitleaks-results.upload.sarif') != ''
continue-on-error: true
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: gitleaks-results.upload.sarif
category: gitleaks
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ jobs:
# The reporter above is the vulnerability gate. Preserve an upload
# quota failure in this step's log without reclassifying it as a CVE.
continue-on-error: true
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: results.sarif
# results.sarif is produced after checkout of the pull request head.
Expand Down Expand Up @@ -383,7 +383,7 @@ jobs:
if: always() && hashFiles('trivy-results.sarif') != ''
# The parser above fails on every fixable Medium+ finding independently.
continue-on-error: true
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: trivy-results.sarif
category: trivy-fs
Expand Down Expand Up @@ -457,7 +457,7 @@ jobs:
id: upload_scorecard_sarif
# Scorecard is soft repository-posture evidence; upload quota is external.
continue-on-error: true
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: results.sarif
category: scorecard
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

<!-- CWL-ENTRY -->
> **Agents: read the master context FIRST.** Before any work, read [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission · naruon-as-platform + inter-component UML · cross-cutting disciplines · conventions · roadmap · current state), the live **GitHub Project #1** <https://github.com/orgs/ContextualWisdomLab/projects/1> (work/roadmap source of truth), the full spec **ContextualWisdomLab/naruon#974**, and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not any private agent memory — is the source of truth.

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/codeql-action-single-version.md`](docs/doctoring/codeql-action-single-version.md).
80 changes: 80 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 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
```

## CodeQL single-SHA gate

```mermaid
flowchart TD
Dep["Dependabot upload-sarif bump"]
Pin{"init analyze upload-sarif same SHA?"}
Use["Every workflow uses 4.37.6"]
Split["Fail contract: mixed trusted action"]

Dep --> Pin
Pin -->|"yes"| Use
Pin -->|"no"| Split
```

CWE-829: a second SHA is a second control sphere.

## 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: merge only on current-head approval + green checks
```

## Trust boundaries

- Required review workflows execute **base-branch** scripts.
- Reviewer agents stay `edit: deny`.
- Logs redact credential shapes. They do not mask operational PII.
- LLM and scheduled agents bind `NVIDIA_NIM_API_KEY`. They never use
`COPILOT_GITHUB_TOKEN`.
- 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/codeql-action-single-version.md`](docs/doctoring/codeql-action-single-version.md)
- [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md)
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- 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.
- Aligned every `github/codeql-action` `init`/`analyze`/`upload-sarif` pin to one immutable SHA (`5595ccaf912efad79be6eef63a5619ff05969be3`, v4.37.6) after the upload-sarif-only Dependabot bump left analyzers on 4.37.0/4.37.5 (CWE-829).
- 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, CodeQL
single-SHA 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
28 changes: 28 additions & 0 deletions docs/doctoring/codeql-action-single-version.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# CodeQL action single-version pin

## Incident and buyer impact

Dependabot opened an `upload-sarif` 4.37.6 bump while `codeql-pr.yml`
still ran `init`/`analyze` at 4.37.0 and `scheduled-security-scan.yml`
ran them at 4.37.5. A green upload does not prove the analyzer executed
the reviewed action.

## Decision

Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include; a lone `--require-hashes` line is not lock evidence.

Pin every `github/codeql-action/{init,analyze,upload-sarif}` use to
`5595ccaf912efad79be6eef63a5619ff05969be3` (v4.37.6). Contract tests
reject per-file and org-wide splits.

CWE-829 forbids including functionality from an untrusted or unreviewed
control sphere (MITRE, 2026). A second SHA is a second control sphere.

## References

MITRE. (2026). *CWE-829: Inclusion of functionality from untrusted
control sphere*. https://cwe.mitre.org/data/definitions/829.html

GitHub. (n.d.). *Using the CodeQL action*. GitHub Docs. Retrieved
August 13, 2026, from
https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/using-the-codeql-action
85 changes: 69 additions & 16 deletions scripts/ci/materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,58 @@ def _is_candidate_lock_name(name: str) -> bool:
)



def _is_candidate_lock_path(path: pathlib.PurePosixPath) -> bool:
"""Return whether one safe tracked path can name a pip requirements lock.

In addition to conventional ``requirements*.txt`` names, repositories often
keep concrete environment closures as direct children such as
``requirements/ci.txt`` or ``service/requirements/package.txt``. Only direct
``.txt`` children of a directory named ``requirements`` gain this path-based
eligibility; content must still pass the independent complete hash-pin
validation before it reaches the trusted image build context.
"""
return _is_candidate_lock_name(path.name) or (
path.suffix == ".txt" and path.parent.name == "requirements"
)


def _is_bounded_requirement_include(line: str) -> bool:
"""Return whether one requirements include names a bounded relative file.

Includes are accepted only as a two-token ``-r``/``--requirement`` form
whose target is itself a candidate lock path written as a normalized
relative POSIX path. Absolute paths, ``.`` or ``..`` components, double
slashes, URLs, option-like targets, shell/Windows path separators,
fragments, queries, extra inline options or hashes, and includes of
non-lock files are rejected before a base-owned file can enter the
trusted build context.
The downstream installer still proves that the candidate is an independently
complete hash closure; this predicate grants syntax eligibility only.
"""
fields = line.split()
if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}:
return False
target = fields[1]
if (
target.startswith(("-", "~"))
or "\\" in target
or ":" in target
or "?" in target
or "#" in target
):
return False
include_path = pathlib.PurePosixPath(target)
return (
bool(include_path.parts)
and target == include_path.as_posix()
and not include_path.is_absolute()
and "." not in include_path.parts
and ".." not in include_path.parts
and _is_candidate_lock_path(include_path)
)


def _requirement_lines(content: bytes) -> list[str]:
"""Return logical requirement lines, joining backslash line-continuations.

Expand All @@ -107,26 +159,27 @@ def _requirement_lines(content: bytes) -> list[str]:


def _is_hash_pinned(content: bytes) -> bool:
"""Return whether content carries hash pins and is safe to preflight.

Discovery is content-based rather than name-based so hash-pinned locks in any
location (a service subdirectory, ``requirements-dev.txt``,
``requirements-test.txt``) can be considered for offline coverage, while an
unpinned or PR-mutable requirements file is still excluded from the networked
build context. Hash syntax cannot prove that a file includes every transitive
dependency, so the trusted image installer separately preflights every
candidate as an independent ``--require-hashes`` closure. An empty file
carries no installable dependency and is not materialized.
"""Return whether content carries only trusted pins or bounded includes.

Discovery is content-based rather than name-based so exact hash-pinned locks
in service subdirectories and role-specific requirements files can be
considered for offline coverage. Candidate syntax is deliberately stricter
than a substring search: each package line must be an exact ``==`` pin with
one or more complete SHA-256 hashes, or a bounded relative requirements
include. A global ``--require-hashes`` directive is not trust evidence by
itself. The downstream installer separately preflights every candidate as an
independent ``pip --require-hashes`` closure, so syntax eligibility never
substitutes for dependency-closure proof.
"""
lines = _requirement_lines(content)
if not lines:
requirement_lines = [line for line in lines if line != "--require-hashes"]
if not requirement_lines:
return False
return any(line == "--require-hashes" for line in lines) or all(
"--hash=" in line or line.startswith(("-r ", "--requirement "))
for line in lines
return all(
_is_fully_hash_pinned_requirement(line)
or _is_bounded_requirement_include(line)
for line in requirement_lines
)


def _is_fully_hash_pinned_requirement(line: str) -> bool:
"""Return whether one uv-export line is an exact package pin with SHA-256 hashes."""
fields = re.split(r"\s+(?=--hash=)", line)
Expand Down
21 changes: 21 additions & 0 deletions tests/test_codeql_pr_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ def test_codeql_action_steps_use_one_version_per_workflow() -> None:
assert len(refs) == 1, f"{filename} mixes CodeQL action refs: {sorted(refs)}"


def test_codeql_workflows_share_one_upload_sarif_sha() -> None:
"""CWE-829: every CodeQL action pin must be the Dependabot-reviewed SHA.

An upload-sarif-only bump that leaves init/analyze on 4.37.0/4.37.5
would analyze or upload with a second unreviewed control sphere.
"""
shas: set[str] = set()
tags: set[str] = set()
pin = re.compile(
r"github/codeql-action/(?:init|analyze|upload-sarif)@"
r"([0-9a-f]{40}) # (v\d+\.\d+\.\d+)"
)
for path in (REPO_ROOT / ".github/workflows").glob("*.yml"):
for sha, tag in pin.findall(path.read_text(encoding="utf-8")):
shas.add(sha)
tags.add(tag)

assert shas == {"5595ccaf912efad79be6eef63a5619ff05969be3"}
assert tags == {"v4.37.6"}


def test_codeql_sarif_gate_logs_and_fails_only_unsuppressed_medium_plus(
tmp_path: Path,
) -> None:
Expand Down
Loading
Loading