Skip to main content
Commands

vg scan --vulns

Detect known vulnerabilities in installed dependencies against the OSV database — online or air-gapped — with severity, CVSS, the fixing version, and git attribution.

Overview

vg scan --vulns adds known-vulnerability detection to a scan. It matches every installed dependency against the public OSV database and reports each advisory with its severity, CVSS score, and the version that fixes it.

Opt in: vulnerability detection is off by default — add --vulns, or run vg scan --full for drift, vulnerabilities, infrastructure misconfiguration findings (--iac), and a banned-dependency report together.


Infrastructure Misconfiguration (--iac)

vg scan --iac (also on with --full) evaluates Terraform, Kubernetes, Helm and Dockerfile facts against the Architecture module's iac-cis-v1 pack: public S3 buckets, security groups open to the internet, unencrypted volumes, privileged or root containers, host networking, wildcard RBAC, missing resource limits, Dockerfiles that run as root, and secret-shaped keys with literal values in ENV. Each finding carries a content-addressed id (the same tree gives the same id tomorrow), an OWASP Top 10:2025 category, a CWE and, where one applies, a CIS control, and lands in the text, JSON and SARIF output next to the drift findings.

vg scan --iac
vg scan --full --format sarif --out vibgrate.sarif --fail-on iac-finding

--fail-on iac-finding exits 2 on a high or critical finding; --fail-on iac-finding=medium lowers the bar, and a comma list combines gates (--fail-on error,iac-finding). The rules run locally inside the Architecture module; no source leaves the machine. Findings are evidence, not a certification, and the pack does not replace Checkov, Trivy or Snyk IaC — it covers a small, gold-gated rule set and joins each finding to the code map. A rule that cannot read an attribute (a variable, a dynamic block) abstains rather than reporting the resource as safe.


Usage

vg scan --vulns
vg scan --full

How Detection Works

Vibgrate reads the installed version of each dependency from your lockfile and queries OSV for advisories affecting that exact version. Each finding carries the advisory's own id. CVE and other aliases are recorded with it. The finding also carries a qualitative severity derived from the CVSS v3 vector, the affected package and version, and the fixing version when one is published.

Critical and high findings are emitted as errors and moderate as warnings, so --fail-on gates apply to vulnerabilities just as they do to drift.


JSON exploitability fields

vg scan --format json (and .vibgrate/scan_result.json) records advisories under extended.vulnerabilities.packages[].advisories. Match the package on ecosystem, package, and version. That is the same identity vg sbom writes as a package URL. The scan file has no CPE field. When the advisory data used for the scan already carries exploitability, each advisory may also include:

FieldTypeMeaning
epssnumber, 0–1FIRST EPSS probability of exploitation within 30 days
epssPercentilenumber, 0–1EPSS percentile for that probability
kevbooleanWhether that data marks the CVE in the CISA Known Exploited Vulnerabilities catalog

A missing value is omitted. It is never written as 0 or false to mean "unknown". A present 0 is a real EPSS score. Text and SARIF findings do not add these fields.

Offline and --package-manifest scans read the fields from the local bundle only — they do not contact an EPSS or KEV service. An online scan copies them from the OSV document when that document already includes them, and does not make a separate request. A package-manifest vulns entry accepts the same optional epss, epssPercentile, and kev fields.


Offline / Air-Gapped

vg scan has no separate advisory-file flag. Offline advisories are vulns entries in a package-version manifest. The file is JSON, or a ZIP whose root contains package-versions.json, manifest.json, or index.json (a ZIP is refused when it is over 256 MiB, declares more than 256 MiB uncompressed, or declares more than 10,000 entries; VG_MAX_ARCHIVE_BYTES and VG_MAX_ARCHIVE_ENTRIES raise those limits, and 0 disables that one limit). Each ecosystem (npm, nuget, pypi, maven, rubygems, swift, go, cargo, composer, pub, hex, docker, helm, terraform) is an object keyed by package name. NuGet names match case-insensitively. An entry's latest and versions feed drift. vulns entries need an id. ranges match from introduced up to, and not including, fixed.

