chore: bundle third-party licenses for Trivy, Grype, and Syft - #6
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
THIRD_PARTY_LICENSES/with the Apache-2.0LICENSEfor Trivy, Grype, and Syft, and Trivy'sNOTICE(Grype and Syft don't ship one at the pinned versions), pulled from each project's upstream repo at the version currently pinned indocker/Dockerfile(Trivy v0.71.2, Grype v0.115.0, Syft v1.46.0).THIRD_PARTY_LICENSES/into the final image indocker/Dockerfileso attribution ships with the container.THIRD_PARTY_LICENSES/.This satisfies CLAUDE.md's Apache-2.0 §4 attribution requirement for these bundled, unmodified binaries.
Test plan
docker build -f docker/Dockerfile -t scrye:0.1.0 .succeeds and/THIRD_PARTY_LICENSESexists in the built image