Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

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, or branch
    • git ls-files Cargo.lock → NOT TRACKED in chroix-headless, chroix-vello, lattice (all three .gitignore their 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”), enables net.git-fetch-with-cli = true so cargo honors that rewrite
    • no publish = false in any manifest in the ecosystem; ~/dev/fera/fera/Cargo.toml:25 — license = "TBD"

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-contract

A 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.

  1. 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.
  2. 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-contract from outside the workspace; forces a publish-first flow for anything reused across repos.
  3. [patch]/vendoring. Keeps git deps but makes the override visible and reviewable. Cost: patch sections still need a pin.
  4. cargo deny / cargo audit in CI. Catches licenses/advisories but does not catch an unpinned git dep — worth adding, but it is not the fix for this finding.
  • 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.lock in chroix-headless, chroix-vello, lattice (binary/plugin-adjacent crates are exactly the case where lockfiles should be tracked).
  • Add --locked to every cargo build/cargo test/cargo clippy invocation in ~/.github/workflows/ci.yml (lines 30, 33, 36).
  • Add a CI guard against source substitution: fail if git config --get-regexp insteadOf rewrites any dependency URL, and fail if a manifest’s git = dependency lacks rev/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.example plus a just dev-local wrapper — so the rewrite is a declared local action rather than ambient machine state.
  • Set publish = false on every crate that must never reach crates.io (workspace-wide via [workspace.package]), and resolve license = "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.