vg scan --vulns --offline --package-manifest ./package-versions.json
{
  "npm": {
    "left-pad": {
      "latest": "1.3.0",
      "versions": ["1.3.0"],
      "vulns": [
        {
          "id": "GHSA-example",
          "severity": "high",
          "ranges": [{ "introduced": "0", "fixed": "1.3.1" }]
        }
      ]
    }
  }
}

A missing file prints error: Package manifest not found: <path>. Pass a readable JSON or ZIP package-version manifest to --package-manifest. A file that is not a package-version manifest prints error: Package manifest is not usable: <path>. Expected a JSON object of package versions, or a ZIP containing package-versions.json, manifest.json, or index.json. The exit code is 1. Stdout is empty, and no scan artifact is written.

--offline does not contact package registries, does not query OSV, and does not upload, including when --push is set or VIBGRATE_DSN is present. It does not call an EPSS or KEV service.

The same tree and the same manifest produce the same findings order and the same advisory ids. JSON lists every finding by path (location), then rule id, then advisory id, then package URL when the finding carries an ecosystem and a package name. Alias lists on a finding are sorted. The text report prints errors, then warnings, then notes, and within a level it uses that same path order. SARIF keeps drift findings first, then vulnerability results by ecosystem, package name, and installed version, and within a package by severity (critical, high, moderate, low, unknown) and advisory id. JSON records that id as details.advisoryId. Key order in the manifest file does not change the result.


Supported Ecosystems

Detection reads each project's lockfile for npm / pnpm / yarn, pip / poetry / pipenv, cargo, composer, bundler, pub, hex, NuGet, and Maven/Gradle. Go is matched from direct require lines in go.mod.

Go modules: pseudo-versions and +incompatible

Go modules pin an untagged commit as a pseudo-version (v0.0.0-20191109021931-daa7c04131f5, or v1.2.4-0.20210101120000-abcdefabcdef for a commit after tag v1.2.3). A major of 2 or higher whose module path has no /vN suffix is written with +incompatible (v2.0.0+incompatible). The local comparison below is what vg scan --vulns uses against a package-version manifest. --offline keeps that comparison on the machine: no module proxy, no OSV query, no registry token. Leave --offline off and the same recorded version is sent to the public OSV API, which applies OSV's own comparison.

Which version is compared

The scan reads direct require lines in go.mod.

  • A concrete v version that is valid semver after the leading v is removed is the recorded version. v1.2.3 is recorded as 1.2.3.
  • Build metadata is dropped. v2.0.0+incompatible is recorded as 2.0.0. The major stays 2.
  • A pseudo-version keeps its pre-release suffix. v0.0.0-20191109021931-daa7c04131f5 is recorded as 0.0.0-20191109021931-daa7c04131f5.
  • A bare module path, a range such as >=1.4.0, a line marked // indirect, and a token that is not a full semver (v1.2) are not matched against advisories.
  • go.sum is not the version source for this match.

A range is half-open: the version matches from introduced up to, and not including, fixed. introduced of 0 means from the beginning. Before that check, the recorded version is reduced to major.minor.patch. The pre-release suffix is not ordered against the tag. An explicit versions list matches the recorded string exactly, so the list entry still has the pre-release suffix and has no leading v.

Pseudo-versions and tagged releases

Require lineRecordedCompared asintroduced 1.0.0, fixed 1.2.4
v1.2.31.2.31.2.3matches
v1.2.3-0.20210101120000-abcdefabcdef1.2.3-0.20210101120000-abcdefabcdef1.2.3matches
v1.2.4-0.20210101120000-abcdefabcdef1.2.4-0.20210101120000-abcdefabcdef1.2.4does not match
v0.0.0-20191109021931-daa7c04131f50.0.0-20191109021931-daa7c04131f50.0.0does not match

In Go's own order, v1.2.4-0.<timestamp>-<commit> is a commit after v1.2.3 and before the v1.2.4 tag. Local matching compares it as 1.2.4, so an advisory fixed at 1.2.4 does not include it. That gap is real. A v0.0.0-<timestamp>-<commit> pseudo-version (no earlier tag for that major) compares as 0.0.0. It matches a range that starts at 0 and is fixed at 1.0.0. It does not match a range that starts at 1.0.0.

An explicit list entry of v0.0.0-20191109021931-daa7c04131f5 does not match the open finding. The entry 0.0.0-20191109021931-daa7c04131f5 does. The entry 0.0.0 does not: the list is exact, and the recorded string still has the pre-release suffix.

Exposure windows replay the require token as written in go.mod, including the leading v and a +incompatible suffix. Range checks reduce that token to the same major.minor.patch as the open finding. An explicit versions list matches the string it is given, so v2.0.0+incompatible can match a history replay and miss the open finding (2.0.0), or the other way around.

+incompatible

The suffix is dropped before the comparison. v2.0.0+incompatible matches a range from 2.0.0 up to 2.1.0. It does not match a range that only covers 1.x. An explicit list entry of v2.0.0+incompatible does not match the open finding. The entry 2.0.0 does.

replace and exclude

replace and exclude are not applied before the match.

  • The module path and version on the require line are what get matched. A replace to a fork, another version, or a local directory does not change that target. A replace with no require adds nothing.
  • An exclude line does not add a module, and it does not drop a version that is also required.

The code graph records those same require paths, including lines marked // indirect, and skips replace and exclude. The graph does not store the version. Vulnerability matching still skips // indirect.

Check it locally

The two files are checked in at examples/go-pseudo-versions/. They use example.com module paths and example advisory ids. Copy them into an empty directory if you are reading this from an installed package.

go.mod:

module example.com/demo

go 1.22

require (
	example.com/tagged v1.2.3
	example.com/pseudo-next v1.2.4-0.20210101120000-abcdefabcdef
	example.com/pseudo-base v0.0.0-20191109021931-daa7c04131f5
	example.com/oldmajor v2.0.0+incompatible
	example.com/replaced-mod v1.0.0
	example.com/indirect-mod v1.2.3 // indirect
)

exclude example.com/excluded-mod v1.2.3

replace example.com/replaced-mod => ./local

replace example.com/fork-only => example.com/fork v1.2.3

package-versions.json:

{
  "go": {
    "example.com/tagged": {
      "latest": "1.2.4",
      "vulns": [
        {
          "id": "GHSA-example-tagged",
          "severity": "high",
          "ranges": [{ "introduced": "1.0.0", "fixed": "1.2.4" }]
        }
      ]
    },
    "example.com/pseudo-next": {
      "latest": "1.2.4",
      "vulns": [
        {
          "id": "GHSA-example-pseudo-next",
          "severity": "high",
          "ranges": [{ "introduced": "1.0.0", "fixed": "1.2.4" }]
        }
      ]
    },
    "example.com/pseudo-base": {
      "latest": "1.0.0",
      "vulns": [
        {
          "id": "GHSA-example-pseudo-base",
          "severity": "moderate",
          "ranges": [{ "introduced": "0", "fixed": "1.0.0" }]
        },
        {
          "id": "GHSA-example-pseudo-explicit-v",
          "severity": "low",
          "versions": ["v0.0.0-20191109021931-daa7c04131f5"]
        },
        {
          "id": "GHSA-example-pseudo-explicit-clean",
          "severity": "low",
          "versions": ["0.0.0-20191109021931-daa7c04131f5"]
        }
      ]
    },
    "example.com/oldmajor": {
      "latest": "2.1.0",
      "vulns": [
        {
          "id": "GHSA-example-incompatible-range",
          "severity": "high",
          "ranges": [{ "introduced": "2.0.0", "fixed": "2.1.0" }]
        },
        {
          "id": "GHSA-example-incompatible-v1",
          "severity": "high",
          "ranges": [{ "introduced": "1.0.0", "fixed": "1.9.0" }]
        },
        {
          "id": "GHSA-example-incompatible-explicit",
          "severity": "low",
          "versions": ["v2.0.0+incompatible"]
        }
      ]
    },
    "example.com/replaced-mod": {
      "latest": "1.0.1",
      "vulns": [
        {
          "id": "GHSA-example-replaced",
          "severity": "moderate",
          "ranges": [{ "introduced": "0", "fixed": "1.0.1" }]
        }
      ]
    },
    "example.com/indirect-mod": {
      "latest": "1.2.4",
      "vulns": [
        {
          "id": "GHSA-example-indirect",
          "severity": "high",
          "ranges": [{ "introduced": "0", "fixed": "9.0.0" }]
        }
      ]
    },
    "example.com/excluded-mod": {
      "latest": "1.2.3",
      "vulns": [
        {
          "id": "GHSA-example-excluded",
          "severity": "high",
          "ranges": [{ "introduced": "0" }]
        }
      ]
    }
  }
}

