The most useful contributions are new matchers and new plugins. Both have dedicated guides:
- docs/writing-matchers.md — grow the matcher set with a coding-agent-friendly workflow
- docs/plugins.md — plugin authoring reference
packages/
core/ Types, schemas, plugin contracts, config loader
scanner/ Regex matchers + scanning engine
processor/ AI agent integration (Claude SDK, Codex SDK), enrich, triage, revalidate
deepsec/ Publishable package: bundled CLI + the `deepsec/config` sub-export + the @vercel/sandbox executor
e2e/ End-to-end tests against a fixture project
fixtures/
vulnerable-app/ Intentionally vulnerable test data (excluded from lint/knip)
docs/ User-facing documentation
samples/ Copy-paste starting points for new users
pnpm install
pnpm test # all packages, including e2e
pnpm test:unit # excludes e2e
pnpm -r build # tsc across all workspaces (typecheck)
pnpm lint # biome check
pnpm lint:fix # biome check --write
pnpm knip # unused code/dep detection
pnpm deepsec --help # the CLI (via tsx)Bundle for distribution:
pnpm bundle # esbuild → packages/deepsec/dist/{cli,config}.mjs
pnpm test:bundle # bundle e2e: runs the produced binary as a subprocessAll of build, test, lint, and knip must pass before a PR is mergeable.
PRs that touch the publish surface (anything imported via deepsec/config)
must also pass pnpm test:bundle.
e2e/pipeline-sandbox.test.ts runs the full pipeline against a real
Vercel Sandbox, but with a stub agent inside the sandbox — exercises
bootstrap snapshot, worker spawn, file upload/download, and result
merge without spending model tokens. Gated on DEEPSEC_E2E_LIVE_SANDBOX=1
- Vercel Sandbox credentials, so
pnpm testskips it by default.
- In CI: trigger the
E2E live sandbox workflow
manually from GitHub Actions. Required repo secrets:
VERCEL_TOKEN,VERCEL_TEAM_ID,VERCEL_PROJECT_ID. (No AI key needed.) - Locally, when working on sandbox code:
VERCEL_OIDC_TOKEN=$(grep ^VERCEL_OIDC .deepsec/.env.local | cut -d= -f2) \ DEEPSEC_E2E_LIVE_SANDBOX=1 \ pnpm exec vitest run --project e2e e2e/pipeline-sandbox.test.ts
Run it before PRs that touch packages/deepsec/src/sandbox/. The
stub-agent flow doesn't exercise the firewall's credential-brokering
transform (no AI traffic flows), so PRs that touch that path
specifically still warrant a one-off run with --agent claude-agent-sdk
- an AI key.
Short version (full version in docs/writing-matchers.md):
- Create
packages/scanner/src/matchers/<slug>.tswith aMatcherPluginexport. - Register it in
packages/scanner/src/matchers/index.ts(import +registry.register(...)). - Run
pnpm deepsec scan --project-id <id> --root <path> --matchers <slug>and check the candidate count is reasonable. pnpm testandpnpm lint.
Matchers that only make sense for one organization (specific helper names, internal package imports) go in a plugin instead. See docs/plugins.md.
A plugin can fill any of five slots: matchers, notifiers, ownership,
people, executor. Read docs/plugins.md for the
full guide.
The minimal shape:
import type { DeepsecPlugin } from "deepsec/config";
import { myMatcher } from "./matchers/my-matcher.js";
export default function myPlugin(): DeepsecPlugin {
return {
name: "@my-org/plugin-internal-services",
matchers: [myMatcher],
};
}- Match the existing code style. Lint via Biome;
pnpm lint:fixauto-formats. - Default to no comments. Add one only when the why is non-obvious (a hidden constraint, a subtle invariant, a workaround). Don't write comments that restate the code.
- Keep PRs small and single-purpose. If a matcher add needs a test fixture refactor, separate PRs.
vitest, run via pnpm test. Each package has its own vitest.config.ts;
the workspace config in vitest.workspace.ts glues them together.
The standard matcher test pattern is in
packages/scanner/src/__tests__/matchers.test.ts: a single test that
asserts the matcher fires on a known-vulnerable input and doesn't on a
known-safe one.
See SECURITY.md. Don't open public issues for security problems in the tool itself.