---
title: "Dependency checks"
description: "Catch made-up, brand-new and look-alike packages, known advisories, denied licences and unreviewed install scripts in your dependencies when you run mainsdk check, with a 24-hour cache and a clear notice offline."
section: "interfaces"
status: "Building for 0.5 · ships as a beta API"
spec: "dependency-check"
canonical: "https://mainsdk.dev/docs/interfaces/deps"
updated: "2026-10-28"
---

# Dependency checks

> Catch made-up, brand-new and look-alike packages, known advisories, denied licences and unreviewed install scripts in your dependencies when you run mainsdk check, with a 24-hour cache and a clear notice offline.

Status: Building for 0.5 · ships as a beta API · Canonical: https://mainsdk.dev/docs/interfaces/deps · Updated: 2026-10-28

The `deps` gate of `mainsdk check` flags made-up, brand-new and look-alike packages, advisories, denied licences and unreviewed install scripts in your dependencies. It runs in the CLI only and is free.

## At a glance

- Status: Building for 0.5 · ships as a beta API
- Phase: 1 (Core, public beta)
- Milestone: 0.5 Pilot ready
- Plan: free in your app (Apache-2.0)
- Spec: https://github.com/hubxero/mainsdk/blob/develop/docs/specs/dependency-check.md
- Category: Security and privacy

Calls:

- `mainsdk check`

In main.yaml:

```yaml
deps:
  min-age: 7d                    # a direct dependency first published more recently is reported
  licences: { deny: [AGPL-3.0-only, SSPL-1.0], allow: [], unknown: warn }
  advisories:
    fail-on: high                # critical | high | moderate | low | none
    ignore:
      - { id: GHSA-xxxx-xxxx-xxxx, until: 2027-01-31, reason: "Not reachable: only used in tests" }
  scripts: { allow: [esbuild, sharp] }
  offline: info                  # info | fail
```

Guarantees, whatever the driver:

- Runs only in the CLI; nothing loads in the app or its request path
- Reads pnpm, npm, yarn (classic and Berry) and bun lockfiles into one component list
- A registry token goes only to the host it is configured for, never to another registry or to OSV
- Offline is an info finding, not a failure, and answers are cached for 24 hours


## Drivers

Pick one in main.yaml with `deps.driver`.

| Driver | Id in main.yaml | Default | Note |
| --- | --- | --- | --- |
| OSV.dev advisories | `osv` | yes |  |
| npm registry and private registries from .npmrc | `npm-registry` |  |  |

## Codes

| Code | Where | Meaning |
| --- | --- | --- |
| `MAIN-DEPS-0001` | check | A direct dependency isn't on its registry: possibly a name a model made up, and anyone may register it (high) |
| `MAIN-DEPS-0002` | check | A direct dependency's first version is younger than `deps.min-age` (high) |
| `MAIN-DEPS-0003` | check | A direct dependency's name is one edit away from a popular package it isn't (medium) |
| `MAIN-DEPS-0004` | check | A resolved package has a known advisory; at or above `advisories.fail-on` it fails, below it warns |
| `MAIN-DEPS-0005` | check | A package's licence is denied, outside a non-empty allow list, or unknown with `unknown: fail` |
| `MAIN-DEPS-0006` | check | A package runs an install script (`preinstall`, `install`, `postinstall`) and isn't in `deps.scripts.allow` (warning) |
| `MAIN-DEPS-0007` | check | Advisories and package ages weren't checked because the network was unavailable (info, or a failure with `offline: fail`) |
| `MAIN-DEPS-0008` | check | An advisory ignore entry has expired, or has no `until` or `reason` |

## Features

| Feature | Phase | Milestone | Spec ticket |
| --- | --- | --- | --- |
| Lockfile readers for pnpm, npm, yarn and bun | 1 | 0.5 | `dependency-check#lockfiles` |
| Unknown, brand-new and look-alike direct dependencies | 1 | 0.5 | `dependency-check#registry-rules` |
| Advisories from OSV with severity, fixed version and path | 1 | 0.5 | `dependency-check#advisories` |
| Licence policy over SPDX expressions | 1 | 0.5 | `dependency-check#licences` |
| Install scripts and the offline cache | 1 | 0.5 | `dependency-check#scripts-offline` |
| Licences in the seal's SBOM | 1 | 0.5 | `dependency-check#seal` |

## MainSDK Cloud

Everything on this page is free in your app.


## Quick start

```bash
npx mainsdk check
```

