capdrift 0.1.2
capdrift: ^0.1.2 copied to clipboard
Show what a Dart package can do, what changed between versions, and whether that change violates your policy.
Changelog #
0.1.2 #
Fixed #
audit --resolvesilently dropped git, path and custom-hosted dependencies. 0.1.1 fixed this for the default lockfile path, and the resolver path kept doing it, and did it worse: it never mentioned the dropped dependency at all. Map constraints were discarded before resolution, so nothing downstream knew they existed,ALLOWwas reachable, and--resolve --approvecould write a baseline over a tree with a dependency nobody had looked at.
Added #
- A coverage invariant test. Every audit path is now checked against one
rule: if any dependency was not analysed,
ALLOWis impossible. This class of bug appeared four separate times, and point fixes did not stop it recurring because each new path had to rediscover the rule. A future path that does not honour it fails the test.
Changed #
- The
--include-devdocumentation said dev dependencies are excluded by default. What is excluded is direct dev dependencies; their transitives are still analysed, because a lockfile does not record which root introduced a transitive. The behaviour errs toward analysing more than promised, which is the safe direction, and the wording now says so. - The baseline refusal message leads with the reason and lists each unanalysable dependency with its source, instead of a package count that was often zero.
0.1.1 #
Security hardening. A second external review of the published 0.1.0 found five issues; every one was verified against the code before being fixed.
Fixed #
- A custom hosted registry was fetched from pub.dev.
source: hosteddoes not mean pub.dev, and a lockfile entry pointing at a private registry was downloaded from the public one. If a public package shared the name and version, capdrift analysed a different package and said nothing. Such entries are now reported as unsupported, with the registry named. - The archive is verified against the lockfile SHA-256. Previously we
fetched by name and version and analysed whatever came back. A mismatch is
now
INCOMPLETE, naming both hashes, because we cannot say what a package does when the bytes are not the ones that were pinned. - The download itself is bounded. 0.1.0 bounded decompression but not the
download, and
http.getbuffers a whole response before anything downstream can object. Downloads are streamed and aborted past 50 MB compressed. - The 30 second analysis limit is a real ceiling. It was checked between
files, so one pathological parse could run indefinitely while
SECURITY.mdpromised otherwise. Each package now analyses in a worker isolate that is killed when the deadline passes. SECURITY.mdcontradicted itself, still describingdart pub getas whatauditruns by default. It readspubspec.lockand runs nothing.auditreturnedALLOWon a tree it had not fully analysed. A git, path or custom-registry dependency was named as skipped and the run still passed, so the report contradicted its own exit code. Worse,--approvewould write a baseline over that partial tree, and every later drift check would measure against it. An unanalysable dependency now makes the auditREVIEW, orINCOMPLETEunderincomplete_analysis: fail, and--approverefuses. The report names each one with its real source rather than a generic label.
Changed #
cryptoadded as a direct dependency for SHA-256. It was already in the tree transitively, so no new download.
0.1.0 #
First release.
Added #
capdrift diff <package> <from> <to>: reports capability changes between two published versions. Nothing else in the Dart ecosystem does this.capdrift inspect <package> [version]: capability profile of one version.capdrift audit [directory]: resolves the full dependency tree and reports what every package in it can do, separating capabilities you chose from ones you inherited.capdrift audit --include-dev: auditsdev_dependenciesas well. They never ship to your users, but they run on your machine and in CI with your credentials in the environment, and the report labels themdev onlyso a reader can tell which findings reach production.- Scheduled corpus refresh workflow. The
verifyreference set is a snapshot, and an ageing set meansverifyquietly stops recognising near-misses of packages that became popular since. It regenerates monthly and opens a pull request only when the set actually changed. capdrift audit --approveandcapdrift audit --baseline: approve the current capability state once, then report only drift from it. Drift confined to test and example surfaces is shown but exits 0.capdrift verify <package>: four checks for a name an assistant may have invented. Existence, then length-scaled Damerau-Levenshtein against a verified reference set of 162 popular packages, then adoption, then age against adoption. Similarity alone never warns.- Seven capability families: network, process execution, filesystem, environment, native code, build hooks, platform.
- Platform capability detection: Android manifest permissions, iOS usage description keys, and the platforms a plugin ships native code for.
- Execution surface model. A finding in
hook/outranks the same finding intest/, and surface drives both report ordering and policy scoping. Anativesurface ranks abovelib, because a plugin's manifest reaches every consumer while the Kotlin or Swift beside it cannot be read. - Dependency resolution through
dart pub get --no-precompilein a temporary directory with a temporaryPUB_CACHE. It never writes into your project. - Stable capability fingerprints (
CF1:FAMILY:SURFACE:API:QUALIFIER), which are independent of file position so reformatting produces an empty diff. - Surface-scoped policy in
capdrift.yaml, with mandatory reasons on every rule. - Four exit codes: 0 ALLOW, 1 REVIEW, 2 BLOCK, 3 INCOMPLETE.
- Text and versioned JSON output.
Verified #
- Never-execute guarantee, enforced by a test that analyses a hostile fixture and asserts both that it did not run and that it was actually detected.
- Corpus A: 15 popular packages, 185 findings, 12 consumer facing.
- Corpus B: recall 8/8 against synthetic malicious fixtures, including a guard that an example app manifest never reports at native weight.
- Corpus C: no phantom changes across real version pairs.
Fixed before first release #
Found by an external technical review, and each one verified against the code rather than taken on faith before being fixed.
auditreported on a tree you may not have. It resolved fresh from the constraints instead of reading your committedpubspec.lock, so a project locked athttp 1.5.0was audited at1.6.0. It now reads the lockfile by default, with--resolvefor the deliberate "what would I get" question.- Decompression was unbounded. The size check ran on the compressed bytes and again only after the archive had fully expanded. Measured: 299 KB of input materialised 300 MB before the limit fired. Decompression is now aborted mid-stream by a bounded sink, and stops at the limit.
- The documented 30 second analysis budget was never enforced.
maxAnalysisTimewas declared and read nowhere. It is now a real deadline producingINCOMPLETE. - Baseline drift reported phantom removals. A package whose archive could not be fetched has an empty fingerprint set, which compared against a baseline read as every capability removed. Comparison now uses the same guard as approval.
SECURITY.mdoverstated the network claim.dart pub getperforms its own downloads, so "every network call goes throughPubClient" was false.
Known limits #
- Transitive packages carry no runtime-versus-dev attribution. A lockfile records that a package is transitive but not which root pulled it in, and the report states that rather than guessing.
- Path, git and SDK dependencies are skipped during resolution. They have no pub.dev archive to fetch, and the report says so rather than implying the tree was fully covered.