Three unpinned git deps + gitignored lockfiles + no --locked + a global insteadOf rewrite that silently substitutes the source
Источник:
dev/fera-research/docs/oc5/unpinned-git-deps-and-source-substitution.md(read-only; сгенерировано scripts/sync-docs.mjs)
Three unpinned git deps + gitignored lockfiles + no --locked + a global insteadOf rewrite that silently substitutes the source
Section titled “Three unpinned git deps + gitignored lockfiles + no --locked + a global insteadOf rewrite that silently substitutes the source”- kind: risk
- area: supply-chain / trust
- severity: high
- evidence (all re-verified directly, not taken on report):
~/dev/fera/chroix-headless/Cargo.toml:18—chroix-contract = { git = "https://github.com/the-fera/chroix-contract" }~/dev/fera/chroix-vello/Cargo.toml:15— same, same shape~/dev/fera/chroix-macros/Cargo.toml:26—chroix-core = { git = "https://github.com/the-fera/chroix-core" }- none of the three declares
rev,tag, orbranch git ls-files Cargo.lock→ NOT TRACKED inchroix-headless,chroix-vello,lattice(all three.gitignoretheir lockfile)~/dev/fera/fera/.github/workflows/ci.yml:30—cargo build --workspace(no--locked)~/dev/fera/fera/.github/workflows/ci.yml:33—cargo test --workspace(no `–locked)git config --global --get-regexp insteadOf→url.file:///home/nikki/dev/fera/chroix-contract.insteadof https://github.com/the-fera/chroix-contract~/dev/fera/chroix-vello/.cargo/config.toml:1-6— gitignored by design (“NEVER committed”), enablesnet.git-fetch-with-cli = trueso cargo honors that rewrite- no
publish = falsein any manifest in the ecosystem;~/dev/fera/fera/Cargo.toml:25—license = "TBD"
Substrate
Section titled “Substrate”Four facts compose into one failure mode.
1. Three dependencies resolve to a moving branch tip. A git =
dependency with no rev/tag/branch tracks the remote default branch
HEAD at every resolve. chroix-contract is depended on by two crates
and chroix-core by a third, so the bytes that build chroix-headless
and chroix-vello today are not necessarily the bytes that built them
yesterday or will build them tomorrow.
2. The lockfiles that would pin them are not in git. All three repos
gitignore Cargo.lock. So the only record of which commit was used is
each developer’s local untracked file. A fresh clone has no pin at all.
3. CI does not use --locked. Even in fera — the one repo that
does track its lockfile and the only repo with CI — the build runs
cargo build --workspace / cargo test --workspace without --locked.
--locked is what turns “the lockfile is authoritative” into “the
lockfile is enforced”; without it, cargo silently re-resolves and
rewrites the lockfile when the manifest and lockfile disagree. The
guarantee is documented by existence but never applied.
4. The actual bytes built are decided by a global git config, outside any repo. This is the sharp edge. On this machine:
url.file:///home/nikki/dev/fera/chroix-contract.insteadOf https://github.com/the-fera/chroix-contractA developer-global rewrite redirects the manifest’s GitHub URL to a
local filesystem path. chroix-vello/.cargo/config.toml sets
net.git-fetch-with-cli = true specifically “so cargo honor[s] that
rewrite” — and that config file is gitignored as “local co-development
overrides, NEVER committed”.
Consequence: the manifest says “github.com/the-fera/chroix-contract”, the lockfile says one thing, and the compiler is fed a third thing from a path in someone’s home directory that no review tool, no CI config and no lockfile records. The supply chain for two of the three git deps is currently a directory on one laptop.
That rewrite is a legitimate dev-loop hack for private repos. The defect is that it is undetectable: nothing in the repo fails, nothing in CI fails, and the build is silently not what the manifest declares.
Adjacent: every crate is publishable by default
Section titled “Adjacent: every crate is publishable by default”No manifest sets publish = false, and fera/Cargo.toml:25 is
license = "TBD". ~22 crates carrying the fera-*, chroix-* and
lttc-format names are therefore publishable to crates.io by anyone with
push access to any of them — and the Fera/Chroix names are squat-able by
anyone at all. Nothing in CI prevents it: there is no publish job, no
cargo publish --dry-run, and no deny.toml.
Options (tradeoffs)
Section titled “Options (tradeoffs)”- Pin
rev+ track the lockfiles +--locked. The standard fix. Cost: cross-repo version bumps become explicit chore commits — real friction, which is the point. Requires un-gitignoring three lockfiles. - Replace git deps with path deps + a release process. Fully
deterministic, no network at build time. Cost: loses the ability to
consume a published
chroix-contractfrom outside the workspace; forces a publish-first flow for anything reused across repos. [patch]/vendoring. Keeps git deps but makes the override visible and reviewable. Cost: patch sections still need a pin.cargo deny/cargo auditin CI. Catches licenses/advisories but does not catch an unpinned git dep — worth adding, but it is not the fix for this finding.
Proposed solution
Section titled “Proposed solution”- Pin all three git deps with an explicit
rev = "..."(full 40-char SHA, never a branch). Update deliberately, not incidentally. - Un-gitignore and commit
Cargo.lockinchroix-headless,chroix-vello,lattice(binary/plugin-adjacent crates are exactly the case where lockfiles should be tracked). - Add
--lockedto everycargo build/cargo test/cargo clippyinvocation in~/.github/workflows/ci.yml(lines 30, 33, 36). - Add a CI guard against source substitution: fail if
git config --get-regexp insteadOfrewrites any dependency URL, and fail if a manifest’sgit =dependency lacksrev/tag. Without this the rewrite above stays invisible forever. - Move the local-path override out of gitignore-forever and into an
explicit, documented dev flow — e.g. a checked-in
.cargo/config.toml.exampleplus ajust dev-localwrapper — so the rewrite is a declared local action rather than ambient machine state. - Set
publish = falseon every crate that must never reach crates.io (workspace-wide via[workspace.package]), and resolvelicense = "TBD"before any publish is possible.
Note for whoever sequences this: the lockfiles currently pin
chroix-contract@c37de46 and chroix-core@647dcaa, which happen to
match the local checkouts. That is luck, not a guarantee — it will
silently stop being true the moment either upstream branch moves.