Deterministic, zero-LLM scanner for dying third-party API integrations: deprecated packages (CVE/EOL) and retired vendor APIs (sunsets), down to file:line.
Project description
Drift Detector
Know before it breaks.
Drift Detector watches your codebase for third-party integrations that are about to break — and tells you which file and line is affected, with the date it breaks, before it does.
It catches three kinds of rot:
- Retired vendor APIs — a service you call (eBay, Amazon, Shopify, …) is shutting down an
API your code still uses. Example: "eBay's
GetCategoryFeatures— called atEbayCategoryFieldsFeature.php:72— is retired as of 2026‑06‑04; migrate to the Taxonomy API." No SBOM or CVE scanner sees this — it's the reason the tool exists. - End-of-life software — a runtime or framework version the maker no longer supports/patches.
- Known security holes — public vulnerabilities in the packages you depend on.
It runs on a schedule, by itself. Each run it files a ticket for every problem it finds — in the repo that has it, assigned to the right person — and publishes an interactive dashboard. Nothing to babysit; every finding is dated, sourced, and self-checked, and where it can't see, it says so instead of reporting a false all-clear.
The jargon, once (plain terms)
| Term | In plain English |
|---|---|
| vendor-API sunset | A third-party service is retiring an API your code calls. |
| EOL (end-of-life) | A software version the maker stopped supporting/patching. |
| CVE | A publicly-catalogued security hole in a software package. |
| OSV | The public database of those security holes (osv.dev) the tool checks against. |
| SBOM | A "bill of materials" — the list of every component your code depends on (standard CycloneDX/SPDX files, for compliance). |
| SARIF | A standard file format for code-scan results (GitHub code-scanning and VS Code read it). |
| the Cockpit | The interactive dashboard the tool publishes each run. |
- How it runs: cloud compute on a schedule — no server to operate.
- What it's made of: Python (stdlib only) + the ast-grep code-search engine. Zero AI/LLM tokens in the scan.
- Trustworthy by construction: same inputs → identical output; every report is machine-verified before it's shown.
Where it's headed: docs/ROADMAP.md.
How it works — the one model
You don't operate it hands-on. You configure it once and it runs itself on a schedule.
flowchart TD
SCHED["⏰ Schedule — GitHub Actions<br/>(free cloud compute, no server)"] --> RUN["drift-detector: one run"]
RUN <-->|"read config + state,<br/>write state back"| OPS[("drift-ops — private Git repo<br/>config · fleet list · saved state")]
RUN -->|"clone + scan"| FLEET["your fleet of repos"]
RUN -->|"one ticket per problem"| ISSUES["issues, in each repo"]
RUN -->|"publish"| COCKPIT["the Cockpit — dashboard"]
Two moving parts, clear roles:
- The compute (GitHub Actions) is just the muscle — it spins up, runs one scan, and disappears. There is no always-on server.
- The
drift-opsrepo (on your GitLab) is the brain. This is the "state repo" that stores everything the tool remembers between runs: the config (drift.yml), the fleet (which repos to scan), the saved state (last scan's results + the learned catalog), and it's where the Cockpit is published from. Each run reads from it and writes updated state back to it.
So the mental model is: a scheduled robot that reads its instructions and memory from one private Git repo, scans your other repos, and drops the results (tickets + dashboard) where your team already works. It is not a chat tool or a thing you invoke per-question.
Configure it — drift.yml
drift.yml lives in the drift-ops repo and is the one control surface — everything the tool
does is set here (a reviewed commit, never a secret in the file):
version: 1
# WHICH repos to scan — the "fleet". All https URLs on one host.
# A GROUP url scans every repo under it.
fleet:
- https://git.example.com/team/service-a
- https://git.example.com/team/service-b
- https://git.example.com/platform # a whole group
delivery:
mode: create # dry-run (print the plan, write nothing) · create (file issues) · off
granularity: per-problem # how findings become tickets — see below
devops: # DevOps tickets = package security + end-of-life
assignee: ops-bot # assigned to this account (required when filing issues)
developer: # Developer tickets = retiring vendor APIs + framework EOL
fallbackAssignee: lead # auto-assigned to the repo's owner; this is the fallback
# Optional. `auth` holds env-var NAMES (never the secret itself); omit to use one GITLAB_TOKEN.
# notify: { gchat: GCHAT_WEBHOOK }
mode—dry-runprints what it would file (safe to try);createactually files/updates issues;offskips delivery.granularity— how many tickets a repo's findings become:comprehensive— 2 tickets per repo (one DevOps, one Developer), each listing everything.per-vendor— one ticket per vendor per repo (all dying eBay calls together, etc.).per-problem— one ticket per finding (every dying API / package / EOL gets its own).
devops/developer— the two audiences. Package-security & runtime-EOL tickets go to the DevOps account; retiring-vendor-API & framework-EOL tickets go to the repo's owner (resolved automatically), falling back tofallbackAssignee.
Re-runs update tickets in place (never duplicates); a fixed problem closes its own ticket.
Run it on your own code
Point it at any folder — a single project or a directory of many. No token, no config, no server; the only network call is the audit step (to public CVE/EOL databases).
Install-free — with uv (or pipx), no clone needed:
uvx --from git+https://github.com/laxit-patel/drift-detector drift-scan \
run --root ~/code/my-project --state /tmp/out --now $(date +%F)
The scan engine (ast-grep) comes along as a pinned dependency — nothing else to install.
Or clone and run — bin/drift-scan provisions its own Python venv + the engine on first run:
git clone https://github.com/laxit-patel/drift-detector && cd drift-detector
./bin/drift-scan run --root ~/code/my-project --state /tmp/out --now $(date +%F)
./bin/drift-scan verify --state /tmp/out
Either way, open /tmp/out/dashboard.html in a browser (or read drift.md in the terminal). Exit
codes make it CI-friendly: 0 ok · 2 error · 3 found problems · 4 couldn't scan / verify.
Architecture
The pipeline is offline and deterministic — same inputs produce byte-identical output, and it spends zero AI tokens. Only the "audit" step reaches the network (to public databases).
flowchart LR
SCAN["① scan<br/>find every integration<br/>(code + manifests)"] --> AUDIT["② audit<br/>check each against<br/>public databases"]
AUDIT --> REPORT["③ one report<br/>drift.json"]
REPORT --> MD["drift.md"]
REPORT --> DASH["the Cockpit"]
REPORT --> ISS["GitLab issues"]
① scan — the ast-grep engine (a pinned static binary) finds the
third-party API calls in your source down to file:line, and manifest/lockfile parsing finds your
packages, runtimes, and frameworks. Output: inventory.json (the map of what you use).
② audit — each thing found is checked against three sources, and classified act-now or review, always with a cited link:
- OSV.dev → known security holes (CVEs) per package version;
- endoflife.date → end-of-life runtimes/frameworks;
- the vendor-sunset catalog (
agent/vendor_sunsets.yaml) → a curated, dated, sourced list of retiring vendor APIs, matched against the API calls found in step ①. This is the layer no other scanner has.
The DevOps view — package security holes (OSV CVEs) and end-of-life findings, grouped by rule, each with the exact upgrade.
③ one report — everything becomes drift.json, the single source of truth. The
human-readable drift.md, the Cockpit dashboard, the SBOM, and the filed issues are all
projections of it.
Why you can trust it — verify
drift-scan verify re-derives every projection from drift.json and fails if any disagrees.
A green verify is the only claim the tool makes that a report is correct — nobody eyeballs the
dashboard for accuracy. It checks the dashboard's embedded data matches the report exactly, that
every dashboard tile's number equals the rows it filters to, and that nothing dated is silently
dropped. Two guarantees underpin everything:
- "Cannot see" is never "clean." If it can't read a repo (no access, unknown language), it says so and exits non-zero — never a false green checkmark.
- Never invent a date. Every retirement carries a source link fetched that run; undated ones say "no date announced." Nothing enters the catalog without passing a review gate.
Detection — what it can see
Packages and security holes are table stakes. The differentiator is the vendor-API layer: it knows which third-party APIs your code calls and when the vendor kills them.
flowchart LR
CODE["your code + manifests"] --> PKG["packages · runtimes"]
CODE --> API["API calls (file:line)"]
PKG --> SEC["security holes + end-of-life"]
API --> SUN["retiring vendor APIs (dated)"]
SEC --> FIND["findings"]
SUN --> FIND
It keeps up with new integration shapes through a reviewed adaptation mechanism: a shape it's
taught (as catalog data, never code) it detects deterministically forever after. It never adapts on
its own — every new shape passes the absorb review gate first (sourced dates, no false endpoints,
residue must shrink), so the tool learns without ever admitting an unverified finding.
Which third-party APIs your code calls, by vendor — the layer no SBOM or CVE scanner has (a 33-repo fleet scan).
Delivery & the Cockpit
Findings roll up into ranked jobs (thirty security holes in one package = one upgrade job, not thirty tickets), split by audience, and filed in each repo's own tracker — idempotently (re-runs update in place; fixed problems auto-close). Each ticket carries an emoji-coded title (🚨 past-due · ⏳ upcoming · ☣️ end-of-life · 🛡️ security), a 📊 link to the Cockpit, and a 🤖 Open in Claude link that pre-loads the finding so whoever picks it up gets full context.
The Cockpit is the interactive dashboard — clickable tiles, a per-operation retirement
timeline, and the drill-down list, published as a static site from the drift-ops repo.
The cockpit — ownership, security, and integration tiles over the Retirement Timeline: every vendor-API sunset on a date axis, past-due left of today.
Outputs
| File | What it is |
|---|---|
inventory.json |
The map of everything your repos use (packages, runtimes, API calls at file:line). |
audit.json |
The findings + ranked jobs + what changed since last run. |
drift.json |
The one report everything else is derived from (and verify-checked against). |
dashboard.html |
The Cockpit — the interactive dashboard. |
drift.md |
The plain-text version of the report. |
sbom.json / *.sarif |
Standard SBOM (CycloneDX/SPDX) + SARIF exports for compliance/other tools. |
What's built today
| Capability | Status |
|---|---|
Deterministic scan → inventory of packages, runtimes & API calls (file:line) |
✅ |
| Security-hole (OSV) + end-of-life (endoflife.date) checks | ✅ |
| Retiring-vendor-API detection + the curated, dated, sourced catalog | ✅ |
Reviewed adaptation (idiom families + the absorb intake gate) |
✅ |
drift.json + verify (the trust contract) |
✅ |
| SBOM (CycloneDX/SPDX) + SARIF exports | ✅ |
| The Cockpit dashboard (tiles, retirement timeline, deep-links) | ✅ |
| Scheduled delivery: per-repo issues, 3 granularities, idempotent, "Open in Claude" | ✅ |
Where it's headed next: docs/ROADMAP.md.
Repo map
bin/drift-scan self-provisioning runner (fetches the pinned scan engine + a venv)
agent/ the pipeline: scan · audit · run · deliver · absorb (catalog intake)
agent/lib/ the pieces — engine, endpoint detection, OSV/EOL, ranking, delivery, verify, dashboard, config
agent/*.yaml the reviewed catalogs — vendors · vendor_sunsets · idioms · frameworks
agent/assets/ the Cockpit — dashboard template + app + vendored runtime
.github/workflows/ scan.yml (the scheduled run)
docs/ ROADMAP.md · drift-absorb.md (catalog-intake doctrine) · EVAL.md · schema/ (the contract)
Working conventions for contributors: CLAUDE.md.
Limits (honest scope)
- API version is read from the URL on the matched line; it's
Nonewhen a repo builds the URL from a base constant elsewhere (would need dataflow — out of scope). - Only directly-declared dependencies are audited (not transitive ones pulled in by lockfiles).
- Security/EOL sources are OSV + endoflife.date; the vendor-sunset catalog is curated — you extend it (each entry cites a source).
- The dashboard shows the latest run; week-over-week history is a future layer (see the roadmap).
Every finding is dated and sourced; every report is verify-certified; where it's blind, it says so.
Know before it breaks.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drift_detector_scan-1.1.0.tar.gz.
File metadata
- Download URL: drift_detector_scan-1.1.0.tar.gz
- Upload date:
- Size: 394.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4384d175deba2607069f4d8a7b2ab0c635ef9fa3767e50b74ea212b2573deeb3
|
|
| MD5 |
cbc1aced290f7a28d906ef0893e40656
|
|
| BLAKE2b-256 |
45f37154199f01c657f5b83029bcfea364f657417338db902510b37dcdea47cf
|
Provenance
The following attestation bundles were made for drift_detector_scan-1.1.0.tar.gz:
Publisher:
publish.yml on laxit-patel/drift-detector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drift_detector_scan-1.1.0.tar.gz -
Subject digest:
4384d175deba2607069f4d8a7b2ab0c635ef9fa3767e50b74ea212b2573deeb3 - Sigstore transparency entry: 2345035655
- Sigstore integration time:
-
Permalink:
laxit-patel/drift-detector@64fe7cb16c80a8d6b1ce405c30d92efcc046788e -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/laxit-patel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@64fe7cb16c80a8d6b1ce405c30d92efcc046788e -
Trigger Event:
push
-
Statement type:
File details
Details for the file drift_detector_scan-1.1.0-py3-none-any.whl.
File metadata
- Download URL: drift_detector_scan-1.1.0-py3-none-any.whl
- Upload date:
- Size: 292.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13adaa2331a40890a688e3c6ca41c8a2ea29e0d4408cb86f434eaa15d3af9a4d
|
|
| MD5 |
52be99d9426a11bc58a44597f557bf81
|
|
| BLAKE2b-256 |
25a7f2ee9a9c8d608e34773f711c4ab25f219a9745eaa79acc33434125d72101
|
Provenance
The following attestation bundles were made for drift_detector_scan-1.1.0-py3-none-any.whl:
Publisher:
publish.yml on laxit-patel/drift-detector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drift_detector_scan-1.1.0-py3-none-any.whl -
Subject digest:
13adaa2331a40890a688e3c6ca41c8a2ea29e0d4408cb86f434eaa15d3af9a4d - Sigstore transparency entry: 2345035700
- Sigstore integration time:
-
Permalink:
laxit-patel/drift-detector@64fe7cb16c80a8d6b1ce405c30d92efcc046788e -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/laxit-patel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@64fe7cb16c80a8d6b1ce405c30d92efcc046788e -
Trigger Event:
push
-
Statement type: