vanara 0.1.0 → 0.2.0
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/README.md +14 -0
- package/bin/vanara.js +66 -2
- package/catalog/agents/api-designer/AGENT.md +140 -0
- package/catalog/agents/api-designer/examples/openapi-snippet.yaml +130 -0
- package/catalog/agents/api-designer/examples/review-notes.md +61 -0
- package/catalog/agents/api-designer/references/contract-and-openapi.md +82 -0
- package/catalog/agents/api-designer/references/design-checklist.md +68 -0
- package/catalog/agents/api-designer/references/versioning-and-evolution.md +63 -0
- package/catalog/agents/api-designer/scripts/lint-openapi.mjs +180 -0
- package/catalog/agents/code-reviewer/AGENT.md +168 -0
- package/catalog/agents/code-reviewer/examples/pr-comment-template.md +48 -0
- package/catalog/agents/code-reviewer/examples/sample-review-output.md +38 -0
- package/catalog/agents/code-reviewer/references/review-checklist.md +46 -0
- package/catalog/agents/code-reviewer/references/security-review.md +62 -0
- package/catalog/agents/code-reviewer/references/severity-rubric.md +63 -0
- package/catalog/agents/code-reviewer/scripts/review-guard.mjs +102 -0
- package/catalog/agents/debugger/AGENT.md +112 -0
- package/catalog/agents/debugger/examples/postmortem-template.md +59 -0
- package/catalog/agents/debugger/examples/repro-template.md +50 -0
- package/catalog/agents/debugger/references/bisection.md +63 -0
- package/catalog/agents/debugger/references/debugging-method.md +57 -0
- package/catalog/agents/debugger/references/observability.md +61 -0
- package/catalog/agents/debugger/scripts/parse-stacktrace.mjs +109 -0
- package/catalog/agents/pr-summarizer/AGENT.md +156 -0
- package/catalog/agents/pr-summarizer/examples/sample-pr-summary.md +46 -0
- package/catalog/agents/pr-summarizer/examples/weak-vs-strong-summary.md +68 -0
- package/catalog/agents/pr-summarizer/references/identifying-risk.md +68 -0
- package/catalog/agents/pr-summarizer/references/reading-the-diff.md +68 -0
- package/catalog/agents/pr-summarizer/references/test-plan-and-missing-tests.md +64 -0
- package/catalog/agents/pr-summarizer/references/writing-the-summary.md +63 -0
- package/catalog/agents/pr-summarizer/scripts/diff-risk.mjs +111 -0
- package/catalog/agents/refactoring-specialist/AGENT.md +185 -0
- package/catalog/agents/refactoring-specialist/examples/before-after.md +91 -0
- package/catalog/agents/refactoring-specialist/examples/refactor-plan.md +97 -0
- package/catalog/agents/refactoring-specialist/references/large-scale-refactor.md +50 -0
- package/catalog/agents/refactoring-specialist/references/safe-workflow.md +75 -0
- package/catalog/agents/refactoring-specialist/references/smell-catalog.md +76 -0
- package/catalog/agents/refactoring-specialist/scripts/detect-smells.mjs +150 -0
- package/catalog/agents/security-auditor/AGENT.md +168 -0
- package/catalog/agents/security-auditor/examples/audit-report.md +68 -0
- package/catalog/agents/security-auditor/examples/finding-template.md +31 -0
- package/catalog/agents/security-auditor/references/audit-workflow.md +116 -0
- package/catalog/agents/security-auditor/references/severity-and-reporting.md +56 -0
- package/catalog/agents/security-auditor/references/vuln-classes.md +129 -0
- package/catalog/agents/security-auditor/scripts/scan-secrets.mjs +86 -0
- package/catalog/agents/technical-writer/AGENT.md +163 -0
- package/catalog/agents/technical-writer/examples/how-to-example.md +76 -0
- package/catalog/agents/technical-writer/examples/reference-example.md +75 -0
- package/catalog/agents/technical-writer/references/clarity-and-style.md +52 -0
- package/catalog/agents/technical-writer/references/diataxis-and-doc-types.md +61 -0
- package/catalog/agents/technical-writer/references/structure-and-maintenance.md +59 -0
- package/catalog/agents/technical-writer/scripts/readability.mjs +103 -0
- package/catalog/agents/test-author/AGENT.md +177 -0
- package/catalog/agents/test-author/examples/test-plan.md +54 -0
- package/catalog/agents/test-author/examples/unit-test-example.ts +55 -0
- package/catalog/agents/test-author/references/aaa-and-naming.md +75 -0
- package/catalog/agents/test-author/references/mocking-and-fixtures.md +94 -0
- package/catalog/agents/test-author/references/test-types-and-pyramid.md +60 -0
- package/catalog/agents/test-author/scripts/check-coverage.mjs +145 -0
- package/catalog/agents/threat-modeler/AGENT.md +163 -0
- package/catalog/agents/threat-modeler/examples/dfd-example.md +55 -0
- package/catalog/agents/threat-modeler/examples/threat-model.md +81 -0
- package/catalog/agents/threat-modeler/references/dfd-and-trust-boundaries.md +76 -0
- package/catalog/agents/threat-modeler/references/mitigation-catalog.md +77 -0
- package/catalog/agents/threat-modeler/references/stride-method.md +68 -0
- package/catalog/agents/threat-modeler/scripts/stride-checklist.mjs +183 -0
- package/catalog/agents/vuln-scanner/AGENT.md +146 -0
- package/catalog/agents/vuln-scanner/examples/finding-template.md +57 -0
- package/catalog/agents/vuln-scanner/examples/scan-report.md +49 -0
- package/catalog/agents/vuln-scanner/references/remediation-and-severity.md +68 -0
- package/catalog/agents/vuln-scanner/references/scan-types-and-tools.md +83 -0
- package/catalog/agents/vuln-scanner/references/triage-and-false-positives.md +73 -0
- package/catalog/agents/vuln-scanner/scripts/parse-scan-results.mjs +133 -0
- package/catalog/packs/code-review-pack/PACK.md +100 -0
- package/catalog/packs/code-review-pack/examples/pr-flow-example.md +50 -0
- package/catalog/packs/code-review-pack/references/rollout-guide.md +40 -0
- package/catalog/packs/security-pack/PACK.md +128 -0
- package/catalog/packs/security-pack/examples/owasp-vuln-triage-and-fix.md +66 -0
- package/catalog/packs/security-pack/examples/threat-model-then-audit.md +77 -0
- package/catalog/packs/security-pack/references/choosing-the-right-tool.md +37 -0
- package/catalog/packs/security-pack/references/threat-model-to-remediation.md +69 -0
- package/catalog/packs/security-pack/references/workflow.md +56 -0
- package/catalog/skills/api-pagination/SKILL.md +83 -0
- package/catalog/skills/api-pagination/examples/keyset-queries.sql +38 -0
- package/catalog/skills/api-pagination/examples/response-shapes.md +51 -0
- package/catalog/skills/api-pagination/references/consuming-pages.md +39 -0
- package/catalog/skills/api-pagination/references/counting-and-totals.md +41 -0
- package/catalog/skills/api-pagination/references/cursor-encoding.md +34 -0
- package/catalog/skills/api-pagination/scripts/check-cursor.mjs +51 -0
- package/catalog/skills/caching-strategies/SKILL.md +180 -0
- package/catalog/skills/caching-strategies/examples/http-cache-headers.md +82 -0
- package/catalog/skills/caching-strategies/examples/redis-cache-aside.js +110 -0
- package/catalog/skills/caching-strategies/references/cache-patterns.md +93 -0
- package/catalog/skills/caching-strategies/references/eviction-and-ttl.md +67 -0
- package/catalog/skills/caching-strategies/references/invalidation-and-stampede.md +99 -0
- package/catalog/skills/caching-strategies/scripts/ttl-jitter.mjs +99 -0
- package/catalog/skills/conventional-commits/SKILL.md +135 -0
- package/catalog/skills/conventional-commits/examples/commit-examples.md +60 -0
- package/catalog/skills/conventional-commits/examples/commitlint.config.js +31 -0
- package/catalog/skills/conventional-commits/references/breaking-changes-semver.md +50 -0
- package/catalog/skills/conventional-commits/references/scoping-commits.md +42 -0
- package/catalog/skills/conventional-commits/references/spec.md +78 -0
- package/catalog/skills/conventional-commits/scripts/lint-commit.mjs +97 -0
- package/catalog/skills/database-migrations/SKILL.md +137 -0
- package/catalog/skills/database-migrations/examples/add-column-safe.sql +34 -0
- package/catalog/skills/database-migrations/examples/migration-config.example.json +27 -0
- package/catalog/skills/database-migrations/references/expand-contract.md +74 -0
- package/catalog/skills/database-migrations/references/rollback-and-safety.md +71 -0
- package/catalog/skills/database-migrations/references/zero-downtime-changes.md +84 -0
- package/catalog/skills/database-migrations/scripts/check-migration-reversible.mjs +139 -0
- package/catalog/skills/error-handling-patterns/SKILL.md +136 -0
- package/catalog/skills/error-handling-patterns/examples/express-error-middleware.js +93 -0
- package/catalog/skills/error-handling-patterns/examples/result-pattern.ts +81 -0
- package/catalog/skills/error-handling-patterns/references/error-taxonomy.md +91 -0
- package/catalog/skills/error-handling-patterns/references/language-patterns.md +102 -0
- package/catalog/skills/error-handling-patterns/references/retry-and-backoff.md +76 -0
- package/catalog/skills/error-handling-patterns/scripts/lint-empty-catch.mjs +113 -0
- package/catalog/skills/git-collaboration-workflows/SKILL.md +104 -0
- package/catalog/skills/git-collaboration-workflows/examples/branch-protection.md +48 -0
- package/catalog/skills/git-collaboration-workflows/examples/pr-review-playbook.md +48 -0
- package/catalog/skills/git-collaboration-workflows/references/branching-models.md +52 -0
- package/catalog/skills/git-collaboration-workflows/references/history-hygiene.md +44 -0
- package/catalog/skills/git-collaboration-workflows/scripts/check-branch-hygiene.mjs +62 -0
- package/catalog/skills/owasp-top10/SKILL.md +112 -0
- package/catalog/skills/owasp-top10/examples/sql-injection-fix.md +33 -0
- package/catalog/skills/owasp-top10/examples/xss-fix.md +27 -0
- package/catalog/skills/owasp-top10/references/access-control.md +36 -0
- package/catalog/skills/owasp-top10/references/crypto-failures.md +29 -0
- package/catalog/skills/owasp-top10/references/injection.md +39 -0
- package/catalog/skills/owasp-top10/references/ssrf-and-design.md +35 -0
- package/catalog/skills/owasp-top10/scripts/scan-injection.mjs +73 -0
- package/catalog/skills/prompt-engineering/SKILL.md +100 -0
- package/catalog/skills/prompt-engineering/examples/classification-prompt.md +29 -0
- package/catalog/skills/prompt-engineering/examples/extraction-prompt.md +25 -0
- package/catalog/skills/prompt-engineering/references/prompt-injection.md +34 -0
- package/catalog/skills/prompt-engineering/references/structured-output.md +34 -0
- package/catalog/skills/prompt-engineering/references/techniques.md +33 -0
- package/catalog/skills/prompt-engineering/scripts/validate-output.mjs +71 -0
- package/catalog/skills/readme-writing/SKILL.md +176 -0
- package/catalog/skills/readme-writing/examples/README.template.md +73 -0
- package/catalog/skills/readme-writing/examples/good-readme-example.md +95 -0
- package/catalog/skills/readme-writing/references/anatomy.md +107 -0
- package/catalog/skills/readme-writing/references/maintenance.md +68 -0
- package/catalog/skills/readme-writing/references/writing-style.md +79 -0
- package/catalog/skills/readme-writing/scripts/lint-readme.mjs +117 -0
- package/catalog/skills/refactoring-patterns/SKILL.md +178 -0
- package/catalog/skills/refactoring-patterns/examples/extract-function.before-after.js +57 -0
- package/catalog/skills/refactoring-patterns/examples/replace-conditional-with-polymorphism.before-after.ts +63 -0
- package/catalog/skills/refactoring-patterns/references/code-smells.md +92 -0
- package/catalog/skills/refactoring-patterns/references/refactoring-catalog.md +78 -0
- package/catalog/skills/refactoring-patterns/references/safe-workflow.md +74 -0
- package/catalog/skills/refactoring-patterns/scripts/detect-smells.mjs +137 -0
- package/catalog/skills/rest-api-design/SKILL.md +147 -0
- package/catalog/skills/rest-api-design/examples/error-envelope.json +26 -0
- package/catalog/skills/rest-api-design/examples/orders-api.openapi.yaml +106 -0
- package/catalog/skills/rest-api-design/references/error-handling.md +43 -0
- package/catalog/skills/rest-api-design/references/pagination.md +41 -0
- package/catalog/skills/rest-api-design/references/status-codes.md +32 -0
- package/catalog/skills/rest-api-design/references/versioning.md +31 -0
- package/catalog/skills/rest-api-design/scripts/check-envelope.mjs +62 -0
- package/catalog/skills/secrets-management/SKILL.md +134 -0
- package/catalog/skills/secrets-management/examples/env-example.md +41 -0
- package/catalog/skills/secrets-management/examples/rotation-runbook.md +50 -0
- package/catalog/skills/secrets-management/references/leak-response.md +47 -0
- package/catalog/skills/secrets-management/references/rotation.md +46 -0
- package/catalog/skills/secrets-management/references/secret-managers.md +52 -0
- package/catalog/skills/secrets-management/scripts/detect-hardcoded.mjs +96 -0
- package/catalog/skills/secure-auth/SKILL.md +135 -0
- package/catalog/skills/secure-auth/examples/auth-flow.md +60 -0
- package/catalog/skills/secure-auth/examples/jwt-verify.ts +89 -0
- package/catalog/skills/secure-auth/references/auth-attacks.md +76 -0
- package/catalog/skills/secure-auth/references/oauth2-oidc.md +65 -0
- package/catalog/skills/secure-auth/references/password-hashing.md +70 -0
- package/catalog/skills/secure-auth/references/sessions-vs-jwt.md +69 -0
- package/catalog/skills/secure-auth/scripts/check-password-policy.mjs +114 -0
- package/catalog/skills/sql-index-tuning/SKILL.md +171 -0
- package/catalog/skills/sql-index-tuning/examples/explain-walkthrough.sql +91 -0
- package/catalog/skills/sql-index-tuning/examples/index-ddl.sql +67 -0
- package/catalog/skills/sql-index-tuning/references/btree-internals.md +58 -0
- package/catalog/skills/sql-index-tuning/references/composite-and-covering.md +60 -0
- package/catalog/skills/sql-index-tuning/references/reading-explain.md +54 -0
- package/catalog/skills/sql-index-tuning/references/selectivity-and-cardinality.md +62 -0
- package/catalog/skills/sql-index-tuning/scripts/suggest-index.mjs +213 -0
- package/catalog/skills/test-plan-design/SKILL.md +154 -0
- package/catalog/skills/test-plan-design/examples/test-case-template.md +45 -0
- package/catalog/skills/test-plan-design/examples/test-plan.md +57 -0
- package/catalog/skills/test-plan-design/references/case-design.md +79 -0
- package/catalog/skills/test-plan-design/references/risk-prioritization.md +58 -0
- package/catalog/skills/test-plan-design/references/test-pyramid.md +58 -0
- package/catalog/skills/test-plan-design/scripts/coverage-gaps.mjs +109 -0
- package/free-tier.json +15 -0
- package/package.json +15 -4
- package/premium-index.json +1 -0
- package/src/config.js +31 -1
- package/src/license.js +143 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: secrets-management
|
|
3
|
+
description: Handle secrets safely across the lifecycle — keep them out of source, load from env or a secret manager, scope to least privilege, encrypt in transit and at rest, rotate on a schedule, and respond fast when one leaks. Deep reference with runbooks, examples, and a runnable leak scanner.
|
|
4
|
+
type: skill
|
|
5
|
+
version: 2.0.0
|
|
6
|
+
updated: 2026-06-29
|
|
7
|
+
---
|
|
8
|
+
# Secrets Management
|
|
9
|
+
|
|
10
|
+
A secret committed to source control is **already compromised** — assume it is public the moment it lands
|
|
11
|
+
in history, even on a private repo. Git history, CI logs, container layers, backups, and forks all retain
|
|
12
|
+
it. The only safe response is rotation, not deletion. This skill is the deep reference for keeping secrets
|
|
13
|
+
out of code, supplying them safely at runtime, and reacting correctly when one escapes. Heavy detail lives
|
|
14
|
+
in `references/`; copy-paste material in `examples/`; a runnable leak scanner in `scripts/`.
|
|
15
|
+
|
|
16
|
+
## Mental model
|
|
17
|
+
|
|
18
|
+
Treat a secret as **runtime configuration**, never as code. Code is committed, reviewed, copied, and
|
|
19
|
+
shipped to laptops; secrets must not ride along. Separate three concerns and never collapse them:
|
|
20
|
+
|
|
21
|
+
| Concern | Question | Answer |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Storage | where does the truth live? | a secret manager (Vault, cloud KMS/Secrets Manager) |
|
|
24
|
+
| Delivery | how does the app get it? | injected env var or a fetch at boot, over TLS |
|
|
25
|
+
| Lifecycle | how does it change? | rotation on a schedule + on suspected leak |
|
|
26
|
+
|
|
27
|
+
The application code should only ever *read* a secret from its environment — it should never know the
|
|
28
|
+
storage backend, the rotation cadence, or the raw value's origin. That decoupling is what lets you rotate
|
|
29
|
+
a leaked key with **zero code changes**.
|
|
30
|
+
|
|
31
|
+
## 1. Keep secrets out of source
|
|
32
|
+
|
|
33
|
+
1. Never hardcode keys, tokens, passwords, connection strings, or private keys in source — not even
|
|
34
|
+
"temporarily". Temporary hardcodes are how most leaks happen.
|
|
35
|
+
2. Commit a placeholder template (`.env.example`) with **fake** values and key names only; add the real
|
|
36
|
+
`.env` to `.gitignore` before the first commit.
|
|
37
|
+
3. Add a secret scanner to pre-commit hooks **and** CI so a leak is caught before it reaches the remote.
|
|
38
|
+
Run `scripts/detect-hardcoded.mjs` over diffs or files as a zero-dependency gate.
|
|
39
|
+
4. Load and validate required secrets at startup; fail fast with a clear message naming the missing var.
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
// Read from the environment; never embed the value. Validate at boot.
|
|
43
|
+
const required = ['DATABASE_URL', 'STRIPE_SECRET_KEY', 'JWT_SIGNING_KEY'];
|
|
44
|
+
const missing = required.filter((k) => !process.env[k]);
|
|
45
|
+
if (missing.length) {
|
|
46
|
+
throw new Error(`Missing required secrets: ${missing.join(', ')}`);
|
|
47
|
+
}
|
|
48
|
+
const stripeKey = process.env.STRIPE_SECRET_KEY; // if this leaks, rotate the value — no code change
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 2. Environment variables vs secret managers
|
|
52
|
+
|
|
53
|
+
Env vars are the universal **delivery** mechanism, but they are not a secure **store** — they leak via
|
|
54
|
+
`/proc`, crash dumps, child processes, and accidental `console.log(process.env)`. For anything beyond a
|
|
55
|
+
single dev machine, the source of truth belongs in a manager.
|
|
56
|
+
|
|
57
|
+
| Approach | Good for | Watch out for |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `.env` file (gitignored) | local dev only | easy to commit by accident; no rotation, no audit |
|
|
60
|
+
| Platform env vars (CI/PaaS) | small apps, single-tenant | visible to anyone with dashboard access; static |
|
|
61
|
+
| Secret manager (Vault, AWS/GCP/Azure) | production, teams | needs auth bootstrap; adds a runtime dependency |
|
|
62
|
+
| Cloud KMS (envelope encryption) | encrypting data + secrets | key policy mistakes are silent until exploited |
|
|
63
|
+
|
|
64
|
+
A manager buys you **dynamic, short-lived credentials** (e.g. Vault issues a DB credential that auto-expires
|
|
65
|
+
in 1 hour), centralized **audit logs**, and **rotation without redeploys**. See
|
|
66
|
+
`references/secret-managers.md` for the comparison and bootstrap-auth patterns.
|
|
67
|
+
|
|
68
|
+
## 3. Least privilege & short-lived credentials
|
|
69
|
+
|
|
70
|
+
- Scope every credential to the **minimum** resources and actions it needs; a build token does not need
|
|
71
|
+
prod database write access.
|
|
72
|
+
- Prefer **short-lived** dynamic credentials over long-lived static keys. A credential that lives 15
|
|
73
|
+
minutes limits the blast radius of a leak to 15 minutes.
|
|
74
|
+
- Use **distinct** credentials per service and per environment so revocation is surgical, not a blackout.
|
|
75
|
+
- Never share one "god" key across services — you lose the ability to attribute and revoke.
|
|
76
|
+
|
|
77
|
+
## 4. Encryption in transit and at rest
|
|
78
|
+
|
|
79
|
+
- **In transit:** fetch secrets only over TLS; reject plaintext transports. Never pass secrets as URL
|
|
80
|
+
query params (they land in logs, proxies, and browser history) — use headers or the request body.
|
|
81
|
+
- **At rest:** the manager encrypts its store; for data you encrypt yourself, use **envelope encryption**
|
|
82
|
+
(a KMS-held key encrypts per-record data keys) rather than a single static key in config.
|
|
83
|
+
- Keep secrets out of logs, error reports, and APM traces. Redact at the logging boundary — see the
|
|
84
|
+
scanner's redaction logic and `references/leak-response.md`.
|
|
85
|
+
|
|
86
|
+
## 5. Rotation
|
|
87
|
+
|
|
88
|
+
Rotation is the difference between "we had an incident" and "we had a non-event." Two triggers:
|
|
89
|
+
|
|
90
|
+
1. **Scheduled** — rotate on a fixed cadence (e.g. 90 days for static keys) so no credential is ancient.
|
|
91
|
+
2. **Reactive** — rotate **immediately** on any suspected exposure, no matter how minor it looks.
|
|
92
|
+
|
|
93
|
+
The safe pattern is **overlap**: provision the new credential, deploy it, verify, then revoke the old —
|
|
94
|
+
so there is no downtime window. The full step-by-step is in
|
|
95
|
+
[`references/rotation.md`](references/rotation.md) and a fill-in incident checklist is in
|
|
96
|
+
`examples/rotation-runbook.md`.
|
|
97
|
+
|
|
98
|
+
## Common pitfalls (failure modes)
|
|
99
|
+
|
|
100
|
+
- **"It's a private repo, so it's fine."** Forks, clones, CI caches, and future open-sourcing all expose
|
|
101
|
+
it. Private is not secret.
|
|
102
|
+
- **Deleting the commit instead of rotating.** `git rm` / force-push does not erase history from clones,
|
|
103
|
+
caches, and PR mirrors. The credential is burned — rotate it.
|
|
104
|
+
- **Logging the whole environment.** `console.log(process.env)` or dumping config on a crash ships every
|
|
105
|
+
secret to your log aggregator. Redact at the boundary.
|
|
106
|
+
- **Secrets in URLs.** Query-string tokens leak into access logs, referrer headers, and proxies.
|
|
107
|
+
- **One static key forever.** No rotation means a years-old leak is still valid today.
|
|
108
|
+
- **Over-scoped credentials.** A read-only reporting job holding admin keys turns a small leak into a
|
|
109
|
+
full compromise.
|
|
110
|
+
- **Checking `.env.example` with real values.** The template must contain only obviously fake placeholders.
|
|
111
|
+
|
|
112
|
+
## When NOT to use / trade-offs
|
|
113
|
+
|
|
114
|
+
- **A full secret manager for a solo side project** is overkill — gitignored `.env` plus a pre-commit
|
|
115
|
+
scanner is proportionate. Adopt a manager when you have a team, multiple environments, or compliance needs.
|
|
116
|
+
- **Short-lived dynamic credentials add operational complexity** (auth bootstrap, renewal, clock skew). If
|
|
117
|
+
your platform can't renew reliably, a rotated static key with tight scope may be the pragmatic choice.
|
|
118
|
+
- **Client-side secrets don't exist.** Anything shipped to a browser or mobile app is public; never put a
|
|
119
|
+
secret in front-end code — use a backend proxy or short-lived scoped tokens instead.
|
|
120
|
+
- **Encrypting everything** has key-management cost. Encrypt what's sensitive; don't build a KMS maze for
|
|
121
|
+
non-secret config.
|
|
122
|
+
|
|
123
|
+
## Files in this package
|
|
124
|
+
|
|
125
|
+
- `references/secret-managers.md` — env vars vs Vault/cloud managers, bootstrap auth, dynamic creds
|
|
126
|
+
- `references/rotation.md` — scheduled & reactive rotation, zero-downtime overlap pattern
|
|
127
|
+
- `references/leak-response.md` — what to do in the first hour after a leak; redaction guidance
|
|
128
|
+
- `examples/env-example.md` — a model `.env.example` with safe placeholders and `.gitignore` notes
|
|
129
|
+
- `examples/rotation-runbook.md` — fill-in incident/rotation checklist
|
|
130
|
+
- `scripts/detect-hardcoded.mjs` — zero-dep scanner that flags & redacts hardcoded secrets (`--selftest`)
|
|
131
|
+
|
|
132
|
+
Pairs with the `secure-auth` skill (where signing keys and session secrets come from), the `owasp-top10`
|
|
133
|
+
skill (A07 identification/auth failures, A02 cryptographic failures), and the `security-auditor` agent for
|
|
134
|
+
a full credential-handling review.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Model `.env.example`
|
|
2
|
+
|
|
3
|
+
Commit this template; never commit the real `.env`. Values here are **obviously fake placeholders** so a
|
|
4
|
+
scanner won't flag them and a human won't mistake them for real. Document every key the app needs.
|
|
5
|
+
|
|
6
|
+
```dotenv
|
|
7
|
+
# === Database ===
|
|
8
|
+
# Real value lives in the secret manager; this is the shape only.
|
|
9
|
+
DATABASE_URL=postgres://USER:PASSWORD@localhost:5432/appdb
|
|
10
|
+
|
|
11
|
+
# === Third-party APIs ===
|
|
12
|
+
STRIPE_SECRET_KEY=sk_test_REPLACE_ME_xxxxxxxxxxxxxxxx
|
|
13
|
+
SENDGRID_API_KEY=SG.REPLACE_ME.xxxxxxxxxxxxxxxxxxxxxxxx
|
|
14
|
+
|
|
15
|
+
# === Auth / signing ===
|
|
16
|
+
# Generate locally with: openssl rand -base64 32
|
|
17
|
+
JWT_SIGNING_KEY=REPLACE_WITH_RANDOM_32_BYTES
|
|
18
|
+
SESSION_SECRET=REPLACE_WITH_RANDOM_32_BYTES
|
|
19
|
+
|
|
20
|
+
# === Runtime config (not secret) ===
|
|
21
|
+
NODE_ENV=development
|
|
22
|
+
LOG_LEVEL=info
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## `.gitignore` (add before the first commit)
|
|
26
|
+
|
|
27
|
+
```gitignore
|
|
28
|
+
# Real secrets — never commit
|
|
29
|
+
.env
|
|
30
|
+
.env.local
|
|
31
|
+
.env.*.local
|
|
32
|
+
*.pem
|
|
33
|
+
*.key
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Rules
|
|
37
|
+
|
|
38
|
+
1. Keep `.env.example` in sync with the code — every `process.env.X` the app reads should appear here.
|
|
39
|
+
2. Placeholders must be clearly fake: `REPLACE_ME`, `sk_test_...`, `xxxx`. Never a redacted-but-real value.
|
|
40
|
+
3. The real `.env` is for local dev only. In CI/production, inject values from the secret manager.
|
|
41
|
+
4. Validate presence at startup (see `SKILL.md` §1) so a missing key fails fast with a named error.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Rotation / Leak Runbook (fill-in)
|
|
2
|
+
|
|
3
|
+
Copy this into your incident tracker and fill the blanks. The goal of the first hour is to make the leaked
|
|
4
|
+
credential useless — rotate first, investigate second.
|
|
5
|
+
|
|
6
|
+
## Incident header
|
|
7
|
+
|
|
8
|
+
- **Date / time detected (UTC):** ________
|
|
9
|
+
- **Detected by:** (scanner / human / third-party report) ________
|
|
10
|
+
- **Credential type:** (API key / DB password / signing key / deploy token / other) ________
|
|
11
|
+
- **Where it leaked:** (commit SHA / log / screenshot / paste / laptop) ________
|
|
12
|
+
- **Likely exposure window start:** ________
|
|
13
|
+
|
|
14
|
+
## Step 1 — Rotate / revoke (do this FIRST)
|
|
15
|
+
|
|
16
|
+
- [ ] Provision replacement credential
|
|
17
|
+
- [ ] Deploy / inject new value via secret manager
|
|
18
|
+
- [ ] Verify new credential works in production
|
|
19
|
+
- [ ] Revoke the leaked credential
|
|
20
|
+
- [ ] New credential id / version: ________
|
|
21
|
+
|
|
22
|
+
## Step 2 — Assess blast radius
|
|
23
|
+
|
|
24
|
+
- [ ] What can this credential do? ________
|
|
25
|
+
- [ ] Which environments / data are reachable? ________
|
|
26
|
+
- [ ] Could it have unlocked other secrets? (list) ________
|
|
27
|
+
|
|
28
|
+
## Step 3 — Check for abuse
|
|
29
|
+
|
|
30
|
+
- [ ] Pulled audit logs since exposure window: yes / no
|
|
31
|
+
- [ ] Unfamiliar IPs / regions / patterns found? ________
|
|
32
|
+
- [ ] Derived sessions/tokens revoked if needed: yes / n/a
|
|
33
|
+
|
|
34
|
+
## Step 4 — Notify
|
|
35
|
+
|
|
36
|
+
- [ ] Owning team notified
|
|
37
|
+
- [ ] Security / compliance notified (if required by policy)
|
|
38
|
+
|
|
39
|
+
## Step 5 — Cleanup (after rotation)
|
|
40
|
+
|
|
41
|
+
- [ ] Scrubbed value from git history (`git filter-repo`)
|
|
42
|
+
- [ ] Invalidated CI caches / mirrors
|
|
43
|
+
- [ ] Did NOT paste the raw secret into this ticket
|
|
44
|
+
|
|
45
|
+
## Step 6 — Prevent recurrence
|
|
46
|
+
|
|
47
|
+
- [ ] Added/tightened scanner rule that missed it
|
|
48
|
+
- [ ] Pre-commit + CI secret gate in place
|
|
49
|
+
- [ ] Post-incident timeline written
|
|
50
|
+
- [ ] Owner: ________ Due: ________
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Leak Response — The First Hour
|
|
2
|
+
|
|
3
|
+
When a secret leaks, speed beats blame. The credential is burned; your job is to make it useless before it
|
|
4
|
+
is abused. Deleting the commit is **not** a fix — history persists in clones, forks, CI caches, and mirrors.
|
|
5
|
+
|
|
6
|
+
## Immediate steps (in order)
|
|
7
|
+
|
|
8
|
+
1. **Rotate / revoke the credential first.** Generate a replacement and invalidate the leaked value. This
|
|
9
|
+
is the only action that actually closes the hole. Everything else is cleanup.
|
|
10
|
+
2. **Assess blast radius.** What could this credential do? Which environments, data, and actions? Use that
|
|
11
|
+
to set urgency and notification scope.
|
|
12
|
+
3. **Check for abuse.** Pull audit logs for the credential since the likely exposure time. Look for
|
|
13
|
+
unfamiliar IPs, regions, or access patterns.
|
|
14
|
+
4. **Contain.** If abuse is suspected, also revoke sessions/tokens derived from it and rotate any secrets
|
|
15
|
+
it could have unlocked (a leaked deploy key may have read other secrets).
|
|
16
|
+
5. **Notify** the owning team and, if required, security/compliance — per your incident policy.
|
|
17
|
+
6. **Purge history** only after rotation: scrub the value from git history (e.g. `git filter-repo`),
|
|
18
|
+
invalidate CI caches, and force-update. Treat this as hygiene, not remediation.
|
|
19
|
+
7. **Post-incident:** add or tighten the scanner rule that missed it; add a pre-commit/CI gate if one was
|
|
20
|
+
absent; write up the timeline.
|
|
21
|
+
|
|
22
|
+
## Redaction (prevent the next leak in logs)
|
|
23
|
+
|
|
24
|
+
Most leaks after the first are in logs. Redact at the logging boundary so secrets never reach the
|
|
25
|
+
aggregator:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const SECRET_KEYS = /(pass(word)?|secret|token|api[-_]?key|authorization)/i;
|
|
29
|
+
function redact(obj) {
|
|
30
|
+
if (typeof obj !== 'object' || obj === null) return obj;
|
|
31
|
+
const out = Array.isArray(obj) ? [] : {};
|
|
32
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
33
|
+
out[k] = SECRET_KEYS.test(k) ? '[REDACTED]' : redact(v);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
logger.info('request', redact(context)); // never log raw process.env or auth headers
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The package scanner (`scripts/detect-hardcoded.mjs`) redacts matched values in its output for the same
|
|
41
|
+
reason — never echo the secret you just found.
|
|
42
|
+
|
|
43
|
+
## Don'ts
|
|
44
|
+
|
|
45
|
+
- Don't quietly delete the commit and move on — the credential is still valid until rotated.
|
|
46
|
+
- Don't paste the leaked value into a ticket, chat, or email to "show" the leak.
|
|
47
|
+
- Don't wait for confirmation of abuse before rotating; rotation is cheap, a breach is not.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Secret Rotation
|
|
2
|
+
|
|
3
|
+
Rotation is the control that turns a leak into a non-event. A credential that is rotated regularly and can
|
|
4
|
+
be rotated *fast* limits how long any exposure is useful.
|
|
5
|
+
|
|
6
|
+
## Two triggers
|
|
7
|
+
|
|
8
|
+
1. **Scheduled rotation** — replace credentials on a fixed cadence so none becomes ancient.
|
|
9
|
+
- Long-lived static keys: every 30–90 days.
|
|
10
|
+
- Signing keys (JWT, sessions): rotate with overlap; keep the previous key valid for verification until
|
|
11
|
+
all issued tokens expire.
|
|
12
|
+
- Prefer **short-lived dynamic credentials** so "rotation" is automatic and continuous.
|
|
13
|
+
2. **Reactive rotation** — rotate **immediately** on any suspected exposure: a key in a commit, a leaked
|
|
14
|
+
log, a laptop loss, a departing employee, or a third-party breach. Do not wait to "confirm" — rotate first.
|
|
15
|
+
|
|
16
|
+
## Zero-downtime overlap pattern
|
|
17
|
+
|
|
18
|
+
Never rotate by deleting the old credential first — that causes an outage. Overlap instead:
|
|
19
|
+
|
|
20
|
+
1. **Provision** a new credential alongside the old one (both valid).
|
|
21
|
+
2. **Deploy / inject** the new value (update the secret manager; let instances pick it up).
|
|
22
|
+
3. **Verify** the new credential works in production (health check, canary).
|
|
23
|
+
4. **Revoke** the old credential.
|
|
24
|
+
5. **Confirm** nothing broke; check error rates and audit logs for use of the revoked key.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
old key: valid ============================x (revoked at step 4)
|
|
28
|
+
new key: valid =========================================>
|
|
29
|
+
^deploy ^verify ^safe to revoke old
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For **signing keys**, the overlap must outlast the longest token lifetime: publish both keys (by key id),
|
|
33
|
+
sign with the new one, accept either for verification, and drop the old key only after every token signed
|
|
34
|
+
with it has expired.
|
|
35
|
+
|
|
36
|
+
## KMS / envelope-encryption key rotation
|
|
37
|
+
|
|
38
|
+
When you encrypt data yourself, rotate the **key-encryption key** (KEK) in KMS without re-encrypting every
|
|
39
|
+
record: data is encrypted with per-record data keys, which are themselves encrypted by the KEK. Rotating the
|
|
40
|
+
KEK re-wraps data keys lazily. Keep old KEK versions enabled for decryption until re-wrap completes.
|
|
41
|
+
|
|
42
|
+
## Automate it
|
|
43
|
+
|
|
44
|
+
Manual rotation is rotation that doesn't happen. Use the manager's native rotation (AWS rotation Lambdas,
|
|
45
|
+
Vault leases, GCP versions) so the cadence is enforced by infrastructure, not a calendar reminder. See
|
|
46
|
+
`leak-response.md` for the reactive path and `examples/rotation-runbook.md` for a fill-in checklist.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Secret Managers — Env Vars vs Vault vs Cloud KMS
|
|
2
|
+
|
|
3
|
+
Where the *truth* of a secret lives. Environment variables deliver secrets to a process; they do not
|
|
4
|
+
securely store them. For anything past a single laptop, the source of truth belongs in a manager.
|
|
5
|
+
|
|
6
|
+
## Why env vars are not a store
|
|
7
|
+
|
|
8
|
+
Env vars are convenient and universal, but:
|
|
9
|
+
- They are inherited by **child processes** — a shelled-out command sees every secret.
|
|
10
|
+
- They appear in `/proc/<pid>/environ`, crash dumps, and many APM/observability agents.
|
|
11
|
+
- They are trivially leaked by `console.log(process.env)` or an error reporter that captures context.
|
|
12
|
+
- They are **static** — no rotation, no expiry, no audit trail of who read what.
|
|
13
|
+
|
|
14
|
+
Use them as the *delivery* channel (the app reads `process.env.X`), but populate them from a manager.
|
|
15
|
+
|
|
16
|
+
## Options compared
|
|
17
|
+
|
|
18
|
+
| Manager | Model | Strengths | Costs / caveats |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| HashiCorp Vault | central server, dynamic secrets | short-lived DB/cloud creds, leasing, rich audit | run/operate a server; auth bootstrap |
|
|
21
|
+
| AWS Secrets Manager / SSM | managed, IAM-scoped | native rotation Lambdas, IAM policies | AWS-only; per-secret cost |
|
|
22
|
+
| GCP Secret Manager | managed, IAM-scoped | versioning, IAM, CMEK | GCP-only |
|
|
23
|
+
| Azure Key Vault | managed, RBAC + keys | HSM-backed keys, certs + secrets | Azure-only |
|
|
24
|
+
| Cloud KMS | key custody, not blob store | envelope encryption of your own data | you still store the ciphertext somewhere |
|
|
25
|
+
|
|
26
|
+
## The bootstrap-auth problem
|
|
27
|
+
|
|
28
|
+
A manager removes secrets from your repo, but the app still needs **one** credential to authenticate to the
|
|
29
|
+
manager. Solve it with platform identity, not another static secret:
|
|
30
|
+
- **Workload identity / IAM roles** — the cloud assigns the running instance an identity; no key to store.
|
|
31
|
+
- **Vault auth methods** — Kubernetes service-account JWT, AWS IAM, or AppRole with a short-lived secret-id.
|
|
32
|
+
- **OIDC federation** in CI — the pipeline exchanges a signed OIDC token for short-lived cloud creds; no
|
|
33
|
+
long-lived keys in CI settings.
|
|
34
|
+
|
|
35
|
+
The goal: the only thing on disk is an *identity*, and the actual secrets are fetched at runtime over TLS.
|
|
36
|
+
|
|
37
|
+
## Dynamic, short-lived credentials
|
|
38
|
+
|
|
39
|
+
The biggest win of a real manager is **generated-on-demand** credentials:
|
|
40
|
+
```
|
|
41
|
+
# Conceptual: Vault issues a DB credential that auto-expires
|
|
42
|
+
GET vault/database/creds/app-readonly
|
|
43
|
+
-> { username: "v-app-ro-7f3a", password: "<generated>", lease_duration: 3600 }
|
|
44
|
+
```
|
|
45
|
+
The credential lives one hour, is unique to this request, and is revoked automatically. A leak is bounded
|
|
46
|
+
by the lease, and every issuance is in the audit log.
|
|
47
|
+
|
|
48
|
+
## Choosing
|
|
49
|
+
|
|
50
|
+
- Solo / hobby: gitignored `.env` + a pre-commit scanner. A manager is overkill.
|
|
51
|
+
- Team / multi-env / compliance: a managed secret manager with IAM-scoped access and rotation.
|
|
52
|
+
- Encrypting your own data at rest: KMS envelope encryption (see `rotation.md` for key rotation).
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Zero-dependency scanner: flags likely hardcoded secrets in a code string and
|
|
3
|
+
// redacts the matched value so the finding can be logged safely.
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// node detect-hardcoded.mjs '<code string>' # scan a literal string
|
|
7
|
+
// echo '<code>' | node detect-hardcoded.mjs # scan from stdin
|
|
8
|
+
// node detect-hardcoded.mjs --selftest # built-in leaky + clean cases
|
|
9
|
+
//
|
|
10
|
+
// Exit codes: 0 = no secrets found (clean) ; 1 = secret(s) found OR selftest failed.
|
|
11
|
+
|
|
12
|
+
// Each rule: a name and a regex with the sensitive value in capture group 1 (or 0).
|
|
13
|
+
const RULES = [
|
|
14
|
+
['AWS access key id', /\b(AKIA[0-9A-Z]{16})\b/],
|
|
15
|
+
['Stripe secret key', /\b(sk_(?:live|test)_[A-Za-z0-9]{16,})\b/],
|
|
16
|
+
['Google API key', /\b(AIza[0-9A-Za-z_\-]{35})\b/],
|
|
17
|
+
['Slack token', /\b(xox[baprs]-[0-9A-Za-z-]{10,})\b/],
|
|
18
|
+
['GitHub token', /\b(gh[pousr]_[0-9A-Za-z]{20,})\b/],
|
|
19
|
+
['JWT', /\b(eyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,})\b/],
|
|
20
|
+
['Private key block', /(-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----)/],
|
|
21
|
+
// Generic: an assignment to a secret-ish name with a non-trivial inline string literal.
|
|
22
|
+
['Hardcoded password/secret assignment',
|
|
23
|
+
/\b(?:password|passwd|secret|api[_-]?key|token|access[_-]?key)\b\s*[:=]\s*["'`]([^"'`\s]{6,})["'`]/i],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Mask all but a short prefix so output never echoes a real secret.
|
|
27
|
+
function redactValue(v) {
|
|
28
|
+
if (v.length <= 6) return '*'.repeat(v.length);
|
|
29
|
+
return v.slice(0, 4) + '*'.repeat(Math.max(4, v.length - 4));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function scan(code) {
|
|
33
|
+
const findings = [];
|
|
34
|
+
for (const [name, re] of RULES) {
|
|
35
|
+
const g = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');
|
|
36
|
+
let m;
|
|
37
|
+
while ((m = g.exec(code)) !== null) {
|
|
38
|
+
const value = m[1] ?? m[0];
|
|
39
|
+
findings.push({ rule: name, redacted: redactValue(value) });
|
|
40
|
+
if (m.index === g.lastIndex) g.lastIndex++; // avoid zero-width loop
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return findings;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function report(label, code) {
|
|
47
|
+
const findings = scan(code);
|
|
48
|
+
if (findings.length === 0) {
|
|
49
|
+
console.log(`OK ${label}: no hardcoded secrets detected`);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
console.error(`FAIL ${label}: ${findings.length} potential secret(s):`);
|
|
53
|
+
for (const f of findings) console.error(` - ${f.rule}: ${f.redacted}`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function selftest() {
|
|
58
|
+
// All sample values are OBVIOUSLY FAKE — fixed filler, not real credentials.
|
|
59
|
+
const leaky = [
|
|
60
|
+
"const k = 'AKIAIOSFODNN7EXAMPLE';",
|
|
61
|
+
"stripe = 'sk_test_0000000000000000FAKE';",
|
|
62
|
+
"googleKey = 'AIzaBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';",
|
|
63
|
+
"const password = 'hunter2hunter2';",
|
|
64
|
+
"apiKey: 'tok_FAKEFAKEFAKE'",
|
|
65
|
+
];
|
|
66
|
+
const clean = [
|
|
67
|
+
"const k = process.env.AWS_ACCESS_KEY_ID;",
|
|
68
|
+
"const password = process.env.DB_PASSWORD; // loaded from env",
|
|
69
|
+
"// example: set STRIPE_SECRET_KEY in your secret manager",
|
|
70
|
+
"const limit = 20; const name = 'orders';",
|
|
71
|
+
];
|
|
72
|
+
let pass = true;
|
|
73
|
+
leaky.forEach((c, i) => {
|
|
74
|
+
const found = scan(c).length > 0;
|
|
75
|
+
const ok = found === true;
|
|
76
|
+
pass &&= ok;
|
|
77
|
+
console.log(`${ok ? 'OK' : 'XX'} leaky[${i}]: ${found ? 'flagged' : 'missed'} (expected flagged)`);
|
|
78
|
+
});
|
|
79
|
+
clean.forEach((c, i) => {
|
|
80
|
+
const found = scan(c).length > 0;
|
|
81
|
+
const ok = found === false;
|
|
82
|
+
pass &&= ok;
|
|
83
|
+
console.log(`${ok ? 'OK' : 'XX'} clean[${i}]: ${found ? 'flagged' : 'clean'} (expected clean)`);
|
|
84
|
+
});
|
|
85
|
+
console.log(pass ? 'selftest: PASS' : 'selftest: FAIL');
|
|
86
|
+
process.exit(pass ? 0 : 1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const arg = process.argv[2];
|
|
90
|
+
if (arg === '--selftest') selftest();
|
|
91
|
+
else if (arg) process.exit(report('input', arg) ? 0 : 1);
|
|
92
|
+
else {
|
|
93
|
+
let buf = '';
|
|
94
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
95
|
+
process.stdin.on('end', () => process.exit(report('stdin', buf) ? 0 : 1));
|
|
96
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: secure-auth
|
|
3
|
+
description: Implement authentication securely — authentication vs authorization, password hashing (argon2id/bcrypt), sessions vs JWT (storage, expiry, refresh, revocation), MFA, OAuth2/OIDC flows, and defenses against credential stuffing, session fixation, and CSRF. Worked examples + a runnable password-policy check.
|
|
4
|
+
type: skill
|
|
5
|
+
version: 2.0.0
|
|
6
|
+
updated: 2026-06-29
|
|
7
|
+
---
|
|
8
|
+
# Secure Authentication
|
|
9
|
+
|
|
10
|
+
Authentication is the front door to your system, and it is the single control attackers probe hardest.
|
|
11
|
+
The goal of this skill is **not** to teach you to invent a clever scheme — it is to help you assemble
|
|
12
|
+
well-understood primitives correctly, because almost every real-world breach in this area comes from a
|
|
13
|
+
broken assembly of good parts, not from cracked cryptography. Heavy detail lives in `references/`;
|
|
14
|
+
copy-paste material in `examples/`; a runnable policy check in `scripts/`.
|
|
15
|
+
|
|
16
|
+
## Mental model
|
|
17
|
+
|
|
18
|
+
Three distinct questions get muddled constantly. Keep them separate:
|
|
19
|
+
|
|
20
|
+
| Question | Concern | Wrong answer looks like |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Who are you? | **Authentication** (login, password, MFA) | trusting a client-supplied `user_id` |
|
|
23
|
+
| What may you do? | **Authorization** (roles, ownership, scopes) | checking auth but not ownership (IDOR) |
|
|
24
|
+
| How do we remember you? | **Session management** (cookies/tokens) | long-lived tokens you can't revoke |
|
|
25
|
+
|
|
26
|
+
A request can be perfectly *authenticated* and still be an attack if you skip *authorization*. The most
|
|
27
|
+
common API vulnerability — Broken Object Level Authorization — is exactly this: a logged-in user reads
|
|
28
|
+
`/accounts/124` when they only own `124`'s neighbor. Always check ownership server-side, never trust an
|
|
29
|
+
identifier the client could change.
|
|
30
|
+
|
|
31
|
+
## 1. Password storage: slow hash, never plaintext
|
|
32
|
+
|
|
33
|
+
The non-negotiable rule: **never store a recoverable password.** Store a one-way hash produced by a
|
|
34
|
+
*deliberately slow* algorithm so that a stolen database is expensive to crack offline.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import argon2 from 'argon2';
|
|
38
|
+
|
|
39
|
+
// Registration / password change — argon2id is the current default recommendation.
|
|
40
|
+
const hash = await argon2.hash(password, { type: argon2.argon2id });
|
|
41
|
+
// Store `hash` (it embeds the salt + cost params). NEVER store `password`.
|
|
42
|
+
|
|
43
|
+
// Login — constant-time verify; argon2 reads cost params from the stored hash.
|
|
44
|
+
const ok = await argon2.verify(hash, attempt);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use **argon2id** (preferred) or **bcrypt** (battle-tested, fine if argon2 isn't available). Never use
|
|
48
|
+
fast general-purpose hashes (`MD5`, `SHA-256`) — a GPU computes billions of those per second, so a leak
|
|
49
|
+
becomes a mass account takeover within hours. Always salt (argon2/bcrypt do this for you), and validate
|
|
50
|
+
the password against a policy and a breached-password list *before* hashing — run
|
|
51
|
+
`scripts/check-password-policy.mjs --selftest` to see the kind of check that belongs at this boundary.
|
|
52
|
+
The full parameter-tuning guidance is in [references/password-hashing.md](references/password-hashing.md).
|
|
53
|
+
|
|
54
|
+
## 2. Sessions vs JWT: how you remember a logged-in user
|
|
55
|
+
|
|
56
|
+
Two dominant models, with a real trade-off around **revocation**:
|
|
57
|
+
|
|
58
|
+
- **Server-side sessions** — a random opaque session ID in an `HttpOnly; Secure; SameSite` cookie; state
|
|
59
|
+
lives server-side (DB/Redis). Revocation is trivial: delete the row. This is the safe default for
|
|
60
|
+
classic web apps.
|
|
61
|
+
- **Stateless JWTs** — a signed token the client carries. Scales without shared session storage, but you
|
|
62
|
+
**cannot un-issue a signed token** before it expires. Mitigate with short-lived access tokens
|
|
63
|
+
(5–15 min) plus a revocable, rotating refresh token kept server-side.
|
|
64
|
+
|
|
65
|
+
```http
|
|
66
|
+
Set-Cookie: session=9f2c...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Storage matters enormously: a token in `localStorage` is readable by any XSS payload, so prefer
|
|
70
|
+
`HttpOnly` cookies (which JavaScript cannot read) over web storage for anything that authenticates a
|
|
71
|
+
request. The complete comparison — expiry, refresh rotation, reuse detection, and revocation strategies —
|
|
72
|
+
is in [references/sessions-vs-jwt.md](references/sessions-vs-jwt.md).
|
|
73
|
+
|
|
74
|
+
## 3. MFA and step-up
|
|
75
|
+
|
|
76
|
+
A password alone is a single point of failure against phishing and credential stuffing. Offer a second
|
|
77
|
+
factor and require it for sensitive actions (password change, payouts):
|
|
78
|
+
|
|
79
|
+
- **TOTP** (authenticator app) — good baseline, phishable but cheap.
|
|
80
|
+
- **WebAuthn / passkeys** — phishing-resistant, hardware-backed; prefer where you can.
|
|
81
|
+
- **SMS** — weakest (SIM-swap), use only as a fallback.
|
|
82
|
+
|
|
83
|
+
Treat MFA as *step-up*: don't force it on every request, escalate it when risk rises.
|
|
84
|
+
|
|
85
|
+
## 4. OAuth2 / OIDC: delegated auth done right
|
|
86
|
+
|
|
87
|
+
When you offload login to a provider (Google, an identity platform), use **OAuth2 Authorization Code
|
|
88
|
+
flow with PKCE** for web and mobile/SPA clients. Do not use the implicit flow (deprecated) and never the
|
|
89
|
+
resource-owner password flow for third-party login. OIDC layers an identity `id_token` on top of OAuth2's
|
|
90
|
+
access token. Validate the `id_token` signature, `iss`, `aud`, and `exp`, and use the `state` parameter to
|
|
91
|
+
defend against CSRF on the callback. See [references/oauth2-oidc.md](references/oauth2-oidc.md) for the
|
|
92
|
+
full flow diagrams and validation checklist, and `examples/auth-flow.md` for an annotated walk-through.
|
|
93
|
+
|
|
94
|
+
## 5. Account recovery
|
|
95
|
+
|
|
96
|
+
Recovery flows are a favorite bypass — they are authentication's back door. Make reset tokens
|
|
97
|
+
**single-use, time-limited (e.g. 15–30 min), and high-entropy**, store only their hash, and invalidate
|
|
98
|
+
all active sessions on a successful reset. Critically, return the **same response** whether or not the
|
|
99
|
+
email exists, and keep timing uniform, so the endpoint can't be used to enumerate accounts.
|
|
100
|
+
|
|
101
|
+
## Common pitfalls (failure modes)
|
|
102
|
+
|
|
103
|
+
- **Fast/unsalted hashes** (`SHA-256`, `MD5`) — instantly crackable at scale after a DB leak.
|
|
104
|
+
- **JWT you can't revoke** — a long-lived access token stays valid after logout/compromise; keep them short.
|
|
105
|
+
- **Tokens in `localStorage`** — any XSS exfiltrates them; use `HttpOnly` cookies.
|
|
106
|
+
- **Session fixation** — not rotating the session ID on login lets an attacker pre-seed a known ID; always
|
|
107
|
+
regenerate the session identifier at the moment privilege changes.
|
|
108
|
+
- **Missing CSRF protection** on cookie-authenticated state-changing requests — use `SameSite` cookies
|
|
109
|
+
plus anti-CSRF tokens. See [references/auth-attacks.md](references/auth-attacks.md).
|
|
110
|
+
- **Account enumeration** — different responses/timing for "wrong password" vs "no such user."
|
|
111
|
+
- **No rate limiting / lockout** — credential stuffing walks straight in; throttle and add MFA.
|
|
112
|
+
- **Authn without authz** — checking *who* but not *whether they own this object* (IDOR/BOLA).
|
|
113
|
+
|
|
114
|
+
## When NOT to roll your own / trade-offs
|
|
115
|
+
|
|
116
|
+
Prefer a vetted identity provider or library over hand-built auth whenever you can. Building it yourself
|
|
117
|
+
means owning password reset, MFA enrollment, session revocation, breach monitoring, and audit trails
|
|
118
|
+
*forever*. Reach for a managed IdP (OIDC provider, an auth platform) when you want SSO, social login, or
|
|
119
|
+
compliance offload; the cost is vendor lock-in and per-MAU pricing. Build in-house only when you have
|
|
120
|
+
hard data-residency constraints or genuinely unusual requirements — and even then, wrap proven libraries
|
|
121
|
+
(argon2, a maintained OAuth2 client) rather than primitives. Stateless JWT trades easy revocation for
|
|
122
|
+
horizontal scale; if you rarely need to scale auth across regions, server-side sessions are simpler and
|
|
123
|
+
safer.
|
|
124
|
+
|
|
125
|
+
## Files in this package
|
|
126
|
+
|
|
127
|
+
- `references/password-hashing.md` — argon2id vs bcrypt, cost tuning, pepper, rehash-on-login
|
|
128
|
+
- `references/sessions-vs-jwt.md` — cookies vs tokens, expiry, refresh rotation, revocation
|
|
129
|
+
- `references/oauth2-oidc.md` — Authorization Code + PKCE, token validation, OIDC claims
|
|
130
|
+
- `references/auth-attacks.md` — credential stuffing, session fixation, CSRF, enumeration + defenses
|
|
131
|
+
- `examples/jwt-verify.ts` — dependency-light JWT verification with the right checks
|
|
132
|
+
- `examples/auth-flow.md` — annotated login + refresh + OAuth2 callback walk-through
|
|
133
|
+
- `scripts/check-password-policy.mjs` — runnable policy check (length, classes, breached list); `--selftest`
|
|
134
|
+
|
|
135
|
+
Pairs with `owasp-top10`, `secrets-management`, `rest-api-design`, `audit-logging`, and `gdpr-compliance`.
|