Skip to content
Open
54 changes: 4 additions & 50 deletions .github/workflows/python-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,53 +226,7 @@ jobs:
- name: Run pip-audit (hard gate on any known vulnerability)
run: |
set -euo pipefail
status=0

# Audit every discovered requirements file.
while IFS= read -r req; do
# A matching requirements-<tool>-ci-overrides.txt (a `uv pip compile --override`
# input, e.g. requirements-strix-ci-overrides.txt) means the *-hashes.txt this
# override applies to pins a version whose declared metadata range intentionally
# conflicts with another pin in the same file (verified safe at override time, not a
# resolution mistake). pip's own dependency resolver -- which pip-audit's default
# `-r` mode still calls even for fully hash-pinned files -- fails on that same
# declared-range conflict regardless of --require-hashes, and plain --no-deps does
# not suppress it (confirmed: --no-deps only skips fetching undeclared transitive
# packages, pip's resolver still cross-checks the packages that *are* listed
# together). --disable-pip bypasses pip's resolver entirely and audits the exact
# pins directly, but it requires every requirement to be an exact version (raises on
# any bare range) -- true for the compiled *-hashes.txt, not necessarily true for the
# hand-maintained raw input (e.g. requirements-strix-ci.txt intentionally leaves
# protobuf as a range). So: hashed output files with an override get
# --disable-pip --no-deps; their raw, non-hash input counterpart is skipped here
# (it is never itself a `pip install --require-hashes` target -- only its compiled
# *-hashes.txt is installed -- and that compiled file is the one audited with full
# transitive coverage).
base="${req%.txt}"
unhashed_base="${base%-hashes}"
if [ "$base" != "$unhashed_base" ] && [ -f "${unhashed_base}-overrides.txt" ]; then
echo "::group::pip-audit -r ${req} (--disable-pip --no-deps: overridden lock)"
pip-audit --strict --desc=on --no-deps --disable-pip -r "${req}" || status=1
echo "::endgroup::"
elif [ "$base" = "$unhashed_base" ] && [ -f "${unhashed_base}-overrides.txt" ]; then
echo "::notice::Skipping pip-audit for ${req}: it is the raw input to an overridden lock (${unhashed_base}-hashes.txt), never itself a pip install --require-hashes target, and its compiled hashes file is audited separately with full resolution."
else
echo "::group::pip-audit -r ${req}"
pip-audit --strict --desc=on -r "${req}" || status=1
echo "::endgroup::"
fi
done < <(find . -type f -name 'requirements*.txt' -not -path './.git/*')

# Audit the project itself when a PEP 621 / lock manifest exists.
if find . -maxdepth 2 -type f \
\( -name 'pyproject.toml' -o -name 'pylock.*.toml' \) \
-not -path './.git/*' -print -quit | grep -q .; then
echo "::group::pip-audit . (project manifest)"
pip-audit --strict --desc=on . || status=1
echo "::endgroup::"
fi

if [ "${status}" != "0" ]; then
echo "::error::pip-audit reported known-vulnerable Python dependencies."
exit 1
fi
# Hashed complete locks are audited with --disable-pip so pip cannot
# re-apply a stale Requires-Dist bound and label ResolutionImpossible
# as a known vulnerability (ContextualWisdomLab/.github#961).
python3 scripts/ci/pip_audit_requirements.py
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ commit and exposed without running build hooks; a lone `--require-hashes`
directive is not trust evidence. See
[`docs/doctoring/opencode-exact-vcs-dependency-evidence.md`](docs/doctoring/opencode-exact-vcs-dependency-evidence.md).
Conflict-scope roots fail closed when the immediate parent directory is a symbolic link.
Hashed-lock pip-audit uses `--disable-pip` only when every package line is an exact SHA-256 pin; `--index-url` is configuration; directory-symlink hops fail closed. See [`docs/doctoring/strix-hashed-lock-no-deps.md`](docs/doctoring/strix-hashed-lock-no-deps.md).

All 18 product hourly review-repair callers (OriginWeave at minute 10, nonnest2 at minute 16, and 16 others) are one file, [`.github/workflows/hourly-review-repair.yml`](.github/workflows/hourly-review-repair.yml), a `github.event.schedule` lookup table rather than 18 near-copy-pasted files. See [`docs/doctoring/hourly-review-repair-single-file-consolidation.md`](docs/doctoring/hourly-review-repair-single-file-consolidation.md); the per-repository doctoring records (e.g. [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md), [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md)) remain as historical background per repository.
Organization edge runtimes use Cloudflare Pingora. Do not add or preserve active Nginx containers, packages, commands, service/config files, or Kubernetes Nginx ingress annotations/classes. Read [`docs/policies/PINGORA_EDGE_POLICY.md`](docs/policies/PINGORA_EDGE_POLICY.md) and ADR-0019 before changing HTTP edge, static-serving, ingress, TLS, or proxy deployment behavior.

Expand Down
9 changes: 9 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ protected `master`) are three of the 18 resolved targets; every target maps
only established scheduler credentials. The reusable engine stays
product-neutral.

## Hashed-lock pip-audit

`scripts/ci/pip_audit_requirements.py` audits complete hashed locks with
`--disable-pip`. Resolver flags such as `--index-url` are not package
lines. `-r` includes and directory-symlink parents fail closed so pip
cannot relabel `ResolutionImpossible` as a known vulnerability.

## Hourly contextual-orchestrator repair gate

```mermaid
Expand Down Expand Up @@ -262,6 +269,8 @@ resolver conflict.
— import-only exact source dependencies for networkless coverage.
- [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md)
— product-specific psychometric repair heartbeat and scientific gates.


Comment on lines +272 to +273

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Two blank lines split the Related-documents list in ARCHITECTURE.md

The diff inserts two blank lines (ARCHITECTURE.md) between two bullet items of the '## Related durable documents' list. In Markdown this terminates the first list and starts a new one, producing extra vertical spacing / a broken list rendering. Cosmetic only, but likely unintended.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md)
— current increment's attestation decision and APA 7th citations.
- [`docs/doctoring/sandboxed-web-readiness-loopback-boundary.md`](docs/doctoring/sandboxed-web-readiness-loopback-boundary.md)
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1265,10 +1265,11 @@ 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.
- Audit complete hashed Python locks with `--disable-pip` so `--index-url` stays configuration, `-r` includes do not earn resolver bypass, and directory-symlink parents fail closed before pip-audit can label `ResolutionImpossible` as a known vulnerability.
- Bound the central Semgrep job to one `SEMGREP_IMAGE` digest for log evidence, manifest inspection, and `docker run`, so a buyer reconstructing the scan can prove the logged scanner is the scanner that ran.
- Published substantive OpenCode LLM probes when they already carried an independent proof and exact source-line digest but omitted a duplicated `path:line` citation, so NVIDIA NIM / OpenCode review evidence is no longer discarded as `NO_CONCLUSION`.
- 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.
Comment thread
seonghobae marked this conversation as resolved.
- 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.
- 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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ repeatable compile command.
- **Product hourly callers** stay thin. Do not hard-code OriginWeave, aFIPC, naruon, or Keyverse
into `pr-review-fix-scheduler.yml`. The model credential remains `NVIDIA_NIM_API_KEY`
on the worker, never `COPILOT_GITHUB_TOKEN`.
- **pip-audit** of hashed locks goes through `scripts/ci/pip_audit_requirements.py`.
Do not call `pip-audit -r` on a complete lock (that re-resolves and can false-fail).
- **Central review routes through the vendored contextual-orchestrator gateway.**
`pr-review-autofix.yml` provisions `scripts/ci/contextual_orchestrator_review_sidecar.sh`
(the five provider secrets flow into its KV; the writer runs
Expand Down
95 changes: 95 additions & 0 deletions docs/doctoring/strix-hashed-lock-no-deps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Strix hashed-lock install and pip-audit without pip re-resolution

검토 기준일: **2026-08-16**

## Incident

The required Strix workflow is `pull_request_target`: GitHub runs the **base
branch** copy of `.github/workflows/strix.yml` against the pull-request head
tree. ContextualWisdomLab/.github#961 therefore compiled a complete lock of
`strix-agent==1.5.3` plus `cryptography==50.0.0` (override for CVE-2026-39892
and CVE-2026-69247) and added `--no-deps` to the PR copy of the installer, but
the live required job still executed main's installer:

```text
pip install --require-hashes -r requirements-strix-ci-hashes.txt
```

pip re-applied `strix-agent 1.5.3 depends on cryptography<49 and >=48.0.1`
and exited `ResolutionImpossible`. The same resolver path is what
`python-security.yml` used for every `requirements*.txt` file. pip-audit then
printed `::error::pip-audit reported known-vulnerable Python dependencies`
even though no advisory was returned. A buyer watching the required security
dashboard saw two red X marks on an honest lock.

## Decision

1. Land `--require-hashes --no-deps` on **main's** Strix installer first, with
no lock change. The current main lock (`strix-agent==1.0.4` +
`cryptography==50.0.0`) is already a complete hashed set, so `--no-deps`
does not widen the install. After this lands, a later 1.5.3 lock can
install under the required base-branch workflow.
2. Audit hashed locks with `pip-audit --disable-pip` via
`scripts/ci/pip_audit_requirements.py`. Compile-time `*-overrides.txt`
files and unhashed inputs that already have a `*-hashes.txt` sibling are
not separate install sets.
3. Do not drop `cryptography==50.0.0` to satisfy the stale `<49` metadata
bound. Do not treat `ResolutionImpossible` as a vulnerability.

`python-security.yml` is `pull_request` (not `_target`), so the helper takes
effect on the same head that introduces it. Strix still needs this installer
line on the protected base before #961 can go green.

## Trust boundary

- `--no-deps` is not an unhashed install: every wheel remains hash-pinned.
- `--disable-pip` still queries the advisory database for every pinned name
and version; it only skips pip's metadata resolver.
- NVIDIA NIM / OpenCode review-agent credentials are untouched.
- No operational PII is masked.

## Verification contract

`tests/test_pip_audit_requirements.py` reconstructs the #961 lock shape and
requires `--disable-pip` for that file, a skip for the override/input pair,
and the `--no-deps` installer line on `strix.yml`. A `*-hashes.txt` name
or a lone `--require-hashes` directive without `--hash=` is not treated
as a complete lock. A mixed file with one hashed line beside unhashed
packages, a filename-only wheel path, a `-r`/`-c`/`-e` include, or a pip
option carrying hash-shaped text also stays on the resolver path. A
complete lock that only adds resolver configuration such as `--index-url`
still receives `--disable-pip`. An unhashed compile input is skipped
only when its regular, non-symlink sibling is itself a valid complete lock.
Invalid UTF-8 and symlink/special-file requirement inputs fail before any
audit command. Discovery does not descend directory symbolic links, and a
presented path whose intermediate parent is a symlink or whose resolved
target leaves the audit root fails closed (CWE-22 / CWE-59).
Repository-controlled filenames are JSON-escaped before GitHub Actions
group titles. Discovery skips `.venv` trees.

## References (APA 7th)

GitHub. (2026). *Cryptography vulnerable to buffer overflow if
non-contiguous buffers were passed to APIs (CVE-2026-39892,
GHSA-p423-j2cm-9vmq)*. GitHub Advisory Database.
https://github.com/advisories/GHSA-p423-j2cm-9vmq

GitHub. (2026). *PKCS#7 decryption timing oracle in pyca/cryptography
(CVE-2026-69247, GHSA-g6cj-pr64-35w5)*. GitHub Advisory Database.
https://github.com/advisories/GHSA-g6cj-pr64-35w5

National Institute of Standards and Technology. (2026).
*CVE-2026-69247*. National Vulnerability Database.
https://nvd.nist.gov/vuln/detail/CVE-2026-69247

pypa. (2025). *pip-audit: ``--disable-pip`` (hashed requirements / ``--no-deps``
only)*. https://github.com/pypa/pip-audit

MITRE. (2026). *CWE-22: Improper limitation of a pathname to a restricted
directory ('Path Traversal')*. https://cwe.mitre.org/data/definitions/22.html

MITRE. (2026). *CWE-59: Improper link resolution before file access
('Link Following')*. https://cwe.mitre.org/data/definitions/59.html

Python Packaging Authority. (n.d.). *Hash-checking mode*. pip documentation.
https://pip.pypa.io/en/stable/topics/secure-installs/#hash-checking-mode
Loading
Loading