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/rules/README.md
CHANGED
|
@@ -16,8 +16,10 @@ negative counterpart in `test/fixtures/clean-app`.
|
|
|
16
16
|
| [SECRET-PRIVKEY-001](SECRET-PRIVKEY-001.md) | PEM private key |
|
|
17
17
|
| [SECRET-DBURL-001](SECRET-DBURL-001.md) | DB URL with inline password |
|
|
18
18
|
| [SECRET-JWT-001](SECRET-JWT-001.md) | Hardcoded JWT secret |
|
|
19
|
+
| [SECRET-SESSION-001](SECRET-SESSION-001.md) | Hardcoded express-session secret |
|
|
19
20
|
| [SECRET-ENTROPY-001](SECRET-ENTROPY-001.md) | High-entropy assignment (low confidence) |
|
|
20
21
|
| [SECRET-ENVFILE-001](SECRET-ENVFILE-001.md) | Committed `.env` file |
|
|
22
|
+
| [SECRET-BUNDLE-001](SECRET-BUNDLE-001.md) | Secret-shaped `NEXT_PUBLIC_`/`VITE_` value in a client bundle (experimental) |
|
|
21
23
|
|
|
22
24
|
## Dependencies
|
|
23
25
|
|
|
@@ -27,13 +29,15 @@ negative counterpart in `test/fixtures/clean-app`.
|
|
|
27
29
|
| [DEPS-TYPOSQUAT-001](DEPS-TYPOSQUAT-001.md) | Direct dep 1 edit from a popular name |
|
|
28
30
|
| [DEPS-DEPRECATED-001](DEPS-DEPRECATED-001.md) | Lockfile `deprecated` field |
|
|
29
31
|
| [DEPS-NOLOCK-001](DEPS-NOLOCK-001.md) | package.json without a lockfile |
|
|
32
|
+
| [SUPPLY-WORM-001](SUPPLY-WORM-001.md) | Experimental: token read + publish/registry/GitHub/workflow write |
|
|
33
|
+
| [SUPPLY-WORKFLOW-001](SUPPLY-WORKFLOW-001.md) | Experimental: dep writes `.github/` / git hooks / CI config |
|
|
30
34
|
|
|
31
35
|
## SAST
|
|
32
36
|
|
|
33
37
|
| ID | What |
|
|
34
38
|
|---|---|
|
|
35
39
|
| [SAST-SQLI-001](SAST-SQLI-001.md) | Tainted SQL concatenation |
|
|
36
|
-
| [SAST-NOSQLI-001](SAST-NOSQLI-001.md) | Raw `req.body`/`query` as Mongo filter |
|
|
40
|
+
| [SAST-NOSQLI-001](SAST-NOSQLI-001.md) | Raw `req.body`/`query` as Mongo filter, or a request field as a filter value |
|
|
37
41
|
| [SAST-CMDI-001](SAST-CMDI-001.md) | Tainted shell `exec` |
|
|
38
42
|
| [SAST-PATH-001](SAST-PATH-001.md) | Tainted path without resolve+prefix |
|
|
39
43
|
| [SAST-EVAL-001](SAST-EVAL-001.md) | `eval` / `new Function` / `vm` / `*.eval` |
|
|
@@ -44,6 +48,16 @@ negative counterpart in `test/fixtures/clean-app`.
|
|
|
44
48
|
| [SAST-PROTO-001](SAST-PROTO-001.md) | Unguarded recursive merge |
|
|
45
49
|
| [SAST-XSS-001](SAST-XSS-001.md) | HTML `res.send` or `<%-` / `{{{` |
|
|
46
50
|
| [SAST-AUTH-001](SAST-AUTH-001.md) | Data route with no auth middleware |
|
|
51
|
+
| [SAST-REDOS-001](SAST-REDOS-001.md) | Nested/overlapping regex quantifiers on tainted input (experimental) |
|
|
52
|
+
| [SAST-DESER-001](SAST-DESER-001.md) | Unsafe deserialize (`node-serialize`, js-yaml, funcster, serialize-javascript) (experimental) |
|
|
53
|
+
| [SAST-XXE-001](SAST-XXE-001.md) | XML external entities on tainted input (experimental) |
|
|
54
|
+
| [SAST-UPLOAD-001](SAST-UPLOAD-001.md) | multer/busboy/formidable without size/type, or `originalname` path (experimental) |
|
|
55
|
+
| [SAST-COOKIE-001](SAST-COOKIE-001.md) | Cookies missing httpOnly/secure/sameSite; hardcoded session secret (experimental) |
|
|
56
|
+
| [SAST-CSRF-001](SAST-CSRF-001.md) | Cookie-auth state-changing route without CSRF and sameSite (experimental) |
|
|
57
|
+
| [SAST-MASSASSIGN-001](SAST-MASSASSIGN-001.md) | `Model.create/update(req.body)` / `Object.assign(user, req.body)` (experimental) |
|
|
58
|
+
| [SAST-TIMING-001](SAST-TIMING-001.md) | Secret/token/HMAC compared with `===` not `timingSafeEqual` (experimental) |
|
|
59
|
+
| [SAST-HASH-001](SAST-HASH-001.md) | bcrypt/argon2/pbkdf2 below OWASP minimums (experimental) |
|
|
60
|
+
| [SAST-GQL-001](SAST-GQL-001.md) | GraphQL introspection in production, no depth limit, tainted resolver args (experimental) |
|
|
47
61
|
|
|
48
62
|
## Config
|
|
49
63
|
|
|
@@ -54,7 +68,39 @@ negative counterpart in `test/fixtures/clean-app`.
|
|
|
54
68
|
| [CONFIG-CORS-001](CONFIG-CORS-001.md) | `origin:'*'` + `credentials:true` |
|
|
55
69
|
| [CONFIG-RATELIMIT-001](CONFIG-RATELIMIT-001.md) | Auth route without a limiter |
|
|
56
70
|
| [CONFIG-ERRSTACK-001](CONFIG-ERRSTACK-001.md) | `err.stack` sent to the client |
|
|
71
|
+
| [CONFIG-DIRLIST-001](CONFIG-DIRLIST-001.md) | `serveIndex()` directory listing |
|
|
57
72
|
| [CONFIG-DOCKER-LATEST-001](CONFIG-DOCKER-LATEST-001.md) | `FROM *:latest` |
|
|
58
73
|
| [CONFIG-DOCKER-ROOT-001](CONFIG-DOCKER-ROOT-001.md) | Dockerfile with no `USER` |
|
|
59
74
|
| [CONFIG-DOCKER-ENV-001](CONFIG-DOCKER-ENV-001.md) | `ENV *_SECRET=` / `*_KEY=` |
|
|
60
75
|
| [CONFIG-INSTALLSCRIPT-001](CONFIG-INSTALLSCRIPT-001.md) | Lockfile `hasInstallScript` |
|
|
76
|
+
| [CONFIG-TRUSTPROXY-001](CONFIG-TRUSTPROXY-001.md) | `trust proxy: true` with IP rate limiting (experimental) |
|
|
77
|
+
|
|
78
|
+
## Runtime DLP (experimental)
|
|
79
|
+
|
|
80
|
+
Requires `FULLSHIELD_EXPERIMENTAL_DLP=1` and `shieldGuard({ dlp: { mode } })`. Not a scan module.
|
|
81
|
+
|
|
82
|
+
| ID | What |
|
|
83
|
+
|---|---|
|
|
84
|
+
| [DLP-SECRET-001](DLP-SECRET-001.md) | Secret pattern in a JSON/text response |
|
|
85
|
+
| [DLP-STACK-001](DLP-STACK-001.md) | Stack trace in a response |
|
|
86
|
+
| [DLP-INTERNAL-001](DLP-INTERNAL-001.md) | Internal IP or hostname in a response |
|
|
87
|
+
| [DLP-CARD-001](DLP-CARD-001.md) | Luhn-valid card number in a response |
|
|
88
|
+
| [DLP-PII-001](DLP-PII-001.md) | Many emails on a one-email route |
|
|
89
|
+
|
|
90
|
+
## MCP config (experimental, `FULLSHIELD_EXPERIMENTAL_MCPCFG=1`)
|
|
91
|
+
|
|
92
|
+
| ID | What |
|
|
93
|
+
|---|---|
|
|
94
|
+
| [MCP-001](MCP-001.md) | Inline secret in MCP client config |
|
|
95
|
+
| [MCP-002](MCP-002.md) | Unpinned `npx` / `@latest` MCP server |
|
|
96
|
+
| [MCP-003](MCP-003.md) | Shell/exec or unrestricted fs-write tool, no allowlist |
|
|
97
|
+
| [MCP-004](MCP-004.md) | Tool-description instruction injection / hidden / ZWC |
|
|
98
|
+
| [MCP-005](MCP-005.md) | Remote MCP over plaintext `http://` |
|
|
99
|
+
|
|
100
|
+
## Host (experimental, `fullshield host --experimental-host`)
|
|
101
|
+
|
|
102
|
+
| ID | What |
|
|
103
|
+
|---|---|
|
|
104
|
+
| [HOST-SSH-001](HOST-SSH-001.md) | OpenSSH version in the CVE-2024-6387 range |
|
|
105
|
+
| [HOST-SSH-002](HOST-SSH-002.md) | Vulnerable OpenSSH without `LoginGraceTime 0` |
|
|
106
|
+
| [HOST-SSH-003](HOST-SSH-003.md) | Possible SSH authentication-timeout attempts |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-COOKIE-001
|
|
2
|
+
|
|
3
|
+
**Session/auth cookies without `httpOnly`, `secure`, or `sameSite`.** A hardcoded `express-session` secret is `SECRET-SESSION-001` only, so this rule does not add a second finding. CWE-614 / CWE-1004.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Set `httpOnly: true`, `secure: true`, and `sameSite: 'lax'` or `'strict'`. Load the session secret from the environment and rotate it.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-CSRF-001
|
|
2
|
+
|
|
3
|
+
**Cookie-authenticated POST/PUT/PATCH/DELETE with no CSRF protection and `sameSite` not `strict`/`lax`.** CWE-352.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Use `csurf`, `csrf-csrf`, `csrf-sync`, `lusca`, or `@fastify/csrf-protection`, including `app.use` in `app.js` when routes live in another file. Set cookie `sameSite` to `lax` or `strict`.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-DESER-001
|
|
2
|
+
|
|
3
|
+
**Tainted input into `node-serialize.unserialize`, unsafe `js-yaml` schemas, `funcster`, or `serialize-javascript` eval paths.** CWE-502.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Do not deserialize untrusted data into objects or code. Use JSON with a schema, or `js-yaml` `JSON_SCHEMA` / `safeLoad`. Never `eval` serialized JavaScript.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-GQL-001
|
|
2
|
+
|
|
3
|
+
**GraphQL introspection enabled in production, no depth/complexity limit, or tainted resolver args reaching sinks.** CWE-770 / CWE-200.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Disable introspection in production. Add `graphql-depth-limit` (or query complexity). Validate resolver arguments before they reach SQL/shell/eval.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-HASH-001
|
|
2
|
+
|
|
3
|
+
**`bcrypt` cost < 10, `argon2` below OWASP memory/time minimums, or `pbkdf2` iterations below OWASP for the digest.** CWE-916.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Use bcrypt cost ≥ 10, argon2id at least OWASP `m=19456,t=2` or `m=12288,t=3`, and PBKDF2-HMAC-SHA256 ≥ 600,000 iterations (SHA-512 ≥ 210,000).
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-MASSASSIGN-001
|
|
2
|
+
|
|
3
|
+
**`Model.create(req.body)` / `update(req.body)` / `Object.assign(user, req.body)` without an allowlist or schema.** CWE-915.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Pick allowed fields, or parse with zod/joi/yup before create/update. Never pass `req.body` straight into an ORM or `Object.assign`.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
package/rules/SAST-NOSQLI-001.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# SAST-NOSQLI-001
|
|
2
2
|
|
|
3
|
-
**req.body/req.query passed as a Mongo filter.** CWE-943.
|
|
3
|
+
**req.body/req.query passed as a Mongo filter, a tainted `$where` string, or request input used as a Mongo filter value without coercion, schema validation, or mongo-sanitize.** CWE-943.
|
|
4
|
+
|
|
5
|
+
Constructed filters such as `{ password: req.body.password }` are medium confidence. They are not flagged when the value is `String()` / `Number()` / `Boolean()`, a zod/joi/yup `.parse`, `mongo-sanitize`, or `express-mongo-sanitize` middleware.
|
|
6
|
+
|
|
7
|
+
The sink must be MongoDB or mongoose: the file imports `mongodb`/`mongoose`, or the receiver is a known collection (`collection.find`, `*Collection.update`) or a PascalCase mongoose model (`User.find`). Sequelize, Prisma, TypeORM, Knex, Drizzle, and unknown receivers are not flagged.
|
|
4
8
|
|
|
5
9
|
## Remediation
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
Coerce or schema-validate each filter field; never pass the request object through. Prefer `mongo-sanitize` or an allow-listed filter object.
|
|
8
12
|
|
|
9
13
|
See `SECURITY_SCANNER_SPEC.md` §4 and the fixtures under `test/fixtures/`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-REDOS-001
|
|
2
|
+
|
|
3
|
+
**Tainted input matched against a regex with nested or overlapping quantifiers.** CWE-1333.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`). Detection is static star-height analysis; user regexes are never executed.
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Rewrite the pattern so quantified groups do not contain further quantifiers or overlapping alternations. Prefer a parser or an allow-list over a complex regex.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-TIMING-001
|
|
2
|
+
|
|
3
|
+
**Secrets, tokens, or HMACs compared with `===` / `==` instead of `crypto.timingSafeEqual`.** CWE-208.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Compare MACs and tokens with `crypto.timingSafeEqual` on equal-length Buffers.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-UPLOAD-001
|
|
2
|
+
|
|
3
|
+
**`multer` / `busboy` / `formidable` without a file size limit or type filter; uploaded `originalname` used in a filesystem path.** CWE-434.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Set `limits.fileSize` (or `maxFileSize`) and a MIME/extension allow-list. Store files under a generated name, never `originalname`.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SAST-XXE-001
|
|
2
|
+
|
|
3
|
+
**XML parsers with external entities enabled (`libxmljs` `noent: true`, `xml2js` custom entities) on tainted input.** CWE-611.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_CLASSES=1`).
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Disable external entity expansion (`noent: false`). Do not register custom SAX entities. Prefer a parser that rejects DTDs.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 29.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SECRET-BUNDLE-001
|
|
2
|
+
|
|
3
|
+
**Secret-shaped value in a client bundle.** CWE-798.
|
|
4
|
+
|
|
5
|
+
`NEXT_PUBLIC_*` and `VITE_*` values are inlined into the browser. Experimental: `scan --bundle` with `FULLSHIELD_EXPERIMENTAL_SECRETS_ADV=1`.
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Do not prefix secrets with `NEXT_PUBLIC_` or `VITE_`. Keep credentials server-side. Rotate any key that was bundled.
|
|
10
|
+
|
|
11
|
+
See `SECURITY_SCANNER_SPEC.md` §24 Phase 30.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# SECRET-SESSION-001
|
|
2
|
+
|
|
3
|
+
**Hardcoded `express-session` `secret` string.** CWE-798.
|
|
4
|
+
|
|
5
|
+
## Remediation
|
|
6
|
+
|
|
7
|
+
Load the session secret from the environment (for example `process.env.SESSION_SECRET`) and rotate any value that was committed.
|
|
8
|
+
|
|
9
|
+
See `SECURITY_SCANNER_SPEC.md` §4 and the fixtures under `test/fixtures/`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# SUPPLY-WORKFLOW-001
|
|
2
|
+
|
|
3
|
+
**Dependency writes GitHub workflows, git hooks, or CI config (static).** CWE-829.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_VEX=1`). Flags install scripts or package code that write `.github/`, `.git/hooks/`, or CI config files. Critical. Never executes packages.
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Do not install the package until the write is reviewed. Prefer `--ignore-scripts` and pin CI config so dependencies cannot persist a workflow.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# SUPPLY-WORM-001
|
|
2
|
+
|
|
3
|
+
**Self-replicating worm indicators in dependency code (static).** CWE-506.
|
|
4
|
+
|
|
5
|
+
Experimental (`FULLSHIELD_EXPERIMENTAL_VEX=1`). Flags package sources that read npm/GitHub tokens (`~/.npmrc`, `NPM_TOKEN`, `GITHUB_TOKEN`) **and** `npm publish`, a registry PUT, GitHub API repo/workflow creation, or writing `.github/workflows/*`. Critical. Never executes packages.
|
|
6
|
+
|
|
7
|
+
## Remediation
|
|
8
|
+
|
|
9
|
+
Do not install the package. Remove it from the lockfile, delete `node_modules`, reinstall, and rotate npm/GitHub tokens on machines that installed it.
|