bubbles-supply-chain-source-locking
Lock build-time dependency resolution to an explicit allowlist of trusted registries/sources so dependencies can never be pulled from an arbitrary or implicit upstream. Use when adding or editing a dependency manifest or lockfile (Cargo.toml/Cargo.lock, package.json/package-lock.json, go.mod/go.sum, requirements.txt/constraints); when configuring a package-source file (cargo-deny config, .npmrc, GOPROXY/GOFLAGS env, pip index config); when wiring or reviewing a lint/pre-push gate that touches dependency installation; when reviewing a PR that adds a new external dependency, a new registry, or a git/path dependency; or when investigating dependency-confusion, typosquat, or malicious-mirror risk. Distinct from deploy-time artifact provenance (cosign/SLSA/SBOM/Trivy) — see "Two Axes" below.
- License 许可
- MIT
- 最近更新
- 2026-11-07
- Stars
- ★ 1
- Forks
- 0
01 前言 Front Matter
- name
- bubbles-supply-chain-source-locking
- description
- Lock build-time dependency resolution to an explicit allowlist of trusted registries/sources so dependencies can never be pulled from an arbitrary or implicit upstream. Use when adding or editing a dependency manifest or lockfile (Cargo.toml/Cargo.lock, package.json/package-lock.json, go.mod/go.sum, requirements.txt/constraints); when configuring a package-source file (cargo-deny config, .npmrc, GOPROXY/GOFLAGS env, pip index config); when wiring or reviewing a lint/pre-push gate that touches dependency installation; when reviewing a PR that adds a new external dependency, a new registry, or a git/path dependency; or when investigating dependency-confusion, typosquat, or malicious-mirror risk. Distinct from deploy-time artifact provenance (cosign/SLSA/SBOM/Trivy) — see "Two Axes" below.
03 SKILL.md 正文
Bubbles Supply-Chain Source Locking
Goal
Make every build resolve its dependencies from an explicit, reviewed allowlist of trusted sources — never from an arbitrary, implicit, or fall-through upstream. The build inputs are as governed as the build outputs.
The single sentence: a dependency can only enter the build from a source you explicitly allowed.
Use This Skill When
- Adding or editing a dependency manifest or lockfile (
Cargo.toml/Cargo.lock,package.json/package-lock.json,go.mod/go.sum,requirements.txt/constraints). - Configuring a package-source file (
cargo-denyconfig,.npmrc,GOPROXY/GOFLAGS, pip index configuration). - Wiring or reviewing a lint / pre-push gate that installs or resolves dependencies.
- Reviewing a PR that adds a new external dependency, a new registry, or a
git/path dependency. - Investigating dependency-confusion, typosquatting, or malicious-mirror exposure.
Core Rule (NON-NEGOTIABLE)
Dependency resolution MUST be locked to an explicit allowlist of trusted registries/sources. Arbitrary or implicit upstream sources are forbidden. The enforcement is BLOCKING — there is no
--skip/--force/--allow-once.
A manifest that can silently resolve a package from an un-allowlisted index, a second
("extra") index, a git/path source, or an implicit fall-through proxy is a blocking
finding — even if today's lockfile happens to point at a trusted source. The control is
the source allowlist, not the current pin.
Two Axes — Source Locking Is NOT Deploy-Time Provenance
These are complementary controls on different axes. A repo needs BOTH; one never substitutes for the other.
| Axis | This skill (build-time SOURCE locking) | Deploy-time artifact PROVENANCE |
|---|---|---|
| Question answered | Where did the code enter the build from? | Is the built artifact authentic, signed, and scanned before it runs? |
| Failure it stops | Dependency-confusion, typosquat, malicious mirror, unexpected upstream, second-index fall-through | Tampered/unsigned image, unknown provenance, unscanned CVEs at deploy |
| Mechanism | Source allowlist in manifest/lockfile config + blocking resolve-source check | Signature + attestation verification at the deploy boundary |
| Typical tools | cargo-deny [sources], pinned .npmrc registry, pinned GOPROXY + go.sum, pip --index-url + hashes |
cosign keyless signing, SLSA build-provenance, SBOM attestation, image CVE scan |
| Owned in Bubbles by | this skill + its instruction; wired into each repo's lint/pre-push gate | bubbles-deployment-target-adapter (see "See Also") |
Source locking stops bad inputs from entering the build. Provenance proves the output is the expected, signed, scanned artifact. Do not collapse them: a perfectly signed image built from a typosquatted dependency still ships the compromised code.
Per-Ecosystem Guidance
The rule is toolchain-agnostic; the wiring is ecosystem-specific. In every case: pin the source allowlist, commit the lockfile, deny implicit fall-through, and wire a blocking check into the repo's existing lint/pre-push gate.
Rust / Cargo
Use cargo-deny's [sources] section as the source allowlist:
# deny.toml
[sources]
unknown-registry = "deny" # any registry not in allow-registry => error
unknown-git = "deny" # any git source not in allow-git => error
allow-registry = ["sparse+https://index.crates.io/"] # exactly one trusted index
allow-git = [] # no git deps unless explicitly allowlisted here
allow-registrySHOULD contain exactly one entry (the canonical first-party index, or the operator's pinned mirror) — not a list of "wherever it happens to be".allow-git = []by default; every git dependency is an explicit, reviewed exception.- Commit
Cargo.lock. - Wire
cargo deny check sourcesinto the repo's blocking lint/pre-push gate.
Node / npm
- Pin the registry in a committed
.npmrc:registry=<trusted-registry>(the canonical first-party registry or the operator's pinned mirror). - Forbid arbitrary per-package / per-scope registry overrides (
@scope:registry=...) unless that scope is an explicitly reviewed allowlist entry. - Commit
package-lock.jsonand install lockfile-strict (npm ci), never an unpinned resolve. - No implicit fall-through to a second registry; a dependency that resolves only from an un-pinned source is a finding.
Go
GOFLAGS=-mod=readonlyso the build can never silently rewritego.mod/go.sum.- Pin
GOPROXYto a single trusted proxy; do not append an open,directfall-through unless that direct fetch path is an explicit, reviewed exception. - Commit
go.sumand verify it (go mod verify) in the gate. - The checksum-disable knobs
GONOSUMCHECK,GONOSUMDB,GOINSECURE, and a permissiveGOPRIVATEcovering public modules are FORBIDDEN — they defeat source verification.
Python / pip
- Pass an explicit
--index-url(the trusted index); never rely on the implicit default. - FORBID
--extra-index-urlto an un-allowlisted host — it is the classic dependency-confusion vector (pip resolves the highest version across all indexes). - Hash-pin every requirement (
--require-hasheswith pinned==versions) and commit the locked requirements/constraints file.
Forbidden vs Required
| ❌ Forbidden | ✅ Required |
|---|---|
| Manifest/config that can resolve from any registry not on an allowlist | Explicit single-source (or reviewed multi-entry) allowlist |
cargo-deny unknown-registry/unknown-git unset or allow |
unknown-registry = "deny", unknown-git = "deny", populated allow-registry |
Implicit ,direct / second-index / extra-index fall-through |
One pinned source; every extra source is a reviewed allowlist entry |
pip --extra-index-url to an un-allowlisted host |
Single --index-url; hash-pinned, version-pinned requirements |
| Uncommitted lockfile, or unpinned resolve in CI | Committed lockfile + lockfile-strict install (npm ci, -mod=readonly) |
Checksum/verification disable knobs (GONOSUMCHECK, GOINSECURE, --trusted-host) |
Verification ON; go.sum/hashes verified in the gate |
Un-reviewed git/path/URL dependency |
Each such source is an explicit allowlist entry with a recorded reason |
A --skip / --force / --allow-once bypass on the source check |
Blocking check, no bypass flag |
Enforcement
- Each downstream repo wires the ecosystem-appropriate source check into its existing blocking lint / pre-push gate (the same gate that already runs format/lint). Source locking adds NO new framework gate — it is a policy that each repo's existing gate enforces with the ecosystem's native tool.
- The check MUST be blocking: an un-allowlisted source, a missing lockfile, a disabled verification knob, or an un-reviewed extra index fails the gate.
- There is NO bypass flag. A legitimately new trusted source is added by editing the allowlist (a reviewed change), never by skipping the check for one run.
- New trusted sources are a reviewed config change (allowlist edit), consistent with
bubbles-config-sstsingle-source-of-truth governance for registry/lockfile config.
Verification Commands
# Rust: source allowlist is enforced and blocking
grep -nE 'unknown-(registry|git)|allow-(registry|git)' deny.toml
# Expectation: unknown-registry = "deny", unknown-git = "deny", explicit allow-registry
# Node: registry pinned, lockfile committed, no stray per-scope registry
grep -nE '^registry=|:registry=' .npmrc
ls -la package-lock.json
# Go: read-only modules, pinned proxy, no checksum-disable knobs
grep -nE 'GOFLAGS|GOPROXY|GONOSUM|GOINSECURE|GOPRIVATE' .env* *.mk Makefile 2>/dev/null
ls -la go.sum
# Python: single index, hash-pinned, no extra index
grep -nE -- '--index-url|--extra-index-url|--require-hashes|--trusted-host' requirements*.txt pip.conf 2>/dev/null
Anti-Patterns (BLOCKING)
| Anti-Pattern | Why It's Wrong | Fix |
|---|---|---|
| "The lockfile already points at the trusted source, so we're fine" | The control is the source allowlist, not the current pin; the next resolve can drift | Lock the allowlist, not just today's pin |
Adding --extra-index-url "to grab one internal package" |
Dependency-confusion: a public typosquat at a higher version wins | Allowlist the internal index explicitly; pin/hash the package |
Disabling go.sum verification to "unblock a build" |
Defeats source verification entirely | Fix the source/pin; never disable verification |
| Treating cosign/SLSA/SBOM as "we already do supply chain" | Provenance is a different axis; it does not govern build inputs | Add source locking AND keep provenance |
A --skip-source-check escape hatch in the gate |
First skip becomes the permanent path | Blocking check; new sources via allowlist edit only |
Integration With Bubbles Governance
| Bubbles Surface | Relevance |
|---|---|
bubbles-config-sst |
Registry/lockfile/source config is single-source-of-truth; no scattered or hand-drifted source config |
| Repo lint / pre-push gate | Hosts the ecosystem source check; blocking, no bypass |
bubbles-deployment-target-adapter |
The complementary deploy-time provenance axis (cosign/SLSA/SBOM/Trivy) |
| Spec / design authoring | A spec that adds a new external dependency or registry declares the trusted source and any allowlist exception |
See Also
- bubbles-supply-chain-source-locking.instructions.md — binding, agent-loaded counterpart
- bubbles-deployment-target-adapter skill — deploy-time artifact provenance (cosign keyless signing, SLSA build-provenance, SBOM attestation, image CVE scan) — the complementary axis
- bubbles-backup-bcdr-doctrine skill — offsite/backup trust boundaries
- bubbles-config-sst skill — single-source-of-truth governance for registry/lockfile config
04 同仓库更多 Skills
smackerel-trace-capture
Wire Smackerel's concrete validate-plane stack into the framework trace-driven defect-discovery method. Use when running live-category tests (integration/e2e/e2e-ui/stress) against the ephemeral test stack; capturing SLO or trace evidence; hunting latency/error/perf bugs from spans; querying the test-stack Jaeger or Prometheus; or touching `.specify/runtime/observability`. Concrete smackerel endpoints + the exact `capture-slo.sh` command; do NOT duplicate `bubbles-observability-adapter` — this only adds the wiring.
smackerel-no-defaults
Enforce Smackerel's NO-DEFAULTS / fail-loud SST policy. Use when editing config, Docker Compose, deploy adapters, docs, instructions, skills, env handling, host bind addresses, or any runtime value that could be hidden by fallback syntax.
bubbles-workflow-mode-resolution
Resolve a Bubbles workflow mode against `bubbles/workflows.yaml`, including template inheritance, status ceilings, gate wiring, and decision policy. Use when picking a mode for a request, when a guard reports a mode-related failure, or when extending workflows.yaml with a new mode or template. Covers natural-language intent routing and the workflow registry consistency rules.
bubbles-workflow-execution-loops
Run per-round synchronous workflow execution loops correctly. Use when orchestrating a multi-round workflow, dispatching specialist subagents, handling round-based remediation, or interpreting a workflow mode's execution policy. Covers authorized direct execution, dispatch-and-wait, batch-then-summarize prohibition, and mapped-mode execution.
bubbles-vscode-agent-constraints
Design agents that fit the VS Code Copilot Chat runtime instead of fighting it. Use when authoring or editing any `*.agent.md`; when adding `tools:`, `agents:`, `handoffs:`, `model:`, `user-invocable:`, or `disable-model-invocation:` frontmatter; when designing an orchestrator that dispatches specialists; when a design implies agent-calls-agent-calls-agent chaining; when a subagent dispatch silently no-ops or an agent cannot be selected in the dropdown; or when writing a governance rule about delegation that the runtime cannot actually enforce. Prevents multi-level-subagent designs, prose-only dispatch laws, and frontmatter/body contradictions.
bubbles-upkeep-cadence
How to schedule, dispatch, and record recurring operational upkeep tasks (backup, restore-drill, BCDR-drill, patch-cycle, secret-rotation, flag-cleanup, compliance-sweep). Use when authoring or modifying `config/upkeep-calendar.yaml`, when investigating a missed task, when reading the upkeep ledger, or when adding a new task type.