Skip to content

chore: bundle third-party licenses for Trivy, Grype, and Syft - #6

Merged
tyler-rich merged 1 commit into
mainfrom
claude/third-party-licenses-iv3buc
Jul 3, 2026
Merged

chore: bundle third-party licenses for Trivy, Grype, and Syft#6
tyler-rich merged 1 commit into
mainfrom
claude/third-party-licenses-iv3buc

Conversation

@tyler-rich

@tyler-rich tyler-rich commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add THIRD_PARTY_LICENSES/ with the Apache-2.0 LICENSE for Trivy, Grype, and Syft, and Trivy's NOTICE (Grype and Syft don't ship one at the pinned versions), pulled from each project's upstream repo at the version currently pinned in docker/Dockerfile (Trivy v0.71.2, Grype v0.115.0, Syft v1.46.0).
  • Copy THIRD_PARTY_LICENSES/ into the final image in docker/Dockerfile so attribution ships with the container.
  • Add a short note in the README's Integrations section pointing to THIRD_PARTY_LICENSES/.

This satisfies CLAUDE.md's Apache-2.0 §4 attribution requirement for these bundled, unmodified binaries.

Test plan

  • CI lint/tests pass
  • docker build -f docker/Dockerfile -t scrye:0.1.0 . succeeds and /THIRD_PARTY_LICENSES exists in the built image

Add THIRD_PARTY_LICENSES/ with the Apache-2.0 LICENSE (and NOTICE, where
upstream ships one) for the pinned trivy/grype/syft versions, copy it into
the final image in the Dockerfile, and point to it from the README's
Integrations section.
@tyler-rich
tyler-rich merged commit 87b7a5c into main Jul 3, 2026
2 checks passed
@tyler-rich
tyler-rich deleted the claude/third-party-licenses-iv3buc branch July 6, 2026 06:43
tyler-rich added a commit that referenced this pull request Jul 9, 2026
chore: bundle third-party licenses for Trivy, Grype, and Syft
tyler-rich added a commit that referenced this pull request Jul 9, 2026
chore: bundle third-party licenses for Trivy, Grype, and Syft
tyler-rich added a commit that referenced this pull request Jul 9, 2026
chore: bundle third-party licenses for Trivy, Grype, and Syft
tyler-rich added a commit that referenced this pull request Aug 2, 2026
…ty-extended

The first triage was wrong twice, in a way worth recording rather than
quietly fixing.

The run uses the security-extended query suite, not the default code-scanning
one. "Default setup" names the setup mode; the suite is a separate dropdown and
is set to Extended. The evidence is exact: the Python job interpreted 52
queries, python-code-scanning.qls resolves to 45, python-security-extended.qls
resolves to 52, and the runner's 52 interpreted paths are a set-identical match
to the extended list.

Because the local reproduction ran code-scanning.qls, it ran 45 of 52 Python
queries and reported five alerts instead of six. The seven omitted queries
include py/log-injection, which is alert #6. Notably the wrong reproduction
matched the runner's file-extraction counts (174/78/5) exactly - so matching
extraction coverage proves nothing about query coverage, and a reproduction is
not equivalent until its query set is checked against the run's.

The corrected run reproduces the Security tab exactly: six alerts, same rules,
files, lines and severities. Counts are 5 High + 1 Medium.

Adds the triage for alert #6, py/log-injection at api/scans.py:574: scan_id is
an int-annotated FastAPI path parameter, coerced by Pydantic before the handler
runs and rendered with %d, so no newline can reach the log record. CodeQL
treats route parameters as tainted regardless of type annotation, and its
sanitizer set is only constant-comparison, explicit line-break replacement, and
models-as-data barriers.

Also corrects the default-vs-advanced reasoning: suite choice is not an
advanced-setup exclusive, so the real exclusives are custom query packs, path
filters, and trigger control - and trigger control is the one that matters,
given CodeQL does not run on dev PRs.