The gate needs no setting. `mainsdk check` asks the registry and [OSV.dev](https://osv.dev) once, keeps the answers for 24 hours under `.mainsdk/cache/deps/`, and then runs every gate.

## Configuration

Add a `deps` section to `main.yaml` to change the policy. Every key is optional.

```yaml title="main.yaml"
deps:
  min-age: 7d                    # a direct dependency first published more recently is reported
  licences:
    deny: [AGPL-1.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later, SSPL-1.0]   # the default list
    allow: []                    # when not empty, any licence outside it is reported
    unknown: warn                # warn | fail
  advisories:
    fail-on: high                # critical | high | moderate | low | none
    ignore:
      - { id: GHSA-xxxx-xxxx-xxxx, until: 2027-01-31, reason: "Not reachable: only used in tests" }
  scripts:
    allow: [esbuild, sharp]      # packages allowed to run install scripts
  offline: info                  # info | fail; fail is for CI with network access
```

| Key | Type | What it does |
| --- | --- | --- |
| `min-age` | duration | A direct dependency first published more recently is reported (`MAIN-DEPS-0002`) |
| `licences.deny` | list | Licences that fail the gate. Defaults to the AGPL and SSPL list shown |
| `licences.allow` | list | When not empty, any licence outside it is reported |
| `licences.unknown` | string | `warn` or `fail` for a package whose licence cannot be read |
| `advisories.fail-on` | string | `critical`, `high`, `moderate`, `low` or `none`; at or above it fails, below it warns |
| `advisories.ignore` | list | Advisories to skip, each with an `id`, an `until` date and a `reason` |
| `scripts.allow` | list | Packages allowed to run install scripts |
| `offline` | string | `info` or `fail` when the network is unavailable; `fail` is for CI |

## What you get

The `deps` gate of `mainsdk check` reads your lockfile and the installed packages and tells you, before CI or a deploy does, that a dependency is a name a model made up, was published two days ago, is one letter from a popular package, has a known advisory, carries a licence you can't ship under, or runs an install script nobody allowed. Every finding names the package, the version, why and the fix. It runs in the CLI only: nothing of it loads in your app or its request path. It is free.

### What it reads

- **Lockfiles:** `pnpm-lock.yaml` (v9), `package-lock.json` (v3), `yarn.lock` (classic and Berry) and `bun.lock`. Every format gives the same list: name, version, direct or transitive, and the path from a direct dependency. Workspace packages are left out.
- **Direct dependencies:** the `dependencies`, `devDependencies`, `optionalDependencies` and `peerDependencies` of every workspace `package.json`.
- **Registries:** the `registry` and `@scope:registry` lines of `.npmrc` (project, then user), else the public npm registry. A token goes only to the host it is configured for, never to another registry and never to OSV.
- **Installed packages:** each `node_modules/<name>/package.json` (and pnpm's store) for the licence and install scripts. A package that isn't installed has no licence or script finding.

### Offline and the cache

With no network and no cache younger than 24 hours, the gate reports `MAIN-DEPS-0007` once and still runs the lockfile, licence and script rules. An unreachable registry or OSV never blocks a local check; after the first failure the CLI stops asking, so an offline run costs one timeout. Set `offline: fail` in CI, where the network is there.

### What it always does

- Runs only in the CLI, never in the app (ADR-0074).
- Sends no credentials to OSV, and registry tokens only to their own host.
- Gives the same component list for the same dependencies in any lockfile format.

## Errors and checks

| Code | Meaning |
| --- | --- |
| `MAIN-DEPS-0001` | A direct dependency isn't on its registry. Anyone may register a name a model made up. |
| `MAIN-DEPS-0002` | A direct dependency's first version is younger than `deps.min-age`. |
| `MAIN-DEPS-0003` | A direct dependency is one edit away from a popular package it isn't (names under 4 characters are skipped; private registries are skipped). |
| `MAIN-DEPS-0004` | A resolved package has an advisory, with its id, severity, fixed version and dependency path. At or above `fail-on` it fails; below it warns. |
| `MAIN-DEPS-0005` | A licence is denied, outside a non-empty `allow`, or unknown with `unknown: fail`. `OR` passes when any branch passes, `AND` needs every part, `WITH` keeps the base licence's result. |
| `MAIN-DEPS-0006` | A package runs `preinstall`, `install` or `postinstall` and isn't in `scripts.allow` (a warning). |
| `MAIN-DEPS-0007` | The network was unavailable, so advisories and ages weren't checked (info, or a failure with `offline: fail`). |
| `MAIN-DEPS-0008` | An advisory `ignore` has expired or has no `until` or `reason`; the advisory is reported again. |

Each code's cause and fix is in [check codes](https://mainsdk.dev/docs/reference/check-codes).

## Swapping drivers

`deps` has no driver. It runs in the CLI against your lockfile, the registry and OSV.dev, so there is nothing to swap.

## Testing

The gate's tests run against a fake registry, a fake private registry and a fake OSV server, so none reaches a real host (`packages/cli/test/deps.test.ts`). In your own CI, run `npx mainsdk check` with `offline: fail` so a missing network fails the build.

## Next

- [Check your dependencies](https://mainsdk.dev/docs/guides/check-your-dependencies)
- [Check codes](https://mainsdk.dev/docs/reference/check-codes)