From that directory:

vg scan --vulns --offline --package-manifest package-versions.json --format json --out go-vulns.json

findings[].details.advisoryId in go-vulns.json (the same ids are under extended.vulnerabilities.packages[].advisories):

ModuleAdvisory idResult
example.com/taggedGHSA-example-taggedreported (1.2.3 is below 1.2.4)
example.com/pseudo-nextGHSA-example-pseudo-nextabsent (compared as 1.2.4)
example.com/pseudo-baseGHSA-example-pseudo-basereported (compared as 0.0.0)
example.com/pseudo-baseGHSA-example-pseudo-explicit-vabsent (the list has the leading v)
example.com/pseudo-baseGHSA-example-pseudo-explicit-cleanreported
example.com/oldmajorGHSA-example-incompatible-rangereported (2.0.0)
example.com/oldmajorGHSA-example-incompatible-v1absent (that range is 1.x)
example.com/oldmajorGHSA-example-incompatible-explicitabsent (the list is v2.0.0+incompatible)
example.com/replaced-modGHSA-example-replacedreported (the require version; the replace is ignored)
example.com/indirect-modGHSA-example-indirectabsent
example.com/excluded-modGHSA-example-excludedabsent
example.com/fork-only—absent (replace only, no require)

timestamp and durationMs change between runs. The advisory ids and their order do not: ecosystem, then package name, then recorded version, then severity (critical, high, moderate, low, unknown), then advisory id. The scan also writes .vibgrate/scan_result.json beside the go.mod.

If the match looks wrong

A surprise match, or a missing one, is a bug report. Include a local repro:

  • the go.mod require line
  • the advisory id and its introduced / fixed bounds, or the explicit versions list
  • the command above, run with --offline, so the repro needs no network and no registry credential
  • the advisory ids from the JSON, and which row in the table you expected to differ
  • vg --version

Leave out tokens, .netrc contents, and private module-proxy URLs.


Exposure Attribution

In a git repository, each finding is attributed to the commit, author, and date that introduced the vulnerable version, with the days you have been exposed. Those windows roll up into CRA remediation metrics. See vg why.


In CI

vg scan --vulns --format sarif --out vibgrate.sarif --fail-on error

Upload the SARIF with the GitHub upload-sarif action to surface vulnerabilities in the Security tab.

Several ids on one advisory

One advisory often has its own id plus aliases (a GHSA, a CVE, another OSV id). SARIF writes one result per package and advisory. Aliases stay on that result. Two advisories stay two results when one lists the other as an alias, so a near-duplicate code-scanning alert is expected in that case. Match alerts on properties.advisoryId.

FieldWhat it holds
ruleIdvibgrate/vulnerability on every vulnerability result
properties.advisoryIdThe advisory's own id (the primary id)
properties.aliasesThe alias list, in the order the advisory supplied
message.textThe advisory id. The first CVE- alias is added in parentheses when it is not already that id

properties is a copy of the finding details. Drift findings come first. Vulnerability results then follow package order (ecosystem, name, version) and, within a package, severity from critical down to unknown, then advisory id. The same advisory set produces the same SARIF.

On GitHub, the maintained vibgrate/cli Action does the scan, the PR gate, and the SARIF upload in one step (the workflow needs permissions: security-events: write):

- uses: actions/checkout@v4
  with:
    fetch-depth: 0
- uses: vibgrate/cli@v1
  with:
    vulns: true
    fail-on: error
    upload-sarif: true
    category: vibgrate-vulns

Related

  • vg scan — the base drift scan
  • vg why — who introduced a dependency or vulnerable version

Example use cases

Expand any use case to watch a live replay of the real @vibgrate/cli running against one of our test repositories. Nothing executes in your browser — these are recordings of actual runs, with the scan time shown as of now.

vg scan --vulns --offline --package-manifest package-versions.json

Related Documentation

Related Help Articles

Vibgrate CLI

See a real scan run

A replay of the actual CLI running against our test repositories — live progress, real findings, a genuine DriftScore. Nothing executes in your browser.

Replay
demo@vibgrate — bash
❯ npx @vibgrate/cli scan
 
╭──────────────────────────────────────────╮
│ Vibgrate Drift Report │
╰──────────────────────────────────────────╯
 
── node-turborepo (node) .
Runtime: >=18.0.0 (6 majors behind)
Frameworks:
Turbo: 1.13.4 → 2.11.7 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
1 current 1 1-behind 3 2+ behind 1 unknown
 
── @repo/admin (node) apps/admin
Frameworks:
TanStack Query: 5.104.1 → 5.104.1 (current)
React: 18.3.1 → 19.3.0 (1 behind)
React DOM: 18.3.1 → 19.3.0 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vite: 5.4.21 → 8.3.3 (3 behind)
Dependencies:
3 current 9 1-behind 3 2+ behind 4 unknown
 
── @repo/api (node) apps/api
Frameworks:
Express: 4.22.3 → 5.2.1 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vitest: 1.6.1 → 5.0.3 (4 behind)
Dependencies:
7 current 4 1-behind 4 2+ behind 4 unknown
 
── @repo/web (node) apps/web
Frameworks:
Next.js: 14.2.35 → 16.4.0 (2 behind)
React: 18.3.1 → 19.3.0 (1 behind)
React DOM: 18.3.1 → 19.3.0 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
2 current 6 1-behind 3 2+ behind 5 unknown
 
── @repo/config (node) packages/config
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
2 current 2 1-behind 5 2+ behind 0 unknown
 
