Skip to content
Draft
54 changes: 4 additions & 50 deletions .github/workflows/python-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,53 +232,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/*' | head -1 | 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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ 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).
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).
9 changes: 8 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ only established scheduler credentials, and grants job-scoped
only established scheduler credentials, and grants job-scoped
`id-token: write`. 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 NVIDIA NIM repair gate

```mermaid
Expand Down Expand Up @@ -125,4 +132,4 @@ trusted `uv` exporter is downloaded from the literal GitHub Releases URL for
- [`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)
— product-specific psychometric repair heartbeat and scientific gates.
— product-specific psychometric repair heartbeat and scientific gates.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ 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.
- 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.
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.
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ repeatable compile command.
- **Product hourly callers** stay thin. Do not hard-code OriginWeave, 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).
- **`pull_request_target` trust boundary.** The required review workflows run the *base branch's*
trusted scripts. A PR that edits the trusted review workflows can fail its own checks until the
base branch catches up; a same-head manual `workflow_dispatch` Strix run may supply review evidence
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