Back to home

Darren-Tang

dsh-provenance

dsh-provenance

Stars
0
Language
TypeScript
Created
Aug 17, 2026
Updated
Aug 17, 2026

Introduction

dsh-provenance

The source you read on GitHub is not necessarily the package you install.

A supply-chain preflight for DeepSeek Harness plugins. It answers a question no other scanner in this ecosystem asks.

Existing plugin scanners read the files already on your disk and ask "does this code do something bad?". That is a useful question, but it comes second. The first question is:

Is this code the code the author showed me?

An npm tarball is whatever the publisher uploaded. The registry does not build it from the repository, and nothing forces the two to agree. A publisher can push clean source to GitHub, collect stars and reviews, then publish a tarball with one extra file in it. Every scanner that runs after dsh plugin add will happily scan the injected file as if it belonged there — and by then the package's install hooks have already executed.

dsh-provenance runs before installation and compares three independent sources of truth: the npm registry, the upstream repository, and the bytes actually on the wire.


What it checks

CheckQuestion answered
Source pinningDoes this specifier identify immutable content, or can it change under the same name?
Registry integrityDo the bytes we received match the digest the registry publishes?
Install hooksWill code execute during dsh plugin add, before any post-install scan can look?
Build provenanceDoes a SLSA attestation tie this artifact to a specific commit and CI workflow?
Artifact vs sourceDo the published files actually match that commit — and was anything added at publish time?

Why pinning matters more than it looks

some-plugin@1.2.3          pinned      a published npm version is immutable
some-plugin@^1.2.3         NOT pinned  the next install may resolve elsewhere
github:owner/repo          NOT pinned  the default branch can be rewritten under the same name
github:owner/repo#v1.0.0   NOT pinned  a tag can be moved by the repo owner
github:owner/repo#<sha>    pinned      a commit sha cannot be forged

A moving git reference is strictly worse than a floating npm range. With npm, the version you audited stays byte-identical forever. With a branch, the code you reviewed and the code you install can differ silently, and the plugin directories are full of github:owner/repo install instructions.


Install

As a dsh plugin:

dsh plugin --profile web add dsh-provenance

As a standalone CLI (no dsh required, useful in CI):

npm install -g dsh-provenance

Requires Node.js 22.19+. Zero runtime dependencies — a tool that inspects untrusted packages should not drag in a supply chain of its own.


Use

Before installing anything

dsh-provenance preflight some-plugin@1.2.3
dsh-provenance preflight github:owner/repo
dsh-provenance preflight some-plugin --json      # for CI

Inside dsh

Two tools are registered, so the agent can run the checks itself:

  • provenance_preflight — audit an install source before installing
  • provenance_verify — inspect what is already installed in a profile

Auditing what you already installed

dsh-provenance verify --profile web

Fully local: no network, no plugin code executed. It reports which installed plugins are pinned, which can still be traced upstream, and which shipped install hooks that already ran.

CI

dsh-provenance preflight some-plugin@1.2.3 --strict

Exit codes: 0 ok/notice, 1 review (with --strict), 2 block.


The install guard

A dsh agent can run shell commands, which means the agent itself can be talked into installing a plugin — through a poisoned README, a fetched web page, or a crafted issue comment. Auditing only what a human types misses that path entirely.

So install attempts are intercepted and refused unless that source has already passed a preflight in this session:

dsh-provenance: refusing to install evil-plugin without a supply-chain preflight.
Installing runs the package's install hooks immediately, so this cannot be checked
afterwards. Call provenance_preflight with source="evil-plugin" first, then retry.

Disable with { guardInstalls: false } if it gets in your way.

Implementation note: this uses ctx.tools.guard() rather than the tools/pre-execute waterfall, because guard's signature is documented exactly ((execution) => string | undefined, synchronous) and its denial is monotonic — no later plugin can turn the refusal back into permission. The guard reads execution.arguments, which is the field name the pipeline actually exposes; a comparable tool in this ecosystem read exec.args and shipped a sentinel that never fired for two releases.


What this tool does NOT do

Stated plainly, because overstating scope is how security tooling does harm:

  • It does not verify sigstore signatures. Attestation payloads are parsed, not cryptographically verified. Run npm audit signatures for that. Every report says so.
  • It cannot verify build output. lib/, dist/ and minified files are reported as unverifiable, never as match. Verifying them requires reproducing the build.
  • A clean report is not proof of safety. It means these rules found nothing.
  • It is not a code scanner. It checks where code came from, not what it does. Pair it with a behavioural scanner; the two answer different questions.

The failure mode it refuses to have

If not a single published file can be matched against the repository, the report does not show a reassuring 0 mismatch. It raises diff.nothing-verified and says the comparison verified nothing. A tool that rounds "I could not check this" up to "this is fine" is worse than no tool, because it manufactures false assurance out of missing evidence.


Safety properties

Auditing hostile packages is itself dangerous. Every report carries a guarantees block asserting:

  • The audited package is never executed. No package manager is invoked, so no install hook can fire as a side effect of auditing.
  • Nothing is written to disk. Archives are parsed entirely in memory, which structurally eliminates zip-slip, symlink escape and disk exhaustion rather than defending against them.
  • Egress is allowlisted to registry.npmjs.org, codeload.github.com and api.github.com. This matters: a malicious package controls its own repository.url, so without a fixed host allowlist a scanner becomes an SSRF pivot into the operator's internal network. Literal IPs, embedded credentials, non-default ports and redirects off the allowlist are all rejected.
  • Bounded everywhere. Response size, gzip output, entry count and per-entry size all have hard ceilings, so an archive cannot exhaust memory.
  • Credentials from the environment only. GITHUB_TOKEN is optional, used solely to raise the anonymous rate limit, and never logged or persisted.

Architecture

src/core/     zero framework dependencies — the engine, fully unit-tested
src/index.ts  thin dsh adapter
src/cli.ts    standalone CLI

dsh v0.1 is a developer preview that states outright that breaking changes are coming. The supply-chain logic is therefore deliberately isolated from the harness interface: when dsh moves, only the thin adapter should need rewriting, and the CLI keeps working regardless.

Development

npm install
npm test              # builds core, then runs the suite
npm run typecheck     # core only, no peer deps needed
npm run build         # full build, needs the dsh peer packages

Tests exercise real gzip bytes, real tar headers and the real documented field names, rather than mocks that could mirror the same wrong assumption as the implementation.

Local development against a dsh source checkout:

npm run build
# set the absolute path inside cordis.yml first
pnpm dsh web --patch /absolute/path/to/dsh-provenance/cordis.yml

License

MIT