── @repo/database (node) packages/database
Frameworks:
Prisma: 5.22.0 → 7.10.0 (2 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
1 current 0 1-behind 3 2+ behind 1 unknown
 
── @repo/types (node) packages/types
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Dependencies:
0 current 0 1-behind 1 2+ behind 1 unknown
 
── @repo/ui (node) packages/ui
Frameworks:
React: 18.3.1 → 19.3.0 (1 behind)
TypeScript: 5.9.3 → 7.0.2 (2 behind)
React: 18.3.1 → 19.3.0 (1 behind)
Dependencies:
1 current 4 1-behind 1 2+ behind 1 unknown
 
── @repo/utils (node) packages/utils
Frameworks:
TypeScript: 5.9.3 → 7.0.2 (2 behind)
Vitest: 1.6.1 → 5.0.3 (4 behind)
Dependencies:
0 current 1 1-behind 2 2+ behind 1 unknown
 
Tech Stack
Frontend: React, React DOM
Meta-frameworks: Next.js
Bundlers: tsx, Turbo, Vite
CSS / UI: Autoprefixer, PostCSS, Tailwind CSS
Backend: Express
ORM / Database: Prisma, Prisma Client
Testing: Vitest
Lint & Format: ESLint, ESLint Prettier, ESLint React, Prettier, typescript-eslint
 
Services & Integrations
Auth: JWT 9.0.3
Databases: Prisma 5.22.0
 
TypeScript
v5.3.3 · strict ✔ · MIXED · target: ES2022
 
Build & Deploy
Package Managers: pnpm
Monorepo: npm-workspaces, pnpm-workspaces, turbo
 
Product Purpose Signals
Frameworks: react, nextjs
Evidence: 177
Top Signals:
- [heading] Dashboard (apps/admin/src/pages/Dashboard.tsx)
- [title] Revenue Overview (apps/admin/src/pages/Dashboard.tsx)
- [copy] workspace:* (packages/ui/package.json)
- [copy] ./dist (packages/ui/tsconfig.json)
- [copy] ./src/index.ts (packages/ui/package.json)
- [copy] @repo/config/tsconfig-base.json (packages/ui/tsconfig.json)
- [copy] @repo/ui (packages/ui/package.json)
- [copy] #3b82f6 (apps/admin/src/pages/Dashboard.tsx)
Unknowns:
- No pricing or billing evidence found.
- No integrations/connectors evidence found.
- No route structure evidence found.
 
Security Posture
Lockfile ✖ · .env ✔ · node_modules ✔
 
Platform
Native modules: turbo
 
Code Quality
Files: 36 · Functions: 183 · Avg complexity: 2.62 · Avg length: 21.13 lines
Max nesting: 2 · Circular deps: 0 · Dead code: 0%
God files: apps/admin/src/pages/Products (448 lines)
 
Database Schema
postgresql · 8 models · 1 enum
Models: Address, CartItem, Category, Order, OrderItem (+3 more)
 
Findings (16 errors, 11 warnings)
✖ Node.js runtime ">=18.0.0" reached end-of-life on 2025-04-30 (latest: 24.0.0).
vibgrate/runtime-eol in .
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in .
✖ 60% of dependencies are 2+ major versions behind in node-turborepo.
vibgrate/dependency-rot in .
✖ @types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.6.4).
vibgrate/dependency-major-lag in .
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/admin
✖ Vite is 3 major versions behind (current: 5.4.21, latest: 8.3.3).
vibgrate/framework-major-lag in apps/admin
✖ vite is 3 major versions behind (spec: ^5.0.12, latest: 8.3.3).
vibgrate/dependency-major-lag in apps/admin
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/api
✖ Vitest is 4 major versions behind (current: 1.6.1, latest: 5.0.3).
vibgrate/framework-major-lag in apps/api
✖ @types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.6.4).
vibgrate/dependency-major-lag in apps/api
✖ vitest is 4 major versions behind (spec: ^1.2.1, latest: 5.0.3).
vibgrate/dependency-major-lag in apps/api
⚠ Next.js is 2 major versions behind (current: 14.2.35, latest: 16.4.0).
vibgrate/framework-major-lag in apps/web
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in apps/web
✖ @types/node is 6 major versions behind (spec: ^20.11.0, latest: 26.6.4).
vibgrate/dependency-major-lag in apps/web
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/config
✖ 56% of dependencies are 2+ major versions behind in @repo/config.
vibgrate/dependency-rot in packages/config
✖ eslint-plugin-react-hooks is 3 major versions behind (spec: ^4.6.0, latest: 7.1.1).
vibgrate/dependency-major-lag in packages/config
⚠ Prisma is 2 major versions behind (current: 5.22.0, latest: 7.10.0).
vibgrate/framework-major-lag in packages/database
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/database
✖ 75% of dependencies are 2+ major versions behind in @repo/database.
vibgrate/dependency-rot in packages/database
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/types
✖ 100% of dependencies are 2+ major versions behind in @repo/types.
vibgrate/dependency-rot in packages/types
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/ui
⚠ TypeScript is 2 major versions behind (current: 5.9.3, latest: 7.0.2).
vibgrate/framework-major-lag in packages/utils
✖ Vitest is 4 major versions behind (current: 1.6.1, latest: 5.0.3).
vibgrate/framework-major-lag in packages/utils
✖ 67% of dependencies are 2+ major versions behind in @repo/utils.
vibgrate/dependency-rot in packages/utils
✖ vitest is 4 major versions behind (spec: ^1.2.1, latest: 5.0.3).
vibgrate/dependency-major-lag in packages/utils
 
