toby-bridges/api-relay-audit
toby-bridges/api-relay-audit从 DeepSeek Harness 对 AI API 中转站和 LLM 代理运行本地安全审计,生成 Markdown 报告,覆盖提示词注入、模型替换信号、工具调用改写、错误泄漏、流完整性和按 profile 启用的 Web3 风险。
为 DeepSeek Harness 插件做安装前供应链校验:在代码运行前核对即将安装的包是否与你读到的源码一致。
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.
| Check | Question answered |
|---|---|
| Source pinning | Does this specifier identify immutable content, or can it change under the same name? |
| Registry integrity | Do the bytes we received match the digest the registry publishes? |
| Install hooks | Will code execute during dsh plugin add, before any post-install scan can look? |
| Build provenance | Does a SLSA attestation tie this artifact to a specific commit and CI workflow? |
| Artifact vs source | Do the published files actually match that commit — and was anything added at publish time? |
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.
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.
dsh-provenance preflight some-plugin@1.2.3
dsh-provenance preflight github:owner/repo
dsh-provenance preflight some-plugin --json # for CI
Two tools are registered, so the agent can run the checks itself:
provenance_preflight — audit an install source before installingprovenance_verify — inspect what is already installed in a profiledsh-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.
As a gate in your own pipeline:
dsh-provenance preflight some-plugin@1.2.3 --strict
Exit codes: 0 ok/notice, 1 review (with --strict), 2 block.
A full example — re-check the sources daily, because a moving git reference can change under the same name (see Why pinning matters above):
# .github/workflows/preflight.yml
name: preflight plugins
on:
push:
branches: [main]
schedule:
- cron: "0 3 * * *" # daily: upstream branches/tags can move
jobs:
preflight:
runs-on: ubuntu-latest
steps:
- run: npm install -g dsh-provenance
- run: dsh-provenance preflight some-plugin@1.2.3 --strict
- run: dsh-provenance preflight github:owner/repo#<sha> --strict
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.
Stated plainly, because overstating scope is how security tooling does harm:
npm audit signatures for that. Every report says so.lib/, dist/ and minified files are reported as
unverifiable, never as match. Verifying them requires reproducing the build.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.
Auditing hostile packages is itself dangerous. Every report carries a guarantees block asserting:
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.GITHUB_TOKEN is optional, used solely to raise the
anonymous rate limit, and never logged or persisted.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.
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
MIT