Supply-chain security
Understand Depsilo v0.11.0 minimum release age, malicious-package blocking, tamper detection, freeze snapshots, and audit integrity.
Depsilo places policy between package managers and upstream registries. v0.11.0 provides these independent controls:
| Control | New-install default | Result | Primary boundary |
|---|---|---|---|
| Minimum release age | Off | Young versions return 451 QUARANTINED |
Timestamp provenance is bound per ecosystem; Go and APT have no usable source |
| Known-malicious blocklist | On, sync every 6 hours | Matched versions return 451 MALICIOUS_BLOCKED |
Covers OSV MAL-* records for 8 ecosystems |
| Tamper detection | On | Retains a first-seen hash and raises a critical alert; no 451 | Passive trust-on-first-use |
| Freeze / golden snapshot | Off | In snapshot mode, unapproved versions return 451 SNAPSHOT_BLOCKED |
Covers only artifacts already in the cache |
| Audit integrity and SIEM routing | Chain on; routing needs configuration | Audit rows are hash-chained and verifiable offline, and can be forwarded to collectors | A full-database rewrite is detectable only with an external anchor |
These controls are independent. None of them changes caching, upstream selection, or proxy behavior.
Minimum release age
Section titled “Minimum release age”Minimum release age refuses versions that are too young on the request path. Depsilo binds “artifact identity + timestamp + the upstream that will serve the bytes” per ecosystem, and only accepts a positive threshold where the timestamp source can be proven:
| Ecosystem | Timestamp source | Enablement |
|---|---|---|
| npm | The packument time[version], carried in the authenticated tarball token |
Available directly |
| PyPI | upload-time from the same upstream’s PEP 691 JSON simple index |
The upstream must serve JSON; HTML-only upstreams fail closed |
| Composer | The p2 metadata entry’s own time field |
Available directly; clients may fall back to the original dist URL after a 451 |
| NuGet | The v3 registration index’s published timestamp |
Available directly |
| Cargo | The sparse index’s pubtime and cksum |
Available directly |
| RubyGems | The compact index’s created_at and checksum |
Available directly |
| Conda, CRAN, Maven, Alpine, Helm | The artifact response’s Last-Modified (approximate) |
Must be acknowledged in supply_chain.approximate_sources |
| Docker | When this instance first saw the digest (first observation, not a publish time) | Must be acknowledged in supply_chain.observation_sources |
| Go, APT | No usable publish-time authority | Keep the threshold at 0 |
[supply_chain]min_release_age_enabled = truemode = "block" # or "warn": record and serve, for an observation periodfail_closed = trueapproximate_sources = ["conda", "cran", "maven", "alpine", "helm"]observation_sources = ["docker"]# allow = ["npm:internal-*"] # bypass by ecosystem prefix; bypasses are audited
[supply_chain.min_release_age]default = "0"pypi = "3d"npm = "7d"cargo = "3d"Behavior and boundaries:
- Decisions run before the cache lookup, so already-cached versions cannot dodge a new policy.
- Missing provenance fails closed. A positive threshold for an unbound or unacknowledged ecosystem is rejected at startup instead of silently degrading to “no check”.
- Administrators can approve a single
(ecosystem, package, version); every approval, revocation, and allow-list bypass is audited. - Docker’s age starts when this instance first pulls the digest and cannot be
forged by the publisher; unseen digests stay quarantined until the threshold
passes, and a registry that omits
Docker-Content-Digestis refused. - Approximate ecosystems trust the serving upstream’s
Last-Modified. Composer clients can fall back around a 451 to the original dist URL, so that gate remains best-effort.
Known-malicious package blocking
Section titled “Known-malicious package blocking”The malicious-package blocklist is enabled by default:
[supply_chain.blocklist]enabled = truesync_interval = "6h"# mirror_url = "https://osv-vulnerabilities.storage.googleapis.com"# proxy = "http://127.0.0.1:7890"v0.11.0 synchronizes MAL-* records from the OSV malicious-packages dataset
for npm, PyPI, Cargo, RubyGems, Composer, NuGet, Go, and Maven. It imports
explicit affected-version lists and all-version advisories. Bounded ranges
without an explicit version list are skipped because Depsilo does not infer
those ranges.
While the dataset covers an ecosystem, an artifact whose identity cannot be
proven is refused rather than served unchecked: PyPI legacy archives
(.zip, .egg, .tar.bz2, …) resolve identity from the simple index that
declares them, and RubyGems resolves the exact gem identity from the compact
index, with platform gems inheriting their base version’s record.
Matching happens before release-age and allow-list checks. A matching artifact already in cache is no longer reachable through this gate, although its bytes remain in storage until LRU or operator cleanup. For a confirmed false positive, an administrator can create an audited override lasting at most 24 hours. It cannot be extended in place; continued access requires another recorded decision.
Synchronization fails open in a bounded way:
- Existing local data remains active after a failed refresh.
- An empty local dataset blocks nothing.
- A synchronization error does not stop the entire proxy from serving packages.
Artifact tamper detection
Section titled “Artifact tamper detection”[supply_chain.tamper_detection]enabled = trueFor artifacts treated as immutable, Depsilo records SHA-256 during the first
streaming fetch. If a natural background refresh later returns different
bytes for the same version, Depsilo refuses to replace the cached content,
keeps the first-seen cached copy, and writes a critical tamper_detected
event. It can also notify a configured webhook. If LRU has already evicted the
first-seen copy, a later cache miss stores and serves the re-fetched bytes
before comparing them with the retained hash baseline. Depsilo alerts, but the
old copy can no longer be restored.
The limitations are as important as the alert:
- Detection is passive. It compares bytes only during a normal background refresh or a re-fetch after eviction, and does not actively probe upstream.
- It is an alerting mechanism, not a generic blocking policy, and does not return 451.
- “First seen” is a local baseline, not a signature or transparency-log proof. If the first fetch is already compromised, its hash becomes the baseline.
- After LRU evicts the original cached bytes, inconsistent new bytes are already stored and may be served before verification. Depsilo can still alert using the old hash, but it cannot restore the old artifact.
Freeze / golden snapshot
Section titled “Freeze / golden snapshot”Promote the artifact set currently in cache into a named, hash-pinned snapshot, then serve only the versions inside it:
- Promote the cache into a named snapshot from the Admin cache page or the
Admin API, and export or import the
depsilo/snapshot/v1manifest. - With snapshot-only mode enabled, anything outside the active snapshot is
refused with 451
SNAPSHOT_BLOCKEDafter the malware gate, and membership lookups fail closed. - Snapshots pin the SHA-256 values recorded by tamper detection. Importing a manifest seeds missing baselines, so a first fetch that differs from the manifest still raises a tamper alert.
Audit integrity and SIEM routing
Section titled “Audit integrity and SIEM routing”Audit rows written since schema v8 are hash-chained (prev_hash / hash,
UTC-normalized timestamps):
- The audit page can re-verify on demand;
GET /api/v1/admin/audit/integrityreturns the full report; anddepsilo audit verify [--json]recomputes the whole chain offline against the SQLite file. - Rows written by earlier versions are reported as a pre-chain prefix instead of being presented as covered.
- Editing, deleting, or re-ordering a chained row names the first broken id.
The chain alone cannot detect a full-database rewrite, because an attacker can
recompute it. Writing chain-head checkpoints outside the database closes most
of that gap: [audit] checkpoint_file appends NDJSON checkpoints with fsync
and cross-checks them during verification, while [audit] checkpoint_url
POSTs the same line to a WORM gateway or log platform, retrying after 60
seconds on failure; delivery status (remote_head_id,
remote_last_success_at, remote_last_error) is visible in the integrity API
and the audit page.
SIEM routing forwards the same audit stream to external collectors as NDJSON
or Splunk HEC envelopes. Each exporter keeps its own cursor and delivers at
least once: a collector outage stops the cursor instead of dropping records,
restarts resume from the last successful batch, and failures back off from 10
seconds to 5 minutes. Filters select events by action and outcome (blocked,
miss, …), and new exporters start at the current audit head. The Admin API
and the audit page cover CRUD, enable/disable, lag and last-error status, and
a test delivery through the real path.
Compliance export (Pro)
Section titled “Compliance export (Pro)”The runtime SBOM export in multi-project workspaces offers a CRA mode and a
technical-file preset: it carries purl, the SHA-256 recorded by tamper
detection, supplier/license fields, and product-to-component dependency
relationships. Supplier and license values come from operator-declared
[compliance.components] annotations; unlisted components export as unknown,
and coverage gaps are reported instead of invented. With a configured Ed25519
key, sign=true returns a detached signature over the response body.
Enforcement boundary
Section titled “Enforcement boundary”Common bypass paths include:
- A direct additional pip index. Do not point
--extra-index-urlat an uncontrolled upstream. - A Go
GOPROXYfallback such as|directthat connects directly after an error. - Composer may fall back to the original dist URL when a mirrored dist is refused. Hard blocking requires restricting client access to those original download hosts.
Depsilo can preserve ecosystem checksums or signatures, but it cannot turn an unsigned artifact into one with proven provenance. It does not replace upstream signing, build attestations, lockfiles, or SBOMs. Minimum release age binds a timestamp source per ecosystem; the blocklist rejects known records; tamper detection reports same-version byte changes; snapshots limit which versions can be served. None is a complete proof that a package is safe.
Audit and alerts
Section titled “Audit and alerts”Quarantine blocks, allow bypasses, approvals and revocations, malicious package blocks and temporary overrides, tamper events, and snapshot blocks are recorded in the database. Admin can filter them by ecosystem, action, and package, verify integrity, and forward them to a SIEM as described above. With webhooks configured, quarantine and malicious-package blocks can notify immediately, while tamper mismatches use critical severity.
Before rollout, verify at least once that:
- A positive threshold for a source-bound ecosystem (for example npm) returns
451
QUARANTINEDfor a young version and leaves an event behind. - When approximate or first-observation ecosystems are enabled, startup
accepts them and the capability summary shows
approximate/observedor the matching safety-paused state instead of an all-green result. - A reviewed Package Rule or the malicious dataset returns 451 and leaves an audit record.
- With snapshot mode active, a version outside the snapshot returns 451
SNAPSHOT_BLOCKED. depsilo audit verifypasses and the checkpoint lands outside the database’s failure domain.- Clients have no fallback registry or direct network route around the proxy, and audit records and webhooks reach the intended operator channel.