Docs only.
tyler-rich added a commit that referenced this pull request Aug 2, 2026
…on (#134)

* docs: triage the first CodeQL run and record the default-setup decision

Code scanning (CodeQL) was enabled via default setup on 2026-08-02. The first
run on main @ bb354a5 produced five alerts, all Python: two py/path-injection
on the filesystem-scan containment gate and three
py/incomplete-url-substring-sanitization on test assertions.

Every alert was read against the source and classified. All five are false
positives, with the reasoning recorded per finding rather than asserted:

- The two targets.py alerts are unclearable by construction. CodeQL's
  PathInjection config models only os.path.normpath/abspath/realpath as
  normalizations, so pathlib's Path.resolve() never moves the taint out of
  NotNormalized and the SafeAccessCheck barrier is unreachable regardless of
  the check written. Its only recognized check is str.startswith - the idiom
  this code deliberately avoids because of prefix confusion.
- The three test-file alerts come from a purely syntactic query that matches
  any `"<host>" in <anything>` comparison, with no dataflow and no requirement
  that the operand is a URL or the result a security decision.

Also records the overlap with the 2026-07-03 filesystem-allowlist entry and
H1/SEC-1 (#53), which built and deliberately kept the gate now being flagged.

Nothing was fixed, dismissed, or excluded; the ROADMAP item is struck with the
remaining disposition work and its two dependencies called out.

Docs only. See docs/ARCHIVE.md section 14 for the full triage.

* docs: record that CodeQL does not run on dev PRs

Default setup's pull-request trigger targets the default branch, so it covers
PRs into main; dev - where day-to-day work is actually PR'd - gets no CodeQL
check at all. Confirmed on #134 itself: four check runs, none of them CodeQL.

Both docs previously left this as a question to confirm against a real dev PR.
It has now been confirmed, so record the observation and its consequence: the
roadmap item's worry that enabling CodeQL would immediately join the per-PR
gate is inverted - on the branch that receives PRs, it does not run at all, and
main is only analysed on push after a promotion has landed, plus weekly.

Adding dev to the trigger requires a committed workflow, so this becomes a
concrete second reason to revisit the default-vs-advanced choice alongside path
filters and query-suite tuning.

Docs only.

* docs: correct the CodeQL triage - six alerts, and the suite is security-extended

The first triage was wrong twice, in a way worth recording rather than
quietly fixing.

The run uses the security-extended query suite, not the default code-scanning
one. "Default setup" names the setup mode; the suite is a separate dropdown and
is set to Extended. The evidence is exact: the Python job interpreted 52
queries, python-code-scanning.qls resolves to 45, python-security-extended.qls
resolves to 52, and the runner's 52 interpreted paths are a set-identical match
to the extended list.

Because the local reproduction ran code-scanning.qls, it ran 45 of 52 Python
queries and reported five alerts instead of six. The seven omitted queries
include py/log-injection, which is alert #6. Notably the wrong reproduction
matched the runner's file-extraction counts (174/78/5) exactly - so matching
extraction coverage proves nothing about query coverage, and a reproduction is
not equivalent until its query set is checked against the run's.

The corrected run reproduces the Security tab exactly: six alerts, same rules,
files, lines and severities. Counts are 5 High + 1 Medium.

Adds the triage for alert #6, py/log-injection at api/scans.py:574: scan_id is
an int-annotated FastAPI path parameter, coerced by Pydantic before the handler
runs and rendered with %d, so no newline can reach the log record. CodeQL
treats route parameters as tainted regardless of type annotation, and its
sanitizer set is only constant-comparison, explicit line-break replacement, and
models-as-data barriers.

Also corrects the default-vs-advanced reasoning: suite choice is not an
advanced-setup exclusive, so the real exclusives are custom query packs, path
filters, and trigger control - and trigger control is the one that matters,
given CodeQL does not run on dev PRs.

Docs only.

* docs: close out the filesystem-gate symlink/TOCTOU risks; assess CodeQL advanced setup

Item 1 - both residual risks the CodeQL entry named are closed, and one of
them was simply wrong.

The symlink escape does not happen. Filesystem targets are Grype-only, grype
0.115.0 embeds syft v1.46.0 (the pinned version), and syft's dir provider
defaults its base to the scan directory, which activates chroot-style
re-rooting of every symlink target under that root. Five planted variants -
absolute and relative, to directories and files, plus one to /etc - were all
re-rooted and dropped; only the genuine in-root package was catalogued. Trivy
fs behaves the same. Note that a comment in indexAllRoots says the opposite;
the re-rooting runs first, so the comment describes an intent the code no
longer implements.

Hardlinks are followed, but that is not a bypass: same inode, requires read
access the attacker already has, cannot cross filesystems. Recorded with the
methodology slip that produced a false negative first time - the probe file
must be named what the cataloger globs.

Impact ceiling matters for future severity ratings: grype dir: output carries
no file contents, so this class cannot reproduce H1/SEC-1, whose severity came
from secret values reaching downloadable output.

TOCTOU is a real mechanism - syft re-resolves the root through EvalSymlinks at
scan time - but needs the feature enabled, host write access to the target's
parent, a concurrent operator-triggered scan, and a won race, for an inventory
disclosure. Accepted, no work proposed.

The one actionable item is a regression test: containment rides on syft's
basePath(), which upstream annotates "FIXME why is the base always being set",
so a routine scanner bump could silently make the escape real. Recommended,
not implemented.

Item 2 - advanced-setup assessment recorded, with the measured CI cost (~0
added wall clock), the maintenance cost (~0 marginal, since dependabot already
groups action bumps weekly), confirmation that security-extended is
reproducible via the queries: input, what migrating does and does not lose,
and the case against. Recommendation is to migrate after branch protection.
Corrects the premise that findings arrive after :latest is published - :latest
comes from a tag push, so the real gap is :dev.

Docs only. Nothing implemented.

* docs: correct the CodeQL sequencing premise; branch protection on dev is already live

The item-2 recommendation said to migrate to advanced setup only after the
branch-protection governance item, on the premise that a CodeQL check on a dev
PR could not block a merge until then. Reading the ruleset via the API shows
that premise was wrong.

protect-dev is enforcement: active and already carries pull_request (1
approval, dismiss-stale-on-push, thread resolution, squash-only),
required_status_checks, deletion, and non_fast_forward. protect-main is
equivalent.

The conclusion survives for a sharper reason: required_status_checks is an
explicit allowlist of contexts, currently naming only "Backend - lint + tests"
and "Frontend - lint + build". Neither image job is on it, and CodeQL's
contexts would not be either - so CodeQL would run and be visible without
blocking a merge. But the remedy is adding two or three strings to a ruleset
that already exists, done alongside the migration, not a governance project.
The sequencing dependency is withdrawn.

On the admin bypass: it does not change much for the owner, but required-ness
still buys enforcement for external contributors and converts "merge anyway"
from a non-event into an explicit act - which matters here specifically
because §14 already records this project normalizing red checks. The bypass
list itself is not readable at this token's permission level, so that part
cites the maintainer's statement and the observed dev deletion rather than an
API dump.

Also flags an operational hazard: a required context that never reports blocks
a PR forever, so the CodeQL workflow must not carry path filters if its
contexts become required.

Re-scopes the ROADMAP governance bullet, which listed branch protection as
wholly open when most of it is done - what remains is code-owner review, tag
push restrictions on main, and a decision about the unrequired image jobs.

Cross-references #135 (the Syft basePath regression test) from the entry.

Docs only.

* docs: track the two ruleset gaps as issues; strike an item that was already done

The 2026-08-02 ruleset readout produced two settings-level gaps. Both are now
issues rather than prose, on the same reasoning the governance checklist
exists for - a settings gap leaves no artifact in the repo, so untracked means
invisible.

#136 - the dogfood self-scan is not on required_status_checks, so a PR can
merge into dev with the image scan red. That job is the control CLAUDE.md
mandates: it caught CVE-2026-5773, verifies the SC-14 dev-tree exclusion, and
demonstrates the seven waived interpreter CVEs are the only outstanding
findings - unverifiable if the gate can be merged past. Includes the
paths:-filter hazard, and the distinction that a job skipped by if: still
reports and satisfies a required check while a workflow that never triggers
does not.

#137 - no tag-targeted ruleset exists, and a v*.*.* tag push triggers
publish.yml: GHCR push, the :latest move, provenance and SBOM attestation.
publish.yml's repository guard and main-ancestry check bound the blast radius
but do not constrain who may tag. Theoretical with a sole maintainer; trigger
is before any collaborator is added.

Split into two issues rather than one, per the #98/#116 precedent that an
issue closes on its own trigger - #136 closes on a settings edit now, #137 on
an event that may be far off.

Separately, auditing the checklist found private vulnerability reporting was
already enabled - the API returns {"enabled": true} - while the roadmap still
listed it as open. Struck, with a note that this is the same drift the
checklist exists to prevent arriving from the opposite direction: a completed
item left listed as outstanding. Signed-commit enforcement is confirmed
genuinely open (no required_signatures rule on either ruleset).

Also records that the attribution footer could not be stripped from the issue
bodies - the ingress layer re-appends it on issue writes as it does on the PR
body - so the inconsistency with #98/#116 is explained rather than looking
like a style lapse.

Docs and issues only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant