failproofai is an npm package that installs hooks into AI agent harnesses (Claude Code, Codex, Cursor, the Hermes and OpenClaw gateways, and others) and runs locally in the environment the agent runs in, observing and gating agent actions. A compromised dependency would therefore run on our users' machines and servers, inside their agent sessions — so we treat our supply chain as part of our users' trust boundary. This document covers how to report a vulnerability and how our dependency supply-chain scanning works.
Please report security issues privately — do not open a public issue.
- Preferred: open a private vulnerability report via GitHub Security Advisories.
- Or email failproofai@exosphere.host.
We aim to acknowledge reports within 3 business days and will keep you updated as we investigate and ship a fix. Please give us a reasonable window to remediate before any public disclosure.
Every pull request — including automated Dependabot dependency bumps — is scanned for supply-chain threats before it can merge, via two complementary layers.
.github/workflows/osv-scanner.yml runs
OSV-Scanner against the resolved
dependency tree (bun.lock). It checks every direct and transitive package
against OSV.dev, which aggregates GitHub/npm security
advisories and the OpenSSF malicious-packages feed
(confirmed malware: typosquats, account-takeover injections, and the like).
Policy: block on any finding. The gate fails on any known-vulnerable or
malicious package in the tree — not just newly introduced ones. It runs on every
PR, on pushes to main, and daily (to catch advisories disclosed after a
dependency was already merged). A daily run that fails on main — where there's
no PR author already looking at the check — also posts to Slack, via the
SLACK_WEBHOOK_URL repository secret.
OSV-Scanner is advisory-based: it can only flag threats already cataloged. To catch novel attacks (malicious install scripts, suspicious network/filesystem access, obfuscation, sudden maintainer changes) before they reach any advisory database, we use Socket via its GitHub App, which comments on PRs that introduce risky dependency behavior. Socket is advisory (it comments); the deterministic blocking gate is OSV-Scanner.
The gate above scans the lockfiles it is given (the --lockfile list in the
workflow). GitHub's dependency graph reads every lockfile in the repository, so
Dependabot alerts cover trees the gate does not — in particular the TypeScript SDK's
integration fixtures, sdk/typescript/integration/fixtures/*/package-lock.json.
Those are not in the scanner's list and are not a Dependabot update ecosystem in
.github/dependabot.yml either, so an alert is the only
thing that will ever mention them. Treat the alert list as authoritative for those
paths, and see Triaging a Dependabot alert in an integration fixture below for how
to resolve one.
When the OSV-Scanner gate fails on a PR:
- Prefer fixing it. Bump the affected dependency to a patched version. For a
transitive dependency that a parent pins to a vulnerable version, add a minimal
overridesentry (as we do forpostcss) and let CI validate the build. - Only if there is no fix, add a justified, time-boxed
[[IgnoredVulns]]entry toosv-scanner.toml(id,reason,ignoreUntil). One advisory per entry, which is the point: any other advisory against the same package still blocks. The scanner resolves aliases, so one id covers every spelling of the same advisory — naming both aPYSEC-id and itsGHSA-alias adds nothing and gets reported as an unused ignore. It does not cover a distinct advisory against that package, which still fails the gate until it gets its own entry. Re-review entries when theirignoreUntildate passes. - Only for a package that keeps accruing unfixable advisories, where an id
list has become whack-a-mole — each new disclosure reddening the gate on an
unrelated PR until someone appends another id — use
[[PackageOverrides]]instead (name,version,ecosystem,vulnerability.ignore,effectiveUntil; note the different date key). This silences every advisory against that package, including fixable ones, so thereasonmust say why losing that signal is acceptable for this package specifically. Pinversionto the locked version so a lockfile bump re-opens the gate, and never omiteffectiveUntil.
Each directory under sdk/typescript/integration/fixtures/ is a real consumer
project with its own committed lockfile, and several pin a deliberately old
framework major — ai@4.3.19, @langchain/core@0.3.80, @mastra/core@0.24.9. That
is the point of those fixtures: an adapter has to keep working at the floor of
every range the SDK declares, and the failure they exist to catch is invisible from
the unit tests (see the harness docstring in sdk/typescript/integration/harness.ts).
The pinned framework is the subject of the test, not an incidental dependency.
That splits an alert in one of these lockfiles into two cases:
- The advisory is against a transitive dependency. Fix it. Add a minimal
overridesentry to that fixture'spackage.jsonnaming the patched version, re-resolve withnpm install --package-lock-only --ignore-scripts, and let thefailproofai-ts-sdk-integrationsCI job validate it — the same mechanism, and the same reasoning, as the rootpackage.json'soverrides. An in-rangenpm updatewill almost never help: these frameworks pin their transitive deps exactly (ai@4.3.19requiresjsondiffpatch@0.6.0, not^0.6.0), which is what makes the lockfiles reproducible and an override the only lever. Never resolve one of these by moving the pinned framework — that deletes the fixture's reason to exist and silently drops a supported release from the matrix. - The advisory is against the pinned framework itself, or against a package whose
only fix is a major that framework cannot take. There is no fix that keeps the
fixture, so record it in the table below rather than pretending it away. Unlike a
failed gate there is nothing to write in
osv-scanner.toml: the scanner does not read these lockfiles, so an[[IgnoredVulns]]entry for one would filter nothing and be reported as an unused ignore.
| Advisory | Package | Fixtures | Why it stays |
|---|---|---|---|
| GHSA-rwvc-j5jr-mgvh (low, CVSS 3.7) | ai@4.3.19 |
ai-4, mastra-0 |
A filetype-whitelist bypass on file upload. Fixed in ai@5.0.52 — the major the ai-4 fixture exists to stay below, and one @mastra/core@0.24.9 cannot take either (it requires ai@^4). Neither fixture uploads a file or exposes an upload surface; each runs one scripted agent against an in-process model. |
| GHSA-866g-f22w-33x8 (low, CVSS 4.3) | @ai-sdk/provider-utils@2.2.8 |
ai-4, mastra-0 |
Unbounded response-body reads in createJsonResponseHandler and its siblings. Fixed in 3.0.28; 2.2.8 ships inside ai@4.3.19, which uses the v2 API, so the fix is the same blocked major as the row above. Exploiting it needs a hostile model-provider HTTP response; these fixtures never call one. |
Both rows are bounded by the same facts: these are test fixtures — "private": true,
never published, installed with --ignore-scripts, and run only in CI against
scripted in-process models. Re-check them whenever the supported framework floor
moves; dropping ai 4.x from the matrix is what actually retires them.
These steps live outside the repo and require admin access:
- Install the Socket GitHub App on the repository to enable behavioral PR comments.
- Make the OSV-Scanner check required: in branch protection for
main, add the OSV-Scanner job (shown asOSV-Scanner) as a required status check, so a red scan blocks merge. - (Optional) For a Socket CI gate in addition to the App, add a
SOCKET_SECURITY_API_KEYrepository secret and the Socket CI action — deferred until tuned, since behavioral findings can have false positives. - Slack alert on a failed daily scan: set the
SLACK_WEBHOOK_URLrepository secret to a Slack Incoming Webhook URL (the same secretintegration-suitealready posts to). Without it, the daily job still runs and still fails CI on a finding — it just skips the Slack POST and logs that the secret is unset.