SECURITY: add repeatable advisory and licence checks for locked dependencies #812

Open
opened 2026-10-02 13:13:54 +00:00 by kayg · 6 comments
Owner

Context

Supply-chain audit under #663, origin/dev c4a61e8cf0. CLAUDE.md requires dependencies compatible with AGPL-3.0-only and excludes proprietary and non-commercial model weights. Duplicate searches for dependency, licence, advisory, audit and supply chain found no matching automation issue.

Evidence and impact

.forgejo/workflows/ci.yml:26-56 installs the frozen Bun lock and runs type checks, tests and Rust gates. It runs no RustSec/Bun advisory check and no dependency licence check. .forgejo/workflows/weekly-consistency.yml audits UI only. No cargo-deny/deny.toml policy is in the tracked tree.

The audit found six Bun advisories in four dependency groups and Rust advisory/unmaintained matches despite frozen locks. Freezing a lock preserves dependency bytes; it does not detect a new advisory or changed licence in a lock update. Manual audit results become stale after later merges.

The inspected metadata has 832 registry crates and 743 npm release records, including platform-only releases. No dependency with a solely GPL-2.0-only, proprietary or non-commercial licence was identified. nom-exif 3.8.0 and svelte-toolbelt 0.10.6 have no SPDX field but include MIT licence files. A string-only check would misclassify these. self_cell 1.3.0 offers Apache-2.0 OR GPL-2.0-only; select Apache-2.0 rather than rejecting all dual licences. Keep notices for MPL/LGPL and data licences. This check is a policy gap, not a finding of current licence incompatibility.

Concrete fix

Add one small advisory/licence job plus a scheduled refresh on dev. Verify tool versions and record the advisory database revision. Cover Cargo.lock, fuzz/Cargo.lock, bench/embed-backends/Cargo.lock, crates/calternal-auth/Cargo.lock, bun.lock and tests/adversarial/bun.lock. Scope exceptions to an advisory/package/reason/expiry. Include a reviewed licence-file clarification when SPDX is absent. Audit model manifests and reviewed licence sources. Fail product changes on proven security holes or incompatible licences; file maintenance issues for unmaintained or unreachable matches.

Validation

Prove the check detects a harmless fixture with a disallowed licence and an advisory-matched version. Prove reviewed MIT licence-file packages and an Apache alternative pass. Prove an expired exception fails. Keep fixtures in tests and do not install or execute fixture packages.

No product edits were made by this audit. Non-blocking: this closes an ongoing assurance gap.

## Context Supply-chain audit under #663, origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5. CLAUDE.md requires dependencies compatible with AGPL-3.0-only and excludes proprietary and non-commercial model weights. Duplicate searches for dependency, licence, advisory, audit and supply chain found no matching automation issue. ## Evidence and impact `.forgejo/workflows/ci.yml:26-56` installs the frozen Bun lock and runs type checks, tests and Rust gates. It runs no RustSec/Bun advisory check and no dependency licence check. `.forgejo/workflows/weekly-consistency.yml` audits UI only. No cargo-deny/deny.toml policy is in the tracked tree. The audit found six Bun advisories in four dependency groups and Rust advisory/unmaintained matches despite frozen locks. Freezing a lock preserves dependency bytes; it does not detect a new advisory or changed licence in a lock update. Manual audit results become stale after later merges. The inspected metadata has 832 registry crates and 743 npm release records, including platform-only releases. No dependency with a solely GPL-2.0-only, proprietary or non-commercial licence was identified. nom-exif 3.8.0 and svelte-toolbelt 0.10.6 have no SPDX field but include MIT licence files. A string-only check would misclassify these. self_cell 1.3.0 offers Apache-2.0 OR GPL-2.0-only; select Apache-2.0 rather than rejecting all dual licences. Keep notices for MPL/LGPL and data licences. This check is a policy gap, not a finding of current licence incompatibility. ## Concrete fix Add one small advisory/licence job plus a scheduled refresh on dev. Verify tool versions and record the advisory database revision. Cover Cargo.lock, fuzz/Cargo.lock, bench/embed-backends/Cargo.lock, crates/calternal-auth/Cargo.lock, bun.lock and tests/adversarial/bun.lock. Scope exceptions to an advisory/package/reason/expiry. Include a reviewed licence-file clarification when SPDX is absent. Audit model manifests and reviewed licence sources. Fail product changes on proven security holes or incompatible licences; file maintenance issues for unmaintained or unreachable matches. ## Validation Prove the check detects a harmless fixture with a disallowed licence and an advisory-matched version. Prove reviewed MIT licence-file packages and an Apache alternative pass. Prove an expired exception fails. Keep fixtures in tests and do not install or execute fixture packages. No product edits were made by this audit. Non-blocking: this closes an ongoing assurance gap.
Author
Owner

