Supply-chain gates: SBOM, SAST, dependency review¶
Review-map item A-30. One script, tools/checks/supply_chain.sh, is the CI job
supply-chain (.github/workflows/ci.yml, SBOM) and runs the same way locally:
tools/checks/supply_chain.sh # SBOM (output in ./sbom-out, git-ignored)
tools/ci/ci_local.sh HEAD --jobs supply-chain # the real workflow job under act
Tool versions are pinned in the script (CYCLONEDX_BOM_VERSION); bump them deliberately, in their own change.
SBOM¶
uv export --frozen of uv.lock (runtime + dev groups) piped through
cyclonedx-py requirements gives a CycloneDX 1.5 JSON document
(sandloom-backend.cdx.json), uploaded by CI as the backend-sbom artifact
(90 days). Not covered yet: the admin/ npm tree and the production image
(syft) — both need a tool outside the pinned set; add them to the script
when an audit or release needs them.
SAST¶
Covered by ruff rule family S (flake8-bandit) in pyproject.toml,
with its own baselined suppression ratchet, enforced by backend-static. There
is deliberately no separate bandit run or baseline here (two overlapping
baselines would drift). A bandit scan of the tree on 2026-10-24 (124 findings)
found nothing exploitable: assert/try-except-pass/jitter-random/token-constant
false positives, subprocess calls with argv lists (backup.py), and 26 SQL
string-building sites (mostly migrations/extract.py) whose identifiers come
from the registry and whose values are bound — worth one audit that none can
originate from request input. Rules ruff S lacks would be a semgrep addition
with a pinned ruleset; not added yet.
Dependency review on PRs¶
.github/workflows/dependency-review.yml runs actions/dependency-review-action
on PRs touching lockfiles/manifests: fails on a newly introduced dependency
with a high+ advisory or an AGPL/GPL-3.0 licence. It needs the GitHub
dependency graph, so it is not reproducible in tools/ci/ci_local.sh.
Existing dependencies are covered by pip-audit in backend-static.
Snyk review and reproducibility¶
The 2026-10-31 audit used Snyk CLI 1.1308.3, authenticated to the
maintainer's organization. The review ledger is
tools/checks/snyk-audit-2026-10-31.json.
It records all 384 Code results against the original commit and all five
unique dependency advisories, with a disposition and evidence for each.
It is review evidence, not a Snyk ignore policy. Future findings still need review.
The dependency scope includes the admin's complete development tree and
its production-only tree, every group/extra of the framework lock, and the
benchmark Cachegrind requirements. Python scanning used a clean CPython 3.12
macOS environment: evaluate lockfile markers for that environment before
installing and scanning (136 framework and 94 benchmark dependency records).
The two inapplicable records, Windows colorama and Python <3.11.3
async-timeout, are listed in the ledger; no unresolved dependency was skipped.
This is not a claim that every platform-specific resolution was tested.
snyk code test .
snyk test --file=admin/package.json --dev
snyk test --file=admin/package.json
# Export uv.lock with all groups/extras, excluding the five workspace packages.
# Evaluate markers, then install the resulting requirements in a clean environment.
snyk test --file=/path/to/evaluated-requirements.txt --package-manager=pip \
--command=/path/to/clean-env/bin/python
snyk test --file=benchmarks/ladder/cg/requirements.txt --package-manager=pip \
--command=/path/to/benchmark-env/bin/python
Authenticate with snyk auth; never put the token in a command, report or PR.
Keep raw scan artifacts outside the public source tree. Running a local scan
does not install a CI publisher or create additional monitored projects.
Remaining dependency alert¶
Snyk's picomatch advisory
has no upstream fixed version at the audit date. The admin pins the reviewed
parser and rejects the vulnerable single-branch repeated-wildcard extglob
before creating a regular expression. npm ci applies the patch only when
the version and source checksums match, then runs timeout and real-consumer
regressions. See
admin/patches/README.md.
One admin development-dependency alert remains visible: scanning does not inspect
the installed source patch. It is mitigated locally, not ignored or represented
as an upstream fix. Do not install these build dependencies with
--ignore-scripts. Replace the patch with an upstream fix when one is available,
after running the compatibility suite and admin builds. The admin production
dependency tree and the two scanned Python environments have no Snyk advisories.
Runtime container scope¶
The audit also built and scanned both final runtime images on linux/arm64, including OS packages and application dependencies. The admin's pinned Alpine base contained 27 unique advisories. Exact security backports for brotli, curl, musl and zlib remove all 27; a repeat scan of 70 dependencies reports zero. The image still runs as UID 101 and passes the nonroot/read-only proxy smokes in both deployment topologies.
The original Python runtime had 68 Debian source-package advisories.
The follow-up removes runtime APT and mount/account administration packages,
strips SUID/SGID bits, and restricts application containers to a read-only root,
zero capabilities and no privilege gain. Repeat scanning reports 67 advisories:
APT disappears, while login.defs retains shadow source-package findings even
after the affected account tools are removed. Package metadata remains intact.
Every original CVE has a separate applicability/remediation record in
debian-runtime-review-2026-10-31.json.
The review covers all 18 source-package groups against Debian's tracker,
including the original 64 low and one medium results. No fixed trixie version
is available for these baseline CVEs; live-library and conditional findings
remain open. An absent systemd daemon is distinguished from an installed
systemd library; optional SQLite and Perl components and architecture-specific
paths are checked separately. Severity alone never authorizes an exception.
The first Snyk monitor run on 2026-11-02 reported 80 advisories: the 67 above
and 13 new ones, 10 in SQLite (three high) and 3 in libstdc++. Debian has no fix for
any of them, even in unstable. The review records each one, listed under
amendments, as open_no_identified_framework_path. The SQLite advisories need
attacker-supplied SQL or database files, FTS, R-Tree or session input.
At runtime the framework opens only an in-memory SQLite self-test with fixed
statements. Keep a sqlite DATABASE_URL out of production.
The three high conclusions are:
- attr: the affected
getfattr/setfattrutilities are absent. The executable presence predicate must continue to pass before using that conclusion. - ACL: real local privileged-path risk, mitigated by the runtime boundary; the installed library is not declared fixed or universally unreachable.
- zlib: the exact installed binary matches the authenticated Debian package. The authenticated source lacks the newer nonblocking writer mechanism, and errors return before the affected buffer movement. This supports investigating a version-classification error, but the status remains under investigation while Debian marks stable vulnerable.
Reproduce evidence from the repository root after building the production image:
docker build -f deploy/docker/Dockerfile --target production -t sandloom-runtime-review .
docker run --rm -i --network none --read-only --cap-drop ALL \
--security-opt no-new-privileges:true \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=128m,uid=10001,gid=10001,mode=0700 \
--tmpfs /app/data:rw,noexec,nosuid,nodev,size=8m,uid=10001,gid=10001,mode=0700 \
--entrypoint python sandloom-runtime-review - --enforce \
< tools/checks/runtime_inventory.py > /tmp/runtime-inventory.json
snyk container test sandloom-runtime-review --file=deploy/docker/Dockerfile \
--json-file-output=/tmp/runtime-scan.json
# A scan with known findings exits 1 but still writes the report; inspect it.
python3 tools/checks/zlib_source_evidence.py --inventory /tmp/runtime-inventory.json \
> /tmp/zlib-evidence.json
python3 tools/checks/runtime_review.py --image sandloom-runtime-review \
--inventory /tmp/runtime-inventory.json --scan /tmp/runtime-scan.json \
--zlib-evidence /tmp/zlib-evidence.json > /tmp/runtime-review.json
If boundary enforcement fails, the inventory command exits 1 and still emits
the collected JSON with boundary_checks: "fail" and boundary_failure.
Docker CI uploads runtime-security-inventory even when that probe fails.
The source evidence command uses a disposable reference container and signed Debian indexes; it verifies source/package checksums and exact library bytes. It is separate from normal CI because it needs live repository access. The evidence checker rejects new CVEs, detects changed components, and requires new source review when zlib bytes or source hashes change. It creates no scanner ignore or automatic risk acceptance. Re-scan after vendor updates; do not mix Debian unstable packages into the stable runtime.
snyk iac test deploy found no supported IaC files. Docker Compose is not
covered by that scan; this result is not a passing infrastructure audit.
Code findings and trust boundaries¶
Confirmed defects have regression tests for PostgreSQL names containing quotes, hostile archive links/special files, private profiler output, and executable documentation URL schemes. Viewer labels stay text even when the inspection model contains HTML. SHA-1 uses retained for Git object IDs and local resource names are explicitly non-security uses.
The repeat Code scan reports 370 results (eight high, 72 medium, 290 low),
with zero ignores and no new rule/file pairs. The high results are the eight
explicit local build/benchmark secret placeholders. Every original finding
is accounted for in the ledger; SQL identifier composition and the data extraction
filter still produce scanner warnings despite the attack regressions passing.
A Code scan of main on 2026-11-02 reports 378 results (eight high, 78 medium,
292 low). The ledger's amendments entry records the eight new ones:
- a throwaway password in the admin package smoke test;
- an OpenCode SDK HTTP call that Snyk misreads as SQL;
- two more operator-selected paths in a docs test.
None needs a code change or an ignore.
The other findings include synthetic test credentials, negative security tests, route authentication labels, and local tools intentionally reading paths or executing project commands chosen by their operator. Benchmark/DX and local CI tools execute project code and are not sandboxes for hostile repositories. Review those trust boundaries again if a tool becomes a remotely callable service. No production security check was relaxed.