fullshield 0.1.1 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +143 -1
- package/README.md +62 -5
- package/dist/agent.js +3744 -640
- package/dist/bench/score.js +118 -0
- package/dist/cli/index.js +6084 -1911
- package/dist/compliance.js +31 -2
- package/dist/dashboard.js +20 -1
- package/dist/index.d.ts +153 -3
- package/dist/index.js +5953 -2539
- package/dist/mcp.js +3736 -632
- package/dist/runtime/auto.js +100 -3
- package/dist/runtime/dlp.js +313 -0
- package/dist/runtime/express.js +337 -0
- package/dist/runtime/fastify.js +325 -0
- package/dist/runtime/hono.js +570 -0
- package/dist/runtime/koa.js +573 -0
- package/dist/runtime/rasp.js +100 -3
- package/dist/runtime/register.js +100 -3
- package/dist/supply.js +87 -1
- package/dist/vex.js +178 -0
- package/package.json +12 -1
- package/rules/CONFIG-DIRLIST-001.md +9 -0
- package/rules/CONFIG-TRUSTPROXY-001.md +11 -0
- package/rules/DLP-CARD-001.md +9 -0
- package/rules/DLP-INTERNAL-001.md +9 -0
- package/rules/DLP-PII-001.md +9 -0
- package/rules/DLP-SECRET-001.md +9 -0
- package/rules/DLP-STACK-001.md +9 -0
- package/rules/HOST-SSH-001.md +13 -0
- package/rules/HOST-SSH-002.md +11 -0
- package/rules/HOST-SSH-003.md +11 -0
- package/rules/MCP-001.md +7 -0
- package/rules/MCP-002.md +7 -0
- package/rules/MCP-003.md +7 -0
- package/rules/MCP-004.md +7 -0
- package/rules/MCP-005.md +7 -0
- package/rules/README.md +47 -1
- package/rules/SAST-COOKIE-001.md +11 -0
- package/rules/SAST-CSRF-001.md +11 -0
- package/rules/SAST-DESER-001.md +11 -0
- package/rules/SAST-GQL-001.md +11 -0
- package/rules/SAST-HASH-001.md +11 -0
- package/rules/SAST-MASSASSIGN-001.md +11 -0
- package/rules/SAST-NOSQLI-001.md +6 -2
- package/rules/SAST-REDOS-001.md +11 -0
- package/rules/SAST-TIMING-001.md +11 -0
- package/rules/SAST-UPLOAD-001.md +11 -0
- package/rules/SAST-XXE-001.md +11 -0
- package/rules/SECRET-BUNDLE-001.md +11 -0
- package/rules/SECRET-SESSION-001.md +9 -0
- package/rules/SUPPLY-WORKFLOW-001.md +9 -0
- package/rules/SUPPLY-WORM-001.md +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,149 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to FullShield are documented here.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.2.6 — 2026-10-16
|
|
6
|
+
|
|
7
|
+
- v0.2.5 reached staging with a fresh token, then sigstore threw `TLOG_CREATE_ENTRY_ERROR` because Rekor already had that transparency-log entry (HTTP 409). npm 12.2.0 leaves `fetchOnConflict` off, so the publish stopped. Staging now turns that on before loading the publisher, and gives the Rekor request 120 seconds. It still refuses to publish without provenance.
|
|
8
|
+
|
|
9
|
+
## 0.2.5 — 2026-10-16
|
|
10
|
+
|
|
11
|
+
- The publish probe saved the GitHub OIDC token, then verify ran for 24 minutes. npm rejected that expired token with HTTP 401 when staging started. Staging now requests a new token immediately before the exchange, and the probe does not save one. The job pins npm 12.2.0.
|
|
12
|
+
|
|
13
|
+
## 0.2.4 — 2026-10-16
|
|
14
|
+
|
|
15
|
+
- Publish installs npm 12. `npm pack --json` is now an object, and the pack budget was parsing the inner `files` array. It reads the pack entry from either shape.
|
|
16
|
+
|
|
17
|
+
## 0.2.3 — 2026-10-16
|
|
18
|
+
|
|
19
|
+
- The publish checkout was shallow, so `semver-check` could see only the tag being released. That job now fetches full history and tags before comparing versions.
|
|
20
|
+
|
|
21
|
+
## 0.2.2 — 2026-10-16
|
|
22
|
+
|
|
23
|
+
- `v0.2.1` failed typecheck before staging. The baseline-tag test now types its tag argument.
|
|
24
|
+
|
|
25
|
+
## 0.2.1 — 2026-10-16
|
|
26
|
+
|
|
27
|
+
- `v0.2.0` was tagged and the publish job failed before staging. `semver-check` treated that tag as the previous release and compared `0.2.0` to itself. The check now skips a tag that points at HEAD.
|
|
28
|
+
|
|
29
|
+
## 0.2.0 — 2026-10-16
|
|
30
|
+
|
|
31
|
+
Phases 36–45 ship in this version. `0.3.0` and `0.4.0` were not tagged.
|
|
32
|
+
|
|
33
|
+
### Phase 36 — provenance and semver
|
|
34
|
+
|
|
35
|
+
- Publish refuses to stage without provenance. `0.1.3` has no attestation because the stage retried after a sigstore 409. The publish job now polls `dist.attestations` and fails if it stays empty.
|
|
36
|
+
- `scripts/semver-check.mjs`, `scripts/pack-budget.mjs`, and `scripts/provenance-gate.mjs`.
|
|
37
|
+
- TypeORM `class User extends BaseEntity` plus `User.find` is not `SAST-NOSQLI-001` when the file does not import `typeorm`. A bare `User.find(req.body)` is unchanged.
|
|
38
|
+
- Default-scan snapshots are `bench/snapshots/0.1.1.json`, `bench/snapshots/0.1.3.json`, and `bench/snapshots/HEAD.json`. Finding keys are `ruleId`, `file`, `line`, `severity`. Published `0.1.3` and this tree match: vulnerable-app 35, clean-app 0, nodegoat 379, juice-shop 105, dvna 23, goof 354, vulnerable-node 61. `0.1.1` has no `--offline-safe` flag, so that snapshot is `scan --json` only. It differs. The printed diff is `bench/snapshots/DIFF.txt`. That file is not a human approval.
|
|
39
|
+
|
|
40
|
+
### Phases 37–45
|
|
41
|
+
|
|
42
|
+
- Holdout registry, three untouched apps, and review sheets. Those apps were not scanned.
|
|
43
|
+
- Comparison harness. One local run is in `COMPARISON.md`: semgrep 1.180.0, osv-scanner 2.7.0, npm 11.19.0. Mapped class buckets stayed empty. osv-scanner returned no JSON for juice-shop and dvna (no lockfile at those pins). Commercial scanners were not executed. This is not a recall claim.
|
|
44
|
+
- Symbol reachability when an advisory's `affected_functions` list is present. Advisories without that list stay on the import-level class. `DEPS-MALICIOUS-001` is not downgraded.
|
|
45
|
+
- Taint propagation for callbacks, `call`/`apply`, `Object.assign`, spread, `this` fields, and closures behind `FULLSHIELD_EXPERIMENTAL_TAINT_V2=1`. Default scan does not enable it.
|
|
46
|
+
- Ten real-world repos are pinned. Nine produced finding counts in `BENCHMARK.md`. next.js aborted with a JavaScript heap out of memory and has no count. Nothing is labeled, so precision is not sampled.
|
|
47
|
+
- Parse pool behind `FULLSHIELD_PARSE_POOL=1`. Measured times are in `bench/perf-baseline.json`. next.js aborted (SIGABRT) on every sample. nest and undici finished under the cold and warm targets and missed the 5s incremental target.
|
|
48
|
+
- Promotion report lists every experimental flag as unmet. Nothing was promoted.
|
|
49
|
+
- `fullshield doctor`, `FS-E###` error codes, and console output capped at 10 findings.
|
|
50
|
+
- GitHub Actions in the project workflows are pinned by commit SHA. Scorecard and CodeQL workflows are added. Scorecard is v2.5.0 from `ghcr.io` because `gcr.io` refused the pull. A tag workflow attaches a CycloneDX SBOM to the GitHub Release.
|
|
51
|
+
- `node scripts/repro-build.mjs --check` on 2026-10-16: 26 `dist` files hashed the same across two builds.
|
|
52
|
+
|
|
53
|
+
## 0.1.3 — 2026-10-15
|
|
54
|
+
|
|
55
|
+
### Phase 35 — experimental host SSH exposure check
|
|
56
|
+
|
|
57
|
+
- `fullshield host --experimental-host` (`FULLSHIELD_EXPERIMENTAL_HOST=1`) reports OpenSSH CVE-2024-6387 (regreSSHion) exposure on Linux: distro package version via `dpkg-query` / `rpm` / `apk` (not `ssh -V`), OSV distro advisories with upstream fallback, `LoginGraceTime 0`, `ss -ltn` listen address, and optional auth.log timeout patterns. Detection/reporting only — no block, patch, or harden. Skips macOS, Windows, containers without sshd, and PaaS. Command refuses without `--experimental-host`. Default `scan` is unchanged.
|
|
58
|
+
|
|
59
|
+
### Phase 34 — policy-as-code + PR summary
|
|
60
|
+
|
|
61
|
+
- Experimental `fullshield.policy.json` (`FULLSHIELD_EXPERIMENTAL_POLICY=1`): required modules, per-rule severity overrides, `failOn` per git/CI branch, and exceptions that require `owner`, `reason`, and ISO `expires`. Expired exceptions fail CI with a clear message. Exceptions without owner or expiry are rejected. Schema is validated with the optional `zod` peer (lazy-imported; not a hard dependency).
|
|
62
|
+
- `scan --pr-summary`: deterministic Markdown for GitHub/GitLab PR comments listing new findings versus baseline (no AI), with links to rule docs. Uses existing baseline/diff.
|
|
63
|
+
- Default `scan` without the flag or a policy file is unchanged.
|
|
64
|
+
|
|
65
|
+
### Phase 33 — MCP configuration security (experimental)
|
|
66
|
+
|
|
67
|
+
- `FULLSHIELD_EXPERIMENTAL_MCPCFG=1` / `scan --experimental-mcpcfg` scans MCP client configs (`.cursor/mcp.json`, `.vscode/mcp.json`, `.mcp.json`, Claude Desktop) and MCP server code. Default `scan` emits no `MCP-*` IDs.
|
|
68
|
+
- `MCP-001` inline secrets (high), `MCP-002` unpinned `npx` / `@latest` (medium), `MCP-003` shell/exec or unrestricted fs-write tools without an allowlist (medium), `MCP-004` tool-description instruction injection / hidden / zero-width characters (high), `MCP-005` remote MCP over `http://` (medium).
|
|
69
|
+
- Home-dir configs are scanned only with `scan --include-home` (never implied). No network.
|
|
70
|
+
- Model-provided MCP tool arguments are taint sources when the flag is on. Optional `guardTools(tools, { allow, confirm })` deny-lists dangerous tools, requires confirm for writes/payments, and logs every call.
|
|
71
|
+
|
|
72
|
+
### Phase 32 — experimental response DLP
|
|
73
|
+
|
|
74
|
+
- Optional `shieldGuard({ dlp: { mode: 'monitor' | 'mask' | 'block' } })` inspects outgoing JSON/text (size-capped at 64 KB) for secret patterns (same regexes as the secrets module), stack traces, internal IPs/hostnames, Luhn card numbers, and bulk emails on a one-email route.
|
|
75
|
+
- Behind `FULLSHIELD_EXPERIMENTAL_DLP=1`. Without the flag, `shieldGuard` and default `scan` are unchanged. `mask` replaces matches; `block` returns 500 `{ error: 'internal error' }`. Events go to `onEvent`. Over-cap bodies are skipped and counted.
|
|
76
|
+
|
|
77
|
+
### Phase 31 — supply chain, advanced + VEX
|
|
78
|
+
|
|
79
|
+
- Experimental (`FULLSHIELD_EXPERIMENTAL_VEX=1` / `scan --experimental-vex`): self-replicating worm indicators (`SUPPLY-WORM-001`) and workflow/git-hook/CI writes (`SUPPLY-WORKFLOW-001`). Default `scan` is unchanged.
|
|
80
|
+
- Transitive `FixProposal.kind = 'dependency-override'` (unsafe) when the parent has no fixed release. Writes npm `overrides` / pnpm `pnpm.overrides` / yarn `resolutions` into `package.json` and prints `run npm install`. The lockfile is not edited.
|
|
81
|
+
- Provenance coverage score (% of deps with provenance; unknown is not counted as present) with history-DB trend helpers.
|
|
82
|
+
- `fullshield vex --format openvex|cyclonedx` from reachability. `not_affected` + `vulnerable_code_not_in_execute_path` only when reachability is `not-imported`; otherwise `under_investigation`. Never guessed.
|
|
83
|
+
|
|
84
|
+
### Phase 30 — secrets, advanced (experimental)
|
|
85
|
+
|
|
86
|
+
- `scan --history` streams `git log -p`, dedupes by blob hash, and reports first-introduced commit + still-at-HEAD. Respects baseline and suppressions.
|
|
87
|
+
- `scan --bundle <dir>` scans `dist/`, `.next/static`, and `build/` for browser-exposed keys, including `NEXT_PUBLIC_*` / `VITE_*` secret-shaped values (`SECRET-BUNDLE-001`).
|
|
88
|
+
- `--verify-secrets` is opt-in, never default, never in generated CI templates. Requires `--i-own-these-credentials`. Read-only identity endpoints only (AWS STS GetCallerIdentity, GitHub `GET /user`, Stripe `GET /v1/balance`, Slack `auth.test`). Results: `live` \| `revoked` \| `unknown`. Never logs the key.
|
|
89
|
+
- Behind `FULLSHIELD_EXPERIMENTAL_SECRETS_ADV=1` / `scan --experimental-secrets-adv`. CLI flags do nothing useful without that flag. Default `scan` does not emit Phase-30-only rule IDs.
|
|
90
|
+
|
|
91
|
+
### Phase 29 — new vulnerability classes
|
|
92
|
+
|
|
93
|
+
- Experimental extra SAST/config classes (`FULLSHIELD_EXPERIMENTAL_CLASSES=1` / `scan --experimental-classes`): ReDoS, insecure deserialization, XXE, unrestricted upload, cookie flags, CSRF, mass assignment, timing-safe compare, password-hash cost, GraphQL introspection/depth, and `trust proxy` + IP rate limits. Default `scan` is unchanged.
|
|
94
|
+
|
|
95
|
+
- Publish CI uses `npm publish --provenance --access public` (no staged-manifest edits). The unpublished `v0.1.2` tag was deleted.
|
|
96
|
+
- CI: push to `next` runs `verify` on ubuntu-24.04 × Node 24 only. The full 6-job matrix (and the non-blocking `bench` job) runs on pull requests to `main` and on push to `main`. Required checks on `main` stay the six matrix jobs.
|
|
97
|
+
|
|
98
|
+
### Phase 28 — framework coverage
|
|
99
|
+
|
|
100
|
+
- Experimental Next.js / NestJS / Hono / Koa taint sources and AUTH (`FULLSHIELD_EXPERIMENTAL_FRAMEWORKS=1` / `scan --experimental-frameworks`). Default `scan` is unchanged.
|
|
101
|
+
- Runtime adapters `fullshield/runtime/koa` and `fullshield/runtime/hono` (optional peers; not required dependencies).
|
|
102
|
+
|
|
103
|
+
### NoSQL constructed-filter values
|
|
104
|
+
|
|
105
|
+
**New stable findings — may change CI results; use `--update-baseline`:**
|
|
106
|
+
|
|
107
|
+
- `SAST-NOSQLI-001` — request input used as a Mongo filter value (e.g. `{ password: req.body.password }`) at medium confidence, unless the value is coerced (`String()` / `Number()` / `Boolean()`), schema-validated (zod / joi / yup `.parse`), or `mongo-sanitize` / `express-mongo-sanitize` is applied. Fires only on MongoDB/mongoose receivers (file imports `mongodb`/`mongoose`, `collection.find` / `*Collection`, or a PascalCase model). Never Sequelize, Prisma, TypeORM, Knex, Drizzle, or unknown receivers.
|
|
108
|
+
|
|
109
|
+
`cr0hn/vulnerable-node` is now a seen holdout (no longer untouched).
|
|
110
|
+
|
|
111
|
+
### Phase 26c — engine gaps from the goof holdout
|
|
112
|
+
|
|
113
|
+
- Taint: method-call return inherits receiver taint; same-file helper parameters are seeded from tainted arguments; imported functions that return taint taint their call sites.
|
|
114
|
+
- Path: `extractAllTo` / `extractAll` / `extract` on a tainted archive object (zip-slip receiver).
|
|
115
|
+
- Config: missing helmet is attributed to the `express()` line when no commented-out helmet is present.
|
|
116
|
+
- Deps: OSV hits on direct dependencies are attributed to the `package.json` name line when a hit exists (still no invented advisories under `--offline-safe`).
|
|
117
|
+
- Multipart uploads: `req.files` is a taint source.
|
|
118
|
+
- `nodejs-goof` is a seen holdout. `cr0hn/vulnerable-node` is an untouched holdout scanned once. Precision in `BENCHMARK.md` is labeled self-triaged, unverified.
|
|
119
|
+
|
|
120
|
+
### Phase 26b — stable public-bench misses
|
|
121
|
+
|
|
122
|
+
**New stable findings — may change CI results; use `--update-baseline`:**
|
|
123
|
+
|
|
124
|
+
- `SAST-NOSQLI-001` — Mongo `$where` with a tainted string
|
|
125
|
+
- `SAST-REDIRECT-001` — destructured `{ query }` as a taint source (`res.redirect(query.to)`)
|
|
126
|
+
- `SAST-CRYPTO-001` — `md5(tainted)` used as a reset token
|
|
127
|
+
- `SAST-XSS-001` — `autoescape: false`
|
|
128
|
+
- `CONFIG-HELMET-001` — commented-out `helmet` (line-attributed; not a new detection on NodeGoat)
|
|
129
|
+
- `CONFIG-DIRLIST-001` — `serveIndex()` directory listing
|
|
130
|
+
- `SECRET-SESSION-001` — hardcoded `express-session` secret
|
|
131
|
+
|
|
132
|
+
Remaining in-scope misses (Swig `{{ }}` template line, JSON XSS, `includes()` allowlist, nested `{ _id: req.body.id }`, deps lockfile/offline, DVNA calc vs eval) stay in README Known limitations. Ground truth files were not edited to improve numbers.
|
|
133
|
+
|
|
134
|
+
Holdout: `bench:public` also clones pinned `snyk-labs/nodejs-goof`. Rules are not tuned from that app.
|
|
135
|
+
|
|
136
|
+
### Phase 27 — SAST + RASP coverage gaps
|
|
137
|
+
|
|
138
|
+
- RASP treats extra tokens in unquoted numeric SQL (`WHERE id = 1 OR 1=1`) as a structure change; a single number (`42`) stays allowed.
|
|
139
|
+
- Experimental SAST sinks (`FULLSHIELD_EXPERIMENTAL_SAST_GAPS=1` / `scan --experimental-sast-gaps`): Prisma `$queryRawUnsafe` / `$executeRawUnsafe` / `Prisma.raw`, Knex `raw`/`whereRaw`/`orderByRaw`, TypeORM `createQueryBuilder().where(concat)`, Drizzle `sql.raw`, mongoose `$where`, `spawn('sh', ['-c'])`, execa `shell: true`, zx `$.raw` / `{ quote: false }`. Prisma `$queryRaw` tagged templates and default zx `$` are not flagged. Default `scan` is unchanged.
|
|
140
|
+
- RASP load-time hooks for `@prisma/client`, `sequelize`, `knex`, `typeorm`, `drizzle-orm`, and mongoose `$where`.
|
|
141
|
+
- Attack corpus adds ≥10 cases each for `numeric-sql`, `orm-raw`, and `template-shell`. Benign corpus still 0 blocked.
|
|
142
|
+
|
|
143
|
+
### Phase 26 — public benchmark harness
|
|
144
|
+
|
|
145
|
+
- `npm run bench:public` clones pinned OWASP NodeGoat, Juice Shop, and DVNA SHAs, scans with `--json --offline-safe`, and writes `BENCHMARK.md`. Ground truth is exhaustive vs each app’s official docs, with `inScope` and `"reviewed": false`. Unmatched findings are triaged in `bench/<app>/triage.json`.
|
|
146
|
+
- CI job `bench` (ubuntu-24.04, Node 24) uploads `BENCHMARK.md` and is non-blocking.
|
|
147
|
+
- `scan --offline-safe` is an alias of `--offline`.
|
|
6
148
|
|
|
7
149
|
## 0.1.1 — 2026-10-15
|
|
8
150
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FullShield
|
|
2
2
|
|
|
3
|
-
**v0.
|
|
3
|
+
**v0.2.6 / alpha.** One install for Node.js/TypeScript backends: detect → classify → report → notify → fix → gate (CI `--fail-on` + runtime monitor). It does not stop attacks by itself.
|
|
4
4
|
|
|
5
5
|
**Headline: dependency reachability.** A lockfile CVE is not the same as an unused package. FullShield classifies each OSV hit as `reachable` (imported and called), `imported-not-called` (−1), `reachable-via-dependency` (lockfile path from an imported direct dep — **severity kept**, path shown, e.g. `express > body-parser > qs`), `not-imported` (−2, only when no such path exists), or `reachability-unknown` (severity kept) when the lockfile format has no parsed dependency edges. DevDependency-only vulns are `info` with a `dev-only` note unless production sources import the package. Pair it with `fullshield sbom --format cyclonedx`.
|
|
6
6
|
|
|
@@ -29,9 +29,13 @@ This is a `0.x` release. APIs may change. Do not treat it as a penetration test.
|
|
|
29
29
|
| Supply-chain install (opt-in, **experimental**) | `fullshield install` — static only; never executes packages. |
|
|
30
30
|
| Exploit confirm (opt-in, **experimental**) | `fullshield confirm` — localhost / 127.0.0.1 / FullShield-started container only. |
|
|
31
31
|
| Editor MCP (opt-in, **experimental**) | `fullshield mcp --stdio` — read-only; `propose_fix` returns diffs, never writes. |
|
|
32
|
+
| MCP client/server config (opt-in, **experimental**) | `MCP-001`–`MCP-005` (`FULLSHIELD_EXPERIMENTAL_MCPCFG=1`). Home-dir configs only with `scan --include-home`. |
|
|
32
33
|
| Threat model / dashboard (opt-in, **experimental**) | STRIDE+Mermaid sketch; dashboard binds 127.0.0.1 and locks `Host`. |
|
|
33
34
|
| Compliance / IaC (opt-in, **experimental**) | Mapping is **not a certification or audit**. IaC is ~10 high-value rules, not a full IaC scanner. |
|
|
34
35
|
| Remediation agent (opt-in, **experimental**) | Localhost mock GitHub API + local git only. Never merges, never pushes to `main`/`master`. |
|
|
36
|
+
| Response DLP (opt-in, **experimental**) | `shieldGuard({ dlp: { mode: 'monitor' \| 'mask' \| 'block' } })` with `FULLSHIELD_EXPERIMENTAL_DLP=1`. Inspects outgoing JSON/text (≤64 KB). Not a DLP product. |
|
|
37
|
+
| Policy / PR summary (opt-in, **experimental**) | `fullshield.policy.json` + `scan --pr-summary` (`FULLSHIELD_EXPERIMENTAL_POLICY=1`). Not an OPA/Kyverno substitute. |
|
|
38
|
+
| Host OpenSSH (opt-in, **experimental**) | `fullshield host --experimental-host` (`FULLSHIELD_EXPERIMENTAL_HOST=1`). Linux only. **What it does NOT do:** FullShield does not stop OS or SSH exploits. |
|
|
35
39
|
|
|
36
40
|
## What it detects (Core)
|
|
37
41
|
|
|
@@ -53,16 +57,22 @@ This is a `0.x` release. APIs may change. Do not treat it as a penetration test.
|
|
|
53
57
|
- RASP / virtual patches / honeytokens / threat auto-block: fail-open hooks; decoy-route hits are low and never auto-block; auto-block is opt-in (shared-IP / NAT risk).
|
|
54
58
|
- `install` / supply-chain: static heuristics only; never a substitute for `npm audit` policy or a malware sandbox.
|
|
55
59
|
- `confirm`: harmless canaries on localhost only; fail-open (`unconfirmed` is not a pass).
|
|
56
|
-
- MCP / LLM-app rules: editor glue and shallow AST/phrase checks. Not a prompt-injection or tool-abuse guarantee.
|
|
60
|
+
- MCP / LLM-app rules: editor glue and shallow AST/phrase checks. Not a prompt-injection or tool-abuse guarantee. `MCP-001`–`MCP-005` (`FULLSHIELD_EXPERIMENTAL_MCPCFG=1`) scan client configs and MCP server code; they do not inspect running MCP traffic and never scan `$HOME` unless `scan --include-home`.
|
|
57
61
|
- Threat-model / incremental / dashboard: a route sketch and a local history UI. Not a STRIDE workshop or a hosted SOC.
|
|
58
62
|
- `report --compliance` / `iac`: evidence-style mapping and a short IaC ruleset. **Not a certification or audit.**
|
|
59
63
|
- `agent`: opens PRs against a **mock/localhost API and a local git remote**. It never talks to real GitHub, never merges, never pushes to protected branches.
|
|
64
|
+
- `vex` / worm / overrides (`FULLSHIELD_EXPERIMENTAL_VEX=1`): static heuristics and reachability-backed VEX only. Default `scan` does not emit `SUPPLY-WORM-001` / `SUPPLY-WORKFLOW-001`. Overrides are **unsafe** (`fix --unsafe`). Apply writes `package.json` and prints `run npm install`; it does not edit the lockfile. Never claim `not_affected` unless reachability proves `not-imported`.
|
|
65
|
+
- Response DLP (`FULLSHIELD_EXPERIMENTAL_DLP=1`): inspects outgoing JSON/text for secret patterns, stack traces, internal IPs/hostnames, Luhn card numbers, and bulk emails. Pattern matching only; size-capped; not a DLP or tokenization product.
|
|
66
|
+
- Policy / `--pr-summary`: CI exceptions with expiry, not a general policy engine. PR comments are deterministic (no AI) and are not posted to GitHub or GitLab.
|
|
67
|
+
- `host` (`FULLSHIELD_EXPERIMENTAL_HOST=1`): Linux-only OpenSSH exposure report. **What it does NOT do:** FullShield does not stop OS or SSH exploits. Detection and reporting only. It does not block, patch, or harden OS services.
|
|
60
68
|
|
|
61
69
|
## Known limitations
|
|
62
70
|
|
|
63
71
|
- Platforms: Linux, macOS, and Windows on Node 22.13+. Quoted filenames in dirty-tree tests are POSIX-only (Windows forbids `"` in names).
|
|
64
72
|
- `node:sqlite` is loaded only by history/confirm, not by `--import fullshield/runtime/register`. Dashboard closes every handle in `try/finally` (Windows cannot delete an open `history.db`).
|
|
65
73
|
- FullShield does not stop OS or SSH exploits (including regreSSHion). It scans Node/TS apps you own.
|
|
74
|
+
- Extra classes (ReDoS, deserialization, XXE, upload, cookie flags, CSRF, mass assignment, timing compare, hash cost, GraphQL limits, `trust proxy` + IP rate limit) run only with `FULLSHIELD_EXPERIMENTAL_CLASSES=1` or `scan --experimental-classes`. ReDoS is a same-file static quantifier check. CSRF looks for `csurf` or `sameSite: 'strict'|'lax'` in that file.
|
|
75
|
+
- Git history, client-bundle, and live credential checks are **experimental** (`FULLSHIELD_EXPERIMENTAL_SECRETS_ADV=1`). Default `scan` is unchanged. `--verify-secrets` never runs without `--i-own-these-credentials`; AWS checks without a signing secret key report `unknown`.
|
|
66
76
|
|
|
67
77
|
Experimental RASP structure-change scoring on the vendored PayloadsAllTheThings corpus (not a claim that live attacks are blocked):
|
|
68
78
|
|
|
@@ -71,7 +81,27 @@ Experimental RASP structure-change scoring on the vendored PayloadsAllTheThings
|
|
|
71
81
|
|
|
72
82
|
Path 51/51 and SSRF 39/39 on the same corpus. Benign corpus stays 0 blocked. Prefer `mode: 'monitor'`.
|
|
73
83
|
|
|
74
|
-
These fixtures are self-authored
|
|
84
|
+
These fixtures are self-authored. Numeric SQL (`WHERE id = ` + input), ORM raw-query APIs (Prisma/Sequelize/Knex/TypeORM/Drizzle/`$where`), and template-literal shell (`exec(\`…${x}\`)`, `spawn('sh', ['-c', …])`, execa `shell: true`, zx `$.raw`) are covered in the attack corpus. Extra SAST sinks for those ORM / `sh -c` / execa / zx shapes stay behind `FULLSHIELD_EXPERIMENTAL_SAST_GAPS=1` (or `scan --experimental-sast-gaps`) so default `scan` behavior does not change. Mongo `$where` strings are **stable** SAST (Juice Shop track-order / reviews).
|
|
85
|
+
|
|
86
|
+
In-scope public-bench patterns we still do not claim (one line each):
|
|
87
|
+
|
|
88
|
+
- Swig `{{firstName}}` in `.html` — XSS only if autoescape is off in another file; we flag `autoescape: false`, not the template line.
|
|
89
|
+
- Juice Shop track-order XSS — server returns `res.json`; we do not treat JSON fields as HTML sinks.
|
|
90
|
+
- Juice Shop `url.includes(allowedUrl)` — weak allowlist helper is not a redirect sink; we flag `res.redirect(tainted)`.
|
|
91
|
+
- Juice Shop `{ _id: req.body.id }` — flagged as `SAST-NOSQLI-001` (medium) unless coerced (`String()`/`Number()`/`Boolean()`), schema-validated, or mongo-sanitize is applied. Same for goof `User.find({ password: req.body.password })`.
|
|
92
|
+
- Juice Shop `data/static/codefixes/*` — six `SAST-NOSQLI-001` hits are copies of the challenge fixes. They stay. There is no special-case exclusion for that directory.
|
|
93
|
+
- `DEPS-VULN-001` — when an OSV hit exists, the finding is attributed to the `package.json` name line; `--offline-safe` on a cache-less clone still emits no OSV hits (`package.json:N` will not match).
|
|
94
|
+
- DVNA calculator “stack leak” — that line is `mathjs.eval` (`SAST-EVAL-001`), not `err.stack`.
|
|
95
|
+
- Worker-pool timings from `node bench/perf.mjs --measure` on 2026-10-16 (`bench/perf-baseline.json`): nest cold p50 9.2s, warm 9.4s, peak 938 MB; undici cold p50 7.9s, warm 8.1s, peak 609 MB. Both missed the 5s incremental target (nest 8.8s, undici 8.0s). next.js aborted with SIGABRT on all five samples (JavaScript heap out of memory); the recorded times are time-to-abort (cold p50 55.4s, warm 56.7s, incremental 51.9s, peak 1705 MB), not finished scans.
|
|
96
|
+
- Real-world precision is not sampled. Nine of the ten pins in `bench/realworld.json` have finding counts in `BENCHMARK.md`. next.js produced no JSON. No false-positive rate is claimed.
|
|
97
|
+
|
|
98
|
+
Public-app in-scope detection on **unreviewed** ground truth (`"reviewed": false` in `bench/*/ground-truth.json`). These are not product recall claims. Matching is class + file + line ±3. Helmet comment-only line attribution is not counted.
|
|
99
|
+
|
|
100
|
+
- Seen holdout `snyk-labs/nodejs-goof` @ `add14ba59e98240d9e00a235dd7d42cd61ae9912`: 10/11 in-scope (unreviewed). Not a recall gain; this app was used to find engine gaps.
|
|
101
|
+
- Seen holdout `cr0hn/vulnerable-node` @ `d68f2a3c9277ece2c2feb9976052033bdecd7fbe`: 9/12 in-scope (unreviewed). Re-scored after the authorized NoSQL filter-value rule; not a recall gain.
|
|
102
|
+
- NodeGoat 5/7, Juice Shop 10/12, DVNA 9/10 (same unreviewed files; deps TPs need an OSV cache hit under `--offline-safe`).
|
|
103
|
+
|
|
104
|
+
Precision in [`BENCHMARK.md`](BENCHMARK.md) is self-triaged, unverified. Unsure counts on that run: NodeGoat 4, Juice Shop 39, DVNA 5, goof 4, vulnerable-node 29.
|
|
75
105
|
|
|
76
106
|
## Quick start
|
|
77
107
|
|
|
@@ -104,11 +134,14 @@ app.use(shieldGuard({ mode: 'monitor', authRoutes: ['/login'] }));
|
|
|
104
134
|
|
|
105
135
|
`npm run bench` reports `inspect()` p50/p95 (must stay under 1ms) and full Express request overhead p50/p95 with the middleware on the stack.
|
|
106
136
|
|
|
137
|
+
**Experimental response DLP** (off by default): `FULLSHIELD_EXPERIMENTAL_DLP=1` plus `shieldGuard({ dlp: { mode: 'monitor' | 'mask' | 'block' } })`. Without the flag, `shieldGuard` is inspect-only on the incoming request (unchanged). `mask` replaces matches; `block` returns HTTP 500 with `{ error: 'internal error' }`. Events go to `onEvent`. Bodies over 64 KB are skipped and counted. Prefer `monitor` until you have measured the benign corpus on your app.
|
|
138
|
+
|
|
107
139
|
## Commands
|
|
108
140
|
|
|
109
141
|
| Command | Purpose |
|
|
110
142
|
|---|---|
|
|
111
|
-
| `scan` | Run deps, secrets, sast, config. `--json`, `--sarif`, `--markdown`, `--html`, `--fail-on`, `--offline`, `--staged`, `--update-baseline`. `--ai explain` is **experimental** (off by default; explanation text only, never a verdict) |
|
|
143
|
+
| `scan` | Run deps, secrets, sast, config. `--json`, `--sarif`, `--markdown`, `--html`, `--fail-on`, `--offline`, `--staged`, `--update-baseline`. `--ai explain` is **experimental** (off by default; explanation text only, never a verdict). `--pr-summary` is **experimental** (`FULLSHIELD_EXPERIMENTAL_POLICY=1`): deterministic Markdown of new findings versus baseline for GitHub/GitLab PR comments (no AI; links to rule docs). |
|
|
144
|
+
| `fullshield.policy.json` | **Experimental** policy-as-code (`FULLSHIELD_EXPERIMENTAL_POLICY=1`): `requiredModules`, per-rule `severityOverrides`, `failOn` per branch, and `exceptions` that require `owner`, `reason`, and ISO `expires`. Expired exceptions fail CI. Exceptions without owner or expiry are rejected. |
|
|
112
145
|
| `fix` | Dry-run patches (unsafe suggestions included). `--apply` writes only `safe: true` patches on a clean git tree. `--unsafe` is required for `helmet()` insertion and secret → `process.env` replacement. `--force` skips files with uncommitted changes. `--ai` is **experimental**: drafts a verified, reviewable diff for findings without a rule-based fix (never auto-merged). |
|
|
113
146
|
| `init` | Config + GitHub/GitLab CI + pre-commit hook |
|
|
114
147
|
| `notify` | Slack / generic webhook / SMTP summary of the last scan |
|
|
@@ -125,6 +158,14 @@ app.use(shieldGuard({ mode: 'monitor', authRoutes: ['/login'] }));
|
|
|
125
158
|
| `iac` | **Experimental** light IaC (`FULLSHIELD_EXPERIMENTAL_IAC=1`): GHA, Terraform, Kubernetes, docker-compose. Not a full IaC scanner. |
|
|
126
159
|
| `agent` | **Experimental** remediation agent (`FULLSHIELD_EXPERIMENTAL_AGENT=1`). Mock/localhost GitHub API + local git remote only — never a real network. Never merges, never pushes to `main`/`master`. `--max-prs` (default 3). |
|
|
127
160
|
| `scan --changed` / `--since` | **Experimental** incremental scan (`FULLSHIELD_EXPERIMENTAL_PLATFORM=1`). |
|
|
161
|
+
| `scan --experimental-sast-gaps` | **Experimental** extra SAST sinks: ORM raw SQL, mongoose `$where`, `spawn('sh', ['-c'])`, execa `shell: true`, zx `$.raw` (`FULLSHIELD_EXPERIMENTAL_SAST_GAPS=1`). |
|
|
162
|
+
| `scan --experimental-frameworks` | **Experimental** Next.js / NestJS / Hono / Koa taint sources and AUTH (`FULLSHIELD_EXPERIMENTAL_FRAMEWORKS=1`). Default `scan` is unchanged. Runtime adapters: `fullshield/runtime/koa`, `fullshield/runtime/hono` (optional peers). |
|
|
163
|
+
| `scan --experimental-classes` | **Experimental** extra vuln classes: ReDoS, deser, XXE, upload, cookies, CSRF, mass assignment, timing compare, hash cost, GraphQL, trust-proxy (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`). Default `scan` is unchanged. |
|
|
164
|
+
| `scan --history` / `--bundle <dir>` / `--verify-secrets` | **Experimental** secrets advanced (`FULLSHIELD_EXPERIMENTAL_SECRETS_ADV=1` or `scan --experimental-secrets-adv`). History streams `git log -p` (first-introduced commit, still-at-HEAD). Bundle scans `dist/` `.next/static` `build/` for browser-exposed keys including `NEXT_PUBLIC_*`/`VITE_*`. `--verify-secrets` is never default and is **not** in generated CI templates; it needs `--i-own-these-credentials` and only hits read-only identity endpoints (AWS STS GetCallerIdentity, GitHub `GET /user`, Stripe `GET /v1/balance`, Slack `auth.test`). Results: `live` / `revoked` / `unknown`. The key is never logged. |
|
|
165
|
+
| `vex` | **Experimental** OpenVEX / CycloneDX VEX from reachability (`FULLSHIELD_EXPERIMENTAL_VEX=1`). `not_affected` + `vulnerable_code_not_in_execute_path` only when reachability is `not-imported`; otherwise `under_investigation`. Never guessed. Also enables worm/workflow indicators, unsafe `dependency-override` proposals, and a provenance coverage score. Apply writes `package.json` and prints `run npm install`; it does not edit the lockfile. |
|
|
166
|
+
| `scan --experimental-mcpcfg` | **Experimental** MCP client/server config rules `MCP-001`–`MCP-005` (`FULLSHIELD_EXPERIMENTAL_MCPCFG=1`). Default `scan` emits no `MCP-*` IDs. |
|
|
167
|
+
| `scan --include-home` | **Experimental** also scan MCP configs under the user home (`.cursor/mcp.json`, `.vscode/mcp.json`, `.mcp.json`, Claude Desktop). Requires `FULLSHIELD_EXPERIMENTAL_MCPCFG=1`. Never implied. |
|
|
168
|
+
| `host --experimental-host` | **Experimental** Linux-only OpenSSH exposure check (`FULLSHIELD_EXPERIMENTAL_HOST=1`). Reports `HOST-SSH-001`..`003` (version, no `LoginGraceTime 0`, auth timeout attempts). Refuses without `--experimental-host`. Default `scan` is unchanged. Skips with an info message on macOS, Windows, containers without sshd, and PaaS. **What it does NOT do:** FullShield does not stop OS or SSH exploits. |
|
|
128
169
|
|
|
129
170
|
Inline ignore (reason required): `// fullshield-ignore SAST-SQLI-001: reviewed`
|
|
130
171
|
|
|
@@ -134,7 +175,7 @@ See [`rules/README.md`](rules/README.md).
|
|
|
134
175
|
|
|
135
176
|
## Benchmarks
|
|
136
177
|
|
|
137
|
-
|
|
178
|
+
Public-app detection results (OWASP NodeGoat, Juice Shop, DVNA, seen holdouts `snyk-labs/nodejs-goof` and `cr0hn/vulnerable-node`) live in [`BENCHMARK.md`](BENCHMARK.md). Those ground-truth files are still `"reviewed": false`. Holdout numbers are listed under Known limitations and are on unreviewed ground truth. Precision in `BENCHMARK.md` is self-triaged, unverified. Self-authored fixture notes live in [`benchmarks/REPORT.md`](benchmarks/REPORT.md). Live Ollama smoke results (not CI) live in [`benchmarks/AI-REPORT.md`](benchmarks/AI-REPORT.md).
|
|
138
179
|
|
|
139
180
|
### Cursor MCP (`mcp.json`)
|
|
140
181
|
|
|
@@ -152,6 +193,22 @@ Honest NodeGoat / DVNA results (what we found and what we missed) live in [`benc
|
|
|
152
193
|
|
|
153
194
|
`propose_fix` returns a diff only. It never writes your tree.
|
|
154
195
|
|
|
196
|
+
## Developer commands
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
npx fullshield doctor
|
|
200
|
+
npx fullshield doctor --offline
|
|
201
|
+
npx fullshield scan --debug
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`doctor` prints the Node version, lockfile, cache directory, experimental flags that are set to `1`, whether `fullshield.config.json` parses, and whether OSV is allowed to use the network. `--offline` skips that network. CLI failures use `FS-E###` codes. The message stays the same. A stack is printed only with `--debug`.
|
|
205
|
+
|
|
206
|
+
Console output shows the highest 10 findings, then `N more — use --json`. Each line has the file, the finding message, a fix line, and `rules/<rule-id>.md`.
|
|
207
|
+
|
|
208
|
+
Experimental taint propagation (`FULLSHIELD_EXPERIMENTAL_TAINT_V2=1`) and the parse pool (`FULLSHIELD_PARSE_POOL=1`) are off by default. Symbol reachability runs only when an OSV advisory lists `affected_functions`. Advisories without that list keep the import-level class.
|
|
209
|
+
|
|
210
|
+
`COMPARISON.md` is not linked here. The mapping has not been reviewed. A local semgrep / osv-scanner / npm audit run is in that file, and it is not a recall claim. Benchmark numbers stay in [`BENCHMARK.md`](BENCHMARK.md) with the unreviewed label.
|
|
211
|
+
|
|
155
212
|
See [`SECURITY.md`](SECURITY.md), [`CONTRIBUTING.md`](CONTRIBUTING.md), and [`RELEASING.md`](RELEASING.md).
|
|
156
213
|
|
|
157
214
|
## License
|