╭──────────────────────────────────────────╮
│ Top Priority Actions │
╰──────────────────────────────────────────╯
 
1. Upgrade EOL runtime in node-turborepo
End-of-life runtimes no longer receive security patches and block ecosystem upgrades.
./.
>=18.0.0 → 24.0.0 (6 majors behind)
Impact: −10 drift points (runtime & EOL)
 
2. Fix security posture: no lockfile found
Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.
./
Missing: package-lock.json, pnpm-lock.yaml, or yarn.lock
 
3. Upgrade Vitest 1.6.1 → 5.0.3 in @repo/api (+2 more)
4 major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.
./apps/api
Vitest: 1.6.1 → 5.0.3 (4 majors behind)
./packages/utils
Vitest: 1.6.1 → 5.0.3 (4 majors behind)
./apps/admin
Vite: 5.4.21 → 8.3.3 (3 majors behind)
Impact: −5–15 drift points
 
4. Reduce dependency rot in @repo/types (100% severely outdated)
1 of 1 dependencies are 2+ majors behind. Run `npm outdated` and prioritise packages with known CVEs or breaking API changes.
./packages/types
typescript: 5.9.3 → 7.0.2 (2 majors behind)
Impact: −5–10 drift points
 
5. Reduce dependency rot in @repo/database (75% severely outdated)
3 of 4 dependencies are 2+ majors behind. Run `npm outdated` and prioritise packages with known CVEs or breaking API changes.
./packages/database
@prisma/client: 5.22.0 → 7.10.0 (2 majors behind)
prisma: 5.22.0 → 7.10.0 (2 majors behind)
typescript: 5.9.3 → 7.0.2 (2 majors behind)
Impact: −5–10 drift points
 
╭──────────────────────────────────────────╮
│ Architecture Layers │
╰──────────────────────────────────────────╯
 
Archetype: nextjs (80% confidence)
Files classified: 24 (11 unclassified)
Folders classified: 8
apps/admin/src presentation 100% 4 files
apps/admin/src/pages presentation 100% 2 files
apps/api/src/middleware middleware 100% 2 files
apps/api/src/routes routing 100% 2 files
apps/web/src/app presentation 100% 4 files
apps/web/src/app/products presentation 100% 2 files
apps/web/src/app/products/[id] presentation 100% 1 file
packages/ui/src presentation 100% 6 files
Unclassified source (sample): 11
 
presentation 15 files drift ████████████████████ 100 risk high
routing 4 files drift ████████████████████ 100 risk high
middleware 2 files drift ███████▍░░░░░░░░░░░░ 37 risk moderate
config 2 files drift ░░░░░░░░░░░░░░░░░░░░ 0 risk none
shared 1 file drift ████████████████████ 100 risk high
 
╭──────────────────────────────────────────╮
│ DriftScore Summary │
╰──────────────────────────────────────────╯
 
DriftScore: 70/100
Risk Level: HIGH
Projects: 9
Classified: 8 nano · 1 micro · 0 small · 0 standard
Billable: 0.42 · 9 detected → 0.42 billable projects (micro-project pricing)
0.1 micro · 0.32 nano
These fractions add up across repositories, then round down to whole billable projects.
 
Score Breakdown
Runtime: ████████████████████ 100
Frameworks: ███████████▊░░░░░░░░ 59
Dependencies: ██████▌░░░░░░░░░░░░░ 33
EOL Risk: ████████████████████ 100
 
Scanned at 2026-10-07T12:34:03.236Z · 6.6s · 286 files scanned · 56 workspace files · 27 dirs
❯
Press Run to start.