Starting #812 on branch job/deps, based at 2f4482ded066d9c5d9c59130377907f7fd2916c9 (job/merge-round-7a). I read the issue, audit evidence, CLAUDE.md, CONTEXT.md and relevant DESIGN sections. I will add repeatable locked-dependency advisory and licence checks with fixtures and explicit exception expiry.

Starting #812 on branch `job/deps`, based at `2f4482ded066d9c5d9c59130377907f7fd2916c9` (`job/merge-round-7a`). I read the issue, audit evidence, CLAUDE.md, CONTEXT.md and relevant DESIGN sections. I will add repeatable locked-dependency advisory and licence checks with fixtures and explicit exception expiry.
Author
Owner

Implemented in 73d03095e: the CI job and weekly dev schedule audit Cargo.lock, fuzz/Cargo.lock, bench/embed-backends/Cargo.lock, crates/calternal-auth/Cargo.lock, bun.lock and tests/adversarial/bun.lock. The checked inventory binds every locked release to its registry checksum/integrity and reviewed licence expression. scripts/refresh_dependency_license_inventory.py refreshes it only after verifying registry bytes against each lock. Missing SPDX metadata is clarified only for nom-exif 3.8.0 and svelte-toolbelt 0.10.6, with the reviewed licence-file SHA-256 and locked release identity recorded.

The fixture suite proves that a disallowed licence, mismatched advisory version and expired exception fail; the Apache option in an OR expression and integrity-bound MIT licence-file clarifications pass. Local lock/inventory verification passed. The cargo-audit 0.22.2 build is still running locally; I will record its output after testing the real RustSec reports.

Implemented in `73d03095e`: the CI job and weekly `dev` schedule audit Cargo.lock, fuzz/Cargo.lock, bench/embed-backends/Cargo.lock, crates/calternal-auth/Cargo.lock, bun.lock and tests/adversarial/bun.lock. The checked inventory binds every locked release to its registry checksum/integrity and reviewed licence expression. `scripts/refresh_dependency_license_inventory.py` refreshes it only after verifying registry bytes against each lock. Missing SPDX metadata is clarified only for nom-exif 3.8.0 and svelte-toolbelt 0.10.6, with the reviewed licence-file SHA-256 and locked release identity recorded. The fixture suite proves that a disallowed licence, mismatched advisory version and expired exception fail; the Apache option in an OR expression and integrity-bound MIT licence-file clarifications pass. Local lock/inventory verification passed. The cargo-audit 0.22.2 build is still running locally; I will record its output after testing the real RustSec reports.
Author
Owner

Completed on branch job/deps. Final head: 306fd0a953.

Added a lock-bound license inventory for the four Cargo and two Bun locks, verified Cargo checksums and npm integrity values, and hashed the reviewed license-file clarifications. CI checks the inventory, product AGPL declarations, RSA source guard, advisory exceptions, and every locked package. Refreshed the inventory after merging origin/dev and origin/job/merge-round-7a.

Gate output:

..........
Ran 10 tests in 0.343s

OK
cargo-audit 0.22.2; release SHA-256 ab28a1bdb54db4d5d8ad5981cf1f959410370b3d28250dbd35f6a44248620e39
PASS dependency advisories and licences; RustSec database 117edb3bed98e9be112f277b7615eea3252e7c43

Known gap: cargo test -p calternal-search was still compiling dependencies at the four-hour stop and exited 130 without reaching a test summary.

Completed on branch job/deps. Final head: 306fd0a953f342395af64cc62f0b18f3416ea95a. Added a lock-bound license inventory for the four Cargo and two Bun locks, verified Cargo checksums and npm integrity values, and hashed the reviewed license-file clarifications. CI checks the inventory, product AGPL declarations, RSA source guard, advisory exceptions, and every locked package. Refreshed the inventory after merging origin/dev and origin/job/merge-round-7a. Gate output: ``` .......... Ran 10 tests in 0.343s OK cargo-audit 0.22.2; release SHA-256 ab28a1bdb54db4d5d8ad5981cf1f959410370b3d28250dbd35f6a44248620e39 PASS dependency advisories and licences; RustSec database 117edb3bed98e9be112f277b7615eea3252e7c43 ``` Known gap: cargo test -p calternal-search was still compiling dependencies at the four-hour stop and exited 130 without reaching a test summary.
Author
Owner

P2: Read Cargo licence metadata from verified archive bytes

Evidence: scripts/refresh_dependency_license_inventory.py:57–:65
reads Cargo.toml from the extracted local registry source. It checks the
name and version, then returns the licence without checking the archive
checksum. Only the fallback at lines 69–83 checks the archive bytes.
The caller at lines 202–209 checks the sparse-index checksum, but that
checksum does not authenticate the separate extracted source file.

A locally changed licence field can therefore enter the committed inventory
with the authentic archive checksum. The policy then accepts the permitted
licence in that inventory. The result depends on the local source cache and
does not meet the claimed integrity check. No changed or incompatible package
in the current inventory was proved.

Fix: read every Cargo licence field from the checksum-verified .crate
archive. Reuse the existing archive reader and download helper. If extracted
files are used, verify those files against the authentic archive first.
Expected result: a local source-cache edit cannot change the licence inventory.
Rule: #812 requires reviewed, lock-bound licence evidence; CLAUDE.md and
DESIGN §43 require compatible dependencies.
Test idea: use a small local archive fixture and a different extracted
Cargo.toml with the same name and version. The verified archive must decide
the licence. A changed archive checksum must fail.

Tracking: searches for licence inventory and registry metadata found #812.
Assign this fix to #812; do not create a duplicate issue.

P2: Include model manifests in the ongoing licence check

Evidence: scripts/dependency_policy.py:526–:530 checks advisories,
the Cargo/npm inventory, the RSA guard and product manifests. It does not
read model manifests or their licence sources. The policy JSON contains
only Cargo and Bun lock lists and two package licence clarifications.
crates/calternal-embed/models/manifest.json:4 and :34 declare the
embedding and CLIP licences, but neither enters this CI check.

Issue #812 explicitly requires model manifests and reviewed licence sources.
A model change can pass this check without a reviewed licence record. The
Rust model loader has checks for its expected licence strings, but this CI
job does not run those checks and a string is not reviewed upstream evidence.
This finding is a missing assurance check, not evidence of incompatible
current model weights.

Fix: add the model manifest identities, revisions, artifact hashes and
reviewed licence sources to the same policy. Reject a new or changed model
identity until its licence evidence is reviewed. Keep one common check.
Expected result: this CI job checks model changes as well as package changes.
Rule: #812; CLAUDE.md forbids non-commercial model weights; DESIGN §43.
Test idea: a local model-manifest fixture with missing evidence or a
non-commercial licence fails. The reviewed pinned embedding and CLIP
fixtures pass. Do not download model weights for this fixture.

Tracking: searches for model licence and model manifest found #812 and #868.
#812 owns the policy fix; #868 is a separate current-licence audit.

## P2: Read Cargo licence metadata from verified archive bytes Evidence: `scripts/refresh_dependency_license_inventory.py:57`–`:65` reads `Cargo.toml` from the extracted local registry source. It checks the name and version, then returns the licence without checking the archive checksum. Only the fallback at lines 69–83 checks the archive bytes. The caller at lines 202–209 checks the sparse-index checksum, but that checksum does not authenticate the separate extracted source file. A locally changed licence field can therefore enter the committed inventory with the authentic archive checksum. The policy then accepts the permitted licence in that inventory. The result depends on the local source cache and does not meet the claimed integrity check. No changed or incompatible package in the current inventory was proved. Fix: read every Cargo licence field from the checksum-verified `.crate` archive. Reuse the existing archive reader and download helper. If extracted files are used, verify those files against the authentic archive first. Expected result: a local source-cache edit cannot change the licence inventory. Rule: #812 requires reviewed, lock-bound licence evidence; CLAUDE.md and DESIGN §43 require compatible dependencies. Test idea: use a small local archive fixture and a different extracted `Cargo.toml` with the same name and version. The verified archive must decide the licence. A changed archive checksum must fail. Tracking: searches for licence inventory and registry metadata found #812. Assign this fix to #812; do not create a duplicate issue. ## P2: Include model manifests in the ongoing licence check Evidence: `scripts/dependency_policy.py:526`–`:530` checks advisories, the Cargo/npm inventory, the RSA guard and product manifests. It does not read model manifests or their licence sources. The policy JSON contains only Cargo and Bun lock lists and two package licence clarifications. `crates/calternal-embed/models/manifest.json:4` and `:34` declare the embedding and CLIP licences, but neither enters this CI check. Issue #812 explicitly requires model manifests and reviewed licence sources. A model change can pass this check without a reviewed licence record. The Rust model loader has checks for its expected licence strings, but this CI job does not run those checks and a string is not reviewed upstream evidence. This finding is a missing assurance check, not evidence of incompatible current model weights. Fix: add the model manifest identities, revisions, artifact hashes and reviewed licence sources to the same policy. Reject a new or changed model identity until its licence evidence is reviewed. Keep one common check. Expected result: this CI job checks model changes as well as package changes. Rule: #812; CLAUDE.md forbids non-commercial model weights; DESIGN §43. Test idea: a local model-manifest fixture with missing evidence or a non-commercial licence fails. The reviewed pinned embedding and CLIP fixtures pass. Do not download model weights for this fixture. Tracking: searches for model licence and model manifest found #812 and #868. #812 owns the policy fix; #868 is a separate current-licence audit.
Author
Owner

Follow-up evidence from licence audit #868, at audit commit f35a06885b558226115c5fff75d1ef48dc2c59bb:

  • Containerfile:24-39 defines the final runtime image. Containerfile:32 copies only calternal-server; the recipe does not copy the project LICENSE or a third-party notice bundle.
  • NOTICE:1-10 names Maple Mono only.
  • The Photos binary embeds the GeoNames data (crates/plugins/photos/src/geonames.rs:3-10). Its README.md:7-17 records CC BY 4.0 and the required GeoNames credit.
  • crates/calternal-server/Cargo.toml:79 enables utz with its balanced data preset. utz_data_balanced 0.4.0+2026c declares MIT AND ODbL-1.0; its local crate source has a separate LICENSE-DATA with OpenStreetMap and timezone-boundary-builder attribution. The server binary includes this asset through utz.

Expected result: include the AGPL licence and a third-party notice bundle in the distributed image. Include GeoNames credit and the ODbL data terms. Test idea: inspect the final OCI image and assert that the project licence and notice entries are present. I did not build an image in this read-only audit.

This adds concrete distribution evidence to the existing notice coverage work. The full lockfile results are in audit-findings.md on #868.

Follow-up evidence from licence audit #868, at audit commit `f35a06885b558226115c5fff75d1ef48dc2c59bb`: - `Containerfile:24-39` defines the final runtime image. `Containerfile:32` copies only `calternal-server`; the recipe does not copy the project `LICENSE` or a third-party notice bundle. - `NOTICE:1-10` names Maple Mono only. - The Photos binary embeds the GeoNames data (`crates/plugins/photos/src/geonames.rs:3-10`). Its `README.md:7-17` records CC BY 4.0 and the required GeoNames credit. - `crates/calternal-server/Cargo.toml:79` enables `utz` with its `balanced` data preset. `utz_data_balanced 0.4.0+2026c` declares `MIT AND ODbL-1.0`; its local crate source has a separate `LICENSE-DATA` with OpenStreetMap and timezone-boundary-builder attribution. The server binary includes this asset through `utz`. Expected result: include the AGPL licence and a third-party notice bundle in the distributed image. Include GeoNames credit and the ODbL data terms. Test idea: inspect the final OCI image and assert that the project licence and notice entries are present. I did not build an image in this read-only audit. This adds concrete distribution evidence to the existing notice coverage work. The full lockfile results are in `audit-findings.md` on #868.
Author
Owner

Fixed both P2 findings from the independent review on job/deps at ea6a37844.

  • The inventory refresher now checks the sparse-index checksum against the lock, hashes the cached .crate bytes (or downloads and verifies them), then reads the exact Cargo.toml member from that archive. Extracted source trees no longer decide the licence.
  • The common dependency licence check now covers every model object in the configured model manifest. It binds model and upstream identities, immutable revisions, licence values, all manifest asset SHA-256 values, reviewed reasons and HTTPS licence sources. New model identities and changed assets fail until the policy record is reviewed. MiniLM evidence uses its pinned Hugging Face revision; CLIP evidence includes the OpenAI licence source and pinned Xenova mirror revision.

The new regressions prove that a changed extracted Cargo.toml cannot override archive metadata, a bad archive checksum fails, missing model evidence fails, non-commercial licences fail, and changed model revisions or asset hashes need review.

Focused output:

....................
----------------------------------------------------------------------
Ran 20 tests in 0.498s

OK
.......
----------------------------------------------------------------------
Ran 7 tests in 0.003s

OK
PASS workflow action SHAs, container image digests and dated Debian snapshots

dependency_policy.py --check is reserved for the merge round by the current verification policy.

Fixed both P2 findings from the independent review on `job/deps` at `ea6a37844`. - The inventory refresher now checks the sparse-index checksum against the lock, hashes the cached `.crate` bytes (or downloads and verifies them), then reads the exact `Cargo.toml` member from that archive. Extracted source trees no longer decide the licence. - The common dependency licence check now covers every model object in the configured model manifest. It binds model and upstream identities, immutable revisions, licence values, all manifest asset SHA-256 values, reviewed reasons and HTTPS licence sources. New model identities and changed assets fail until the policy record is reviewed. MiniLM evidence uses its pinned Hugging Face revision; CLIP evidence includes the OpenAI licence source and pinned Xenova mirror revision. The new regressions prove that a changed extracted `Cargo.toml` cannot override archive metadata, a bad archive checksum fails, missing model evidence fails, non-commercial licences fail, and changed model revisions or asset hashes need review. Focused output: ``` .................... ---------------------------------------------------------------------- Ran 20 tests in 0.498s OK ....... ---------------------------------------------------------------------- Ran 7 tests in 0.003s OK PASS workflow action SHAs, container image digests and dated Debian snapshots ``` `dependency_policy.py --check` is reserved for the merge round by the current verification policy.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#812
No description provided.