vanara 0.1.0 → 0.3.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.
Files changed (195) hide show
  1. package/README.md +14 -0
  2. package/bin/vanara.js +66 -2
  3. package/catalog/agents/api-designer/AGENT.md +156 -0
  4. package/catalog/agents/api-designer/examples/openapi-snippet.yaml +130 -0
  5. package/catalog/agents/api-designer/examples/review-notes.md +61 -0
  6. package/catalog/agents/api-designer/references/contract-and-openapi.md +82 -0
  7. package/catalog/agents/api-designer/references/design-checklist.md +68 -0
  8. package/catalog/agents/api-designer/references/versioning-and-evolution.md +63 -0
  9. package/catalog/agents/api-designer/scripts/lint-openapi.mjs +180 -0
  10. package/catalog/agents/code-reviewer/AGENT.md +184 -0
  11. package/catalog/agents/code-reviewer/examples/pr-comment-template.md +48 -0
  12. package/catalog/agents/code-reviewer/examples/sample-review-output.md +38 -0
  13. package/catalog/agents/code-reviewer/references/review-checklist.md +46 -0
  14. package/catalog/agents/code-reviewer/references/security-review.md +62 -0
  15. package/catalog/agents/code-reviewer/references/severity-rubric.md +63 -0
  16. package/catalog/agents/code-reviewer/scripts/review-guard.mjs +102 -0
  17. package/catalog/agents/debugger/AGENT.md +128 -0
  18. package/catalog/agents/debugger/examples/postmortem-template.md +59 -0
  19. package/catalog/agents/debugger/examples/repro-template.md +50 -0
  20. package/catalog/agents/debugger/references/bisection.md +63 -0
  21. package/catalog/agents/debugger/references/debugging-method.md +57 -0
  22. package/catalog/agents/debugger/references/observability.md +61 -0
  23. package/catalog/agents/debugger/scripts/parse-stacktrace.mjs +109 -0
  24. package/catalog/agents/pr-summarizer/AGENT.md +172 -0
  25. package/catalog/agents/pr-summarizer/examples/sample-pr-summary.md +46 -0
  26. package/catalog/agents/pr-summarizer/examples/weak-vs-strong-summary.md +68 -0
  27. package/catalog/agents/pr-summarizer/references/identifying-risk.md +68 -0
  28. package/catalog/agents/pr-summarizer/references/reading-the-diff.md +68 -0
  29. package/catalog/agents/pr-summarizer/references/test-plan-and-missing-tests.md +64 -0
  30. package/catalog/agents/pr-summarizer/references/writing-the-summary.md +63 -0
  31. package/catalog/agents/pr-summarizer/scripts/diff-risk.mjs +111 -0
  32. package/catalog/agents/refactoring-specialist/AGENT.md +201 -0
  33. package/catalog/agents/refactoring-specialist/examples/before-after.md +91 -0
  34. package/catalog/agents/refactoring-specialist/examples/refactor-plan.md +97 -0
  35. package/catalog/agents/refactoring-specialist/references/large-scale-refactor.md +50 -0
  36. package/catalog/agents/refactoring-specialist/references/safe-workflow.md +75 -0
  37. package/catalog/agents/refactoring-specialist/references/smell-catalog.md +76 -0
  38. package/catalog/agents/refactoring-specialist/scripts/detect-smells.mjs +150 -0
  39. package/catalog/agents/security-auditor/AGENT.md +184 -0
  40. package/catalog/agents/security-auditor/examples/audit-report.md +68 -0
  41. package/catalog/agents/security-auditor/examples/finding-template.md +31 -0
  42. package/catalog/agents/security-auditor/references/audit-workflow.md +116 -0
  43. package/catalog/agents/security-auditor/references/severity-and-reporting.md +56 -0
  44. package/catalog/agents/security-auditor/references/vuln-classes.md +129 -0
  45. package/catalog/agents/security-auditor/scripts/scan-secrets.mjs +86 -0
  46. package/catalog/agents/technical-writer/AGENT.md +179 -0
  47. package/catalog/agents/technical-writer/examples/how-to-example.md +76 -0
  48. package/catalog/agents/technical-writer/examples/reference-example.md +75 -0
  49. package/catalog/agents/technical-writer/references/clarity-and-style.md +52 -0
  50. package/catalog/agents/technical-writer/references/diataxis-and-doc-types.md +61 -0
  51. package/catalog/agents/technical-writer/references/structure-and-maintenance.md +59 -0
  52. package/catalog/agents/technical-writer/scripts/readability.mjs +103 -0
  53. package/catalog/agents/test-author/AGENT.md +193 -0
  54. package/catalog/agents/test-author/examples/test-plan.md +54 -0
  55. package/catalog/agents/test-author/examples/unit-test-example.ts +55 -0
  56. package/catalog/agents/test-author/references/aaa-and-naming.md +75 -0
  57. package/catalog/agents/test-author/references/mocking-and-fixtures.md +94 -0
  58. package/catalog/agents/test-author/references/test-types-and-pyramid.md +60 -0
  59. package/catalog/agents/test-author/scripts/check-coverage.mjs +145 -0
  60. package/catalog/agents/threat-modeler/AGENT.md +179 -0
  61. package/catalog/agents/threat-modeler/examples/dfd-example.md +55 -0
  62. package/catalog/agents/threat-modeler/examples/threat-model.md +81 -0
  63. package/catalog/agents/threat-modeler/references/dfd-and-trust-boundaries.md +76 -0
  64. package/catalog/agents/threat-modeler/references/mitigation-catalog.md +77 -0
  65. package/catalog/agents/threat-modeler/references/stride-method.md +68 -0
  66. package/catalog/agents/threat-modeler/scripts/stride-checklist.mjs +183 -0
  67. package/catalog/agents/vuln-scanner/AGENT.md +162 -0
  68. package/catalog/agents/vuln-scanner/examples/finding-template.md +57 -0
  69. package/catalog/agents/vuln-scanner/examples/scan-report.md +49 -0
  70. package/catalog/agents/vuln-scanner/references/remediation-and-severity.md +68 -0
  71. package/catalog/agents/vuln-scanner/references/scan-types-and-tools.md +83 -0
  72. package/catalog/agents/vuln-scanner/references/triage-and-false-positives.md +73 -0
  73. package/catalog/agents/vuln-scanner/scripts/parse-scan-results.mjs +133 -0
  74. package/catalog/packs/code-review-pack/PACK.md +100 -0
  75. package/catalog/packs/code-review-pack/examples/pr-flow-example.md +50 -0
  76. package/catalog/packs/code-review-pack/references/rollout-guide.md +40 -0
  77. package/catalog/packs/security-pack/PACK.md +128 -0
  78. package/catalog/packs/security-pack/examples/owasp-vuln-triage-and-fix.md +66 -0
  79. package/catalog/packs/security-pack/examples/threat-model-then-audit.md +77 -0
  80. package/catalog/packs/security-pack/references/choosing-the-right-tool.md +37 -0
  81. package/catalog/packs/security-pack/references/threat-model-to-remediation.md +69 -0
  82. package/catalog/packs/security-pack/references/workflow.md +56 -0
  83. package/catalog/skills/api-pagination/SKILL.md +83 -0
  84. package/catalog/skills/api-pagination/examples/keyset-queries.sql +38 -0
  85. package/catalog/skills/api-pagination/examples/response-shapes.md +51 -0
  86. package/catalog/skills/api-pagination/references/consuming-pages.md +39 -0
  87. package/catalog/skills/api-pagination/references/counting-and-totals.md +41 -0
  88. package/catalog/skills/api-pagination/references/cursor-encoding.md +34 -0
  89. package/catalog/skills/api-pagination/scripts/check-cursor.mjs +51 -0
  90. package/catalog/skills/caching-strategies/SKILL.md +180 -0
  91. package/catalog/skills/caching-strategies/examples/http-cache-headers.md +82 -0
  92. package/catalog/skills/caching-strategies/examples/redis-cache-aside.js +110 -0
  93. package/catalog/skills/caching-strategies/references/cache-patterns.md +93 -0
  94. package/catalog/skills/caching-strategies/references/eviction-and-ttl.md +67 -0
  95. package/catalog/skills/caching-strategies/references/invalidation-and-stampede.md +99 -0
  96. package/catalog/skills/caching-strategies/scripts/ttl-jitter.mjs +99 -0
  97. package/catalog/skills/conventional-commits/SKILL.md +135 -0
  98. package/catalog/skills/conventional-commits/examples/commit-examples.md +60 -0
  99. package/catalog/skills/conventional-commits/examples/commitlint.config.js +31 -0
  100. package/catalog/skills/conventional-commits/references/breaking-changes-semver.md +50 -0
  101. package/catalog/skills/conventional-commits/references/scoping-commits.md +42 -0
  102. package/catalog/skills/conventional-commits/references/spec.md +78 -0
  103. package/catalog/skills/conventional-commits/scripts/lint-commit.mjs +97 -0
  104. package/catalog/skills/database-migrations/SKILL.md +137 -0
  105. package/catalog/skills/database-migrations/examples/add-column-safe.sql +34 -0
  106. package/catalog/skills/database-migrations/examples/migration-config.example.json +27 -0
  107. package/catalog/skills/database-migrations/references/expand-contract.md +74 -0
  108. package/catalog/skills/database-migrations/references/rollback-and-safety.md +71 -0
  109. package/catalog/skills/database-migrations/references/zero-downtime-changes.md +84 -0
  110. package/catalog/skills/database-migrations/scripts/check-migration-reversible.mjs +139 -0
  111. package/catalog/skills/error-handling-patterns/SKILL.md +136 -0
  112. package/catalog/skills/error-handling-patterns/examples/express-error-middleware.js +93 -0
  113. package/catalog/skills/error-handling-patterns/examples/result-pattern.ts +81 -0
  114. package/catalog/skills/error-handling-patterns/references/error-taxonomy.md +91 -0
  115. package/catalog/skills/error-handling-patterns/references/language-patterns.md +102 -0
  116. package/catalog/skills/error-handling-patterns/references/retry-and-backoff.md +76 -0
  117. package/catalog/skills/error-handling-patterns/scripts/lint-empty-catch.mjs +113 -0
  118. package/catalog/skills/git-collaboration-workflows/SKILL.md +104 -0
  119. package/catalog/skills/git-collaboration-workflows/examples/branch-protection.md +48 -0
  120. package/catalog/skills/git-collaboration-workflows/examples/pr-review-playbook.md +48 -0
  121. package/catalog/skills/git-collaboration-workflows/references/branching-models.md +52 -0
  122. package/catalog/skills/git-collaboration-workflows/references/history-hygiene.md +44 -0
  123. package/catalog/skills/git-collaboration-workflows/scripts/check-branch-hygiene.mjs +62 -0
  124. package/catalog/skills/owasp-top10/SKILL.md +112 -0
  125. package/catalog/skills/owasp-top10/examples/sql-injection-fix.md +33 -0
  126. package/catalog/skills/owasp-top10/examples/xss-fix.md +27 -0
  127. package/catalog/skills/owasp-top10/references/access-control.md +36 -0
  128. package/catalog/skills/owasp-top10/references/crypto-failures.md +29 -0
  129. package/catalog/skills/owasp-top10/references/injection.md +39 -0
  130. package/catalog/skills/owasp-top10/references/ssrf-and-design.md +35 -0
  131. package/catalog/skills/owasp-top10/scripts/scan-injection.mjs +73 -0
  132. package/catalog/skills/prompt-engineering/SKILL.md +100 -0
  133. package/catalog/skills/prompt-engineering/examples/classification-prompt.md +29 -0
  134. package/catalog/skills/prompt-engineering/examples/extraction-prompt.md +25 -0
  135. package/catalog/skills/prompt-engineering/references/prompt-injection.md +34 -0
  136. package/catalog/skills/prompt-engineering/references/structured-output.md +34 -0
  137. package/catalog/skills/prompt-engineering/references/techniques.md +33 -0
  138. package/catalog/skills/prompt-engineering/scripts/validate-output.mjs +71 -0
  139. package/catalog/skills/readme-writing/SKILL.md +176 -0
  140. package/catalog/skills/readme-writing/examples/README.template.md +73 -0
  141. package/catalog/skills/readme-writing/examples/good-readme-example.md +95 -0
  142. package/catalog/skills/readme-writing/references/anatomy.md +107 -0
  143. package/catalog/skills/readme-writing/references/maintenance.md +68 -0
  144. package/catalog/skills/readme-writing/references/writing-style.md +79 -0
  145. package/catalog/skills/readme-writing/scripts/lint-readme.mjs +117 -0
  146. package/catalog/skills/refactoring-patterns/SKILL.md +178 -0
  147. package/catalog/skills/refactoring-patterns/examples/extract-function.before-after.js +57 -0
  148. package/catalog/skills/refactoring-patterns/examples/replace-conditional-with-polymorphism.before-after.ts +63 -0
  149. package/catalog/skills/refactoring-patterns/references/code-smells.md +92 -0
  150. package/catalog/skills/refactoring-patterns/references/refactoring-catalog.md +78 -0
  151. package/catalog/skills/refactoring-patterns/references/safe-workflow.md +74 -0
  152. package/catalog/skills/refactoring-patterns/scripts/detect-smells.mjs +137 -0
  153. package/catalog/skills/rest-api-design/SKILL.md +147 -0
  154. package/catalog/skills/rest-api-design/examples/error-envelope.json +26 -0
  155. package/catalog/skills/rest-api-design/examples/orders-api.openapi.yaml +106 -0
  156. package/catalog/skills/rest-api-design/references/error-handling.md +43 -0
  157. package/catalog/skills/rest-api-design/references/pagination.md +41 -0
  158. package/catalog/skills/rest-api-design/references/status-codes.md +32 -0
  159. package/catalog/skills/rest-api-design/references/versioning.md +31 -0
  160. package/catalog/skills/rest-api-design/scripts/check-envelope.mjs +62 -0
  161. package/catalog/skills/secrets-management/SKILL.md +134 -0
  162. package/catalog/skills/secrets-management/examples/env-example.md +41 -0
  163. package/catalog/skills/secrets-management/examples/rotation-runbook.md +50 -0
  164. package/catalog/skills/secrets-management/references/leak-response.md +47 -0
  165. package/catalog/skills/secrets-management/references/rotation.md +46 -0
  166. package/catalog/skills/secrets-management/references/secret-managers.md +52 -0
  167. package/catalog/skills/secrets-management/scripts/detect-hardcoded.mjs +96 -0
  168. package/catalog/skills/secure-auth/SKILL.md +135 -0
  169. package/catalog/skills/secure-auth/examples/auth-flow.md +60 -0
  170. package/catalog/skills/secure-auth/examples/jwt-verify.ts +89 -0
  171. package/catalog/skills/secure-auth/references/auth-attacks.md +76 -0
  172. package/catalog/skills/secure-auth/references/oauth2-oidc.md +65 -0
  173. package/catalog/skills/secure-auth/references/password-hashing.md +70 -0
  174. package/catalog/skills/secure-auth/references/sessions-vs-jwt.md +69 -0
  175. package/catalog/skills/secure-auth/scripts/check-password-policy.mjs +114 -0
  176. package/catalog/skills/sql-index-tuning/SKILL.md +171 -0
  177. package/catalog/skills/sql-index-tuning/examples/explain-walkthrough.sql +91 -0
  178. package/catalog/skills/sql-index-tuning/examples/index-ddl.sql +67 -0
  179. package/catalog/skills/sql-index-tuning/references/btree-internals.md +58 -0
  180. package/catalog/skills/sql-index-tuning/references/composite-and-covering.md +60 -0
  181. package/catalog/skills/sql-index-tuning/references/reading-explain.md +54 -0
  182. package/catalog/skills/sql-index-tuning/references/selectivity-and-cardinality.md +62 -0
  183. package/catalog/skills/sql-index-tuning/scripts/suggest-index.mjs +213 -0
  184. package/catalog/skills/test-plan-design/SKILL.md +154 -0
  185. package/catalog/skills/test-plan-design/examples/test-case-template.md +45 -0
  186. package/catalog/skills/test-plan-design/examples/test-plan.md +57 -0
  187. package/catalog/skills/test-plan-design/references/case-design.md +79 -0
  188. package/catalog/skills/test-plan-design/references/risk-prioritization.md +58 -0
  189. package/catalog/skills/test-plan-design/references/test-pyramid.md +58 -0
  190. package/catalog/skills/test-plan-design/scripts/coverage-gaps.mjs +109 -0
  191. package/free-tier.json +15 -0
  192. package/package.json +15 -4
  193. package/premium-index.json +1 -0
  194. package/src/config.js +31 -1
  195. package/src/license.js +143 -0
@@ -0,0 +1,60 @@
1
+ # Annotated Auth Flows
2
+
3
+ Three end-to-end flows with the security-relevant step called out at each line. These are reference
4
+ sequences, not framework-specific code.
5
+
6
+ ## 1. Password login → session
7
+
8
+ ```text
9
+ POST /login { email, password }
10
+ 1. Look up user by email.
11
+ 2. ALWAYS run argon2.verify even if the user doesn't exist (compare against a dummy hash)
12
+ -> keeps response time uniform, defeats account enumeration by timing.
13
+ 3. On success:
14
+ a. Regenerate the session ID <- defeats session fixation
15
+ b. Persist session server-side (user, absolute + idle expiry, CSRF token)
16
+ c. Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
17
+ 4. On failure: generic 401 "invalid email or password" (never say which was wrong).
18
+ 5. Increment a per-account + per-IP failure counter -> backoff/lockout on abuse.
19
+ ```
20
+
21
+ The dummy-hash compare in step 2 matters: skipping the hash when the user is missing makes "no such
22
+ user" measurably faster than "wrong password," which is an enumeration oracle.
23
+
24
+ ## 2. Access token refresh (JWT model)
25
+
26
+ ```text
27
+ Login issues:
28
+ access_token (JWT, exp ~10 min, carried in Authorization: Bearer)
29
+ refresh_token (opaque, stored server-side, exp ~14 days, in HttpOnly cookie)
30
+
31
+ POST /token/refresh (presents refresh_token)
32
+ 1. Look up the refresh token record server-side.
33
+ 2. If it's already been used (rotated out) -> REUSE DETECTED:
34
+ revoke the entire token family + force re-login. <- catches stolen refresh tokens
35
+ 3. Otherwise: issue a NEW access_token AND a NEW refresh_token,
36
+ invalidate the old refresh token (rotation).
37
+ 4. Logout: delete the refresh record -> access dies within ~10 min.
38
+ ```
39
+
40
+ ## 3. OAuth2 / OIDC login (Authorization Code + PKCE)
41
+
42
+ ```text
43
+ 1. App generates code_verifier (random) + code_challenge = SHA256(verifier),
44
+ and a random `state`. Store both in the user's pending session.
45
+ 2. Redirect to provider /authorize?...&code_challenge=...&state=...&scope=openid email
46
+ 3. User authenticates at the provider; redirected back with ?code=...&state=...
47
+ 4. App verifies returned state == stored state. <- CSRF defense on callback
48
+ 5. App POSTs code + code_verifier to /token. <- PKCE binds code to this client
49
+ 6. Provider returns access_token + id_token (+ refresh_token).
50
+ 7. App verifies id_token: signature (JWKS), iss, aud, exp, nonce. (see jwt-verify.ts)
51
+ 8. App maps id_token.sub -> local user (create on first login). Use `sub`, not `email`.
52
+ 9. Establish a local session/token exactly as in flows 1 or 2.
53
+ ```
54
+
55
+ ## Logout that actually logs out
56
+
57
+ - Server-side sessions: delete the session record (not just the cookie). Optionally support
58
+ "log out all devices" by deleting every session for the user.
59
+ - JWT model: delete the refresh token; rely on short access-token expiry for the residual window. If you
60
+ need *instant* access-token revocation, keep a short-TTL denylist of token IDs (`jti`).
@@ -0,0 +1,89 @@
1
+ // jwt-verify.ts
2
+ //
3
+ // A dependency-light illustration of how to verify a JWT *correctly*. In production
4
+ // you would use a maintained library (e.g. `jose`) plus JWKS fetching/caching — this
5
+ // file shows the checks that library performs so you know what "good" looks like and
6
+ // can audit a hand-rolled verifier. It uses only the Node built-in `crypto` module.
7
+ //
8
+ // SECURITY NOTES (the parts people get wrong):
9
+ // 1. PIN the algorithm. Never trust the token's own `alg` header — an attacker can
10
+ // set `alg: none` or switch RS256 -> HS256 and sign with the public key.
11
+ // 2. ALWAYS verify the signature BEFORE reading any claim as trusted.
12
+ // 3. Check `iss`, `aud`, and `exp` (and `nbf`/`iat`) — a valid signature on a token
13
+ // meant for a different audience is still not for you.
14
+
15
+ import { createHmac, timingSafeEqual } from 'node:crypto';
16
+
17
+ interface VerifyOptions {
18
+ secret: string; // HMAC secret (HS256). For RS256 you'd verify with a public key.
19
+ issuer: string; // expected `iss`
20
+ audience: string; // expected `aud`
21
+ now?: number; // injectable clock (seconds) for testing
22
+ }
23
+
24
+ interface JwtClaims {
25
+ iss?: string;
26
+ aud?: string | string[];
27
+ exp?: number;
28
+ nbf?: number;
29
+ sub?: string;
30
+ [k: string]: unknown;
31
+ }
32
+
33
+ function b64urlDecode(part: string): Buffer {
34
+ return Buffer.from(part.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
35
+ }
36
+
37
+ function b64urlEncode(buf: Buffer): string {
38
+ return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
39
+ }
40
+
41
+ export function verifyJwtHS256(token: string, opts: VerifyOptions): JwtClaims {
42
+ const parts = token.split('.');
43
+ if (parts.length !== 3) throw new Error('malformed token: expected 3 segments');
44
+ const [headerB64, payloadB64, signatureB64] = parts;
45
+
46
+ // 1. Pin the algorithm — reject whatever the header claims if it isn't ours.
47
+ const header = JSON.parse(b64urlDecode(headerB64).toString('utf8'));
48
+ if (header.alg !== 'HS256') throw new Error(`unexpected alg: ${header.alg}`);
49
+
50
+ // 2. Recompute and compare the signature in constant time.
51
+ const expected = createHmac('sha256', opts.secret)
52
+ .update(`${headerB64}.${payloadB64}`)
53
+ .digest();
54
+ const provided = b64urlDecode(signatureB64);
55
+ if (expected.length !== provided.length || !timingSafeEqual(expected, provided)) {
56
+ throw new Error('invalid signature');
57
+ }
58
+
59
+ // 3. Only now is it safe to read claims as trusted.
60
+ const claims: JwtClaims = JSON.parse(b64urlDecode(payloadB64).toString('utf8'));
61
+ const now = opts.now ?? Math.floor(Date.now() / 1000);
62
+
63
+ if (claims.iss !== opts.issuer) throw new Error('issuer mismatch');
64
+ const aud = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
65
+ if (!aud.includes(opts.audience)) throw new Error('audience mismatch');
66
+ if (typeof claims.exp === 'number' && now >= claims.exp) throw new Error('token expired');
67
+ if (typeof claims.nbf === 'number' && now < claims.nbf) throw new Error('token not yet valid');
68
+
69
+ return claims;
70
+ }
71
+
72
+ // Tiny demo helper to mint a token for local experimentation (NOT for production use).
73
+ export function signJwtHS256(claims: JwtClaims, secret: string): string {
74
+ const header = b64urlEncode(Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })));
75
+ const payload = b64urlEncode(Buffer.from(JSON.stringify(claims)));
76
+ const sig = b64urlEncode(createHmac('sha256', secret).update(`${header}.${payload}`).digest());
77
+ return `${header}.${payload}.${sig}`;
78
+ }
79
+
80
+ // Example (fake secret — never hardcode real secrets; load from a secrets manager):
81
+ // const SECRET = process.env.JWT_SECRET!;
82
+ // const token = signJwtHS256(
83
+ // { iss: 'https://auth.example.com', aud: 'my-api', sub: 'user-123',
84
+ // exp: Math.floor(Date.now() / 1000) + 600 },
85
+ // SECRET,
86
+ // );
87
+ // const claims = verifyJwtHS256(token, {
88
+ // secret: SECRET, issuer: 'https://auth.example.com', audience: 'my-api',
89
+ // });
@@ -0,0 +1,76 @@
1
+ # Common Auth Attacks and Defenses
2
+
3
+ Most authentication breaches exploit operational gaps, not broken crypto. Know the named attacks and the
4
+ specific control that stops each.
5
+
6
+ ## Credential stuffing
7
+
8
+ Attackers replay username/password pairs leaked from *other* breaches, betting on password reuse. It is
9
+ high-volume and automated.
10
+
11
+ **Defenses:**
12
+ - Rate-limit and progressively delay/lock by IP **and** by account.
13
+ - Screen new/changed passwords against a breached-password corpus (HaveIBeenPwned range API or a local
14
+ Pwned Passwords list) — see `scripts/check-password-policy.mjs`.
15
+ - Offer/require MFA; it neutralizes a correct-but-stolen password.
16
+ - Add bot defenses (device fingerprinting, CAPTCHA on risk) for anomalous traffic.
17
+
18
+ ## Brute force
19
+
20
+ Guessing one account's password through sheer volume.
21
+
22
+ **Defenses:** account lockout / exponential backoff, strong minimum length, slow password hashing
23
+ (argon2id), and MFA. Avoid hard permanent lockouts — they become a denial-of-service against legitimate
24
+ users; prefer temporary backoff plus alerting.
25
+
26
+ ## Session fixation
27
+
28
+ The attacker obtains or sets a session ID *before* the victim logs in, then rides the now-authenticated
29
+ session.
30
+
31
+ **Defense:** **regenerate the session identifier at every privilege change** (especially on login). Never
32
+ accept a session ID supplied via URL/query string. Set `HttpOnly; Secure; SameSite` cookies.
33
+
34
+ ## CSRF (Cross-Site Request Forgery)
35
+
36
+ A malicious site causes the victim's browser to send an authenticated, state-changing request to your app
37
+ using the victim's cookies.
38
+
39
+ **Defenses:**
40
+ - `SameSite=Lax` (or `Strict`) cookies — blocks most cross-site sends.
41
+ - Anti-CSRF tokens (synchronizer or double-submit) on state-changing requests.
42
+ - Require a custom header / re-auth for sensitive actions.
43
+ - Note: token-in-`Authorization`-header APIs (not cookie-auth) are largely immune, since the browser
44
+ doesn't auto-attach the header cross-site.
45
+
46
+ ## Account enumeration
47
+
48
+ The app reveals whether an account exists via different responses, status codes, or **timing** on login,
49
+ signup, or password reset.
50
+
51
+ **Defense:** return identical responses and keep timing uniform regardless of account existence ("If that
52
+ email is registered, we've sent a reset link.").
53
+
54
+ ## XSS-driven token theft
55
+
56
+ Cross-site scripting that steals tokens from `localStorage` or readable cookies.
57
+
58
+ **Defenses:** `HttpOnly` cookies (JS can't read them), a strict Content-Security-Policy, output encoding,
59
+ and never storing long-lived credentials in web storage.
60
+
61
+ ## Phishing / MITM of credentials
62
+
63
+ **Defenses:** phishing-resistant MFA (WebAuthn/passkeys bind to origin), HSTS, and never accepting
64
+ credentials over plain HTTP.
65
+
66
+ ## Quick map
67
+
68
+ | Attack | Primary control |
69
+ |---|---|
70
+ | Credential stuffing | Rate limit + breached-password screen + MFA |
71
+ | Brute force | Backoff/lockout + slow hash + MFA |
72
+ | Session fixation | Regenerate session ID on login |
73
+ | CSRF | SameSite cookies + anti-CSRF token |
74
+ | Enumeration | Uniform responses + timing |
75
+ | XSS token theft | HttpOnly cookies + CSP |
76
+ | Phishing | WebAuthn/passkeys + HSTS |
@@ -0,0 +1,65 @@
1
+ # OAuth2 / OIDC — Flows and Validation
2
+
3
+ OAuth2 is a **delegated authorization** framework: it lets your app obtain an access token to call an API
4
+ on a user's behalf. OpenID Connect (OIDC) is a thin identity layer on top that adds an `id_token` so you
5
+ also learn **who the user is**. For "log in with Google/Apple/etc.", you want OIDC.
6
+
7
+ ## Use Authorization Code + PKCE
8
+
9
+ For both server-side web apps and public clients (SPAs, mobile), use the **Authorization Code flow with
10
+ PKCE** (Proof Key for Code Exchange).
11
+
12
+ ```text
13
+ 1. Client generates code_verifier (random) + code_challenge = SHA256(code_verifier).
14
+ 2. Redirect user to /authorize?response_type=code&client_id=...&redirect_uri=...
15
+ &scope=openid profile email&state=<csrf>&code_challenge=...&code_challenge_method=S256
16
+ 3. User authenticates at the provider, is redirected back with ?code=...&state=...
17
+ 4. Client verifies returned state == sent state, then POSTs to /token with the code + code_verifier.
18
+ 5. Provider returns access_token (+ id_token for OIDC, + refresh_token).
19
+ ```
20
+
21
+ PKCE binds the authorization code to the client that started the flow, so a stolen code is useless to an
22
+ attacker who lacks the original `code_verifier`.
23
+
24
+ ## Flows to avoid
25
+
26
+ | Flow | Verdict |
27
+ |---|---|
28
+ | Authorization Code + PKCE | **Use this** for web, SPA, mobile |
29
+ | Client Credentials | Machine-to-machine only (no user) |
30
+ | Implicit | **Deprecated** — tokens leak via URL/history; use Code+PKCE |
31
+ | Resource Owner Password | **Avoid** — app handles the user's IdP password; defeats the point of delegation |
32
+
33
+ ## The `state` parameter (CSRF defense)
34
+
35
+ Always send a random, unguessable `state` on the authorize request and reject the callback if the
36
+ returned `state` doesn't match what you stored. Without it, an attacker can splice their own
37
+ authorization code into the victim's session (login CSRF).
38
+
39
+ ## Validating the `id_token` (OIDC)
40
+
41
+ The `id_token` is a JWT. Do not trust it until you verify:
42
+
43
+ - **Signature** — against the provider's published JWKS (`jwks_uri`), matched by the `kid` header.
44
+ - **`iss`** — exactly equals the provider's issuer.
45
+ - **`aud`** — contains your client_id.
46
+ - **`exp` / `iat`** — not expired; issued recently.
47
+ - **`nonce`** — matches the nonce you sent (replay defense), when used.
48
+
49
+ Never accept an unsigned token (`alg: none`) and never let the token dictate the algorithm — pin the
50
+ expected algorithm server-side. See `examples/jwt-verify.ts` for the structural checks.
51
+
52
+ ## Scopes vs claims
53
+
54
+ - **Scopes** (`openid profile email`) request *access* to categories of data.
55
+ - **Claims** (`sub`, `email`, `name`) are the actual values inside the `id_token` / userinfo response.
56
+ - Treat `sub` (subject) as the stable user identifier — `email` can change or be reassigned.
57
+
58
+ ## Checklist
59
+
60
+ - [ ] Authorization Code + PKCE (not implicit, not password grant)
61
+ - [ ] `state` generated, stored, and verified on callback
62
+ - [ ] `id_token` signature verified against JWKS; `iss`/`aud`/`exp` checked
63
+ - [ ] Algorithm pinned server-side; `alg: none` rejected
64
+ - [ ] `sub` used as the durable user key, not `email`
65
+ - [ ] `redirect_uri` allowlisted exactly (no open redirects)
@@ -0,0 +1,70 @@
1
+ # Password Hashing — argon2id, bcrypt, and tuning
2
+
3
+ The job of a password hash is to make offline cracking of a stolen database *slow and expensive*. That
4
+ is the opposite of a normal hash function's goal (speed). Never use `MD5`, `SHA-1`, `SHA-256`, or any
5
+ fast hash for passwords — a single GPU computes billions of SHA-256 hashes per second, so an unsalted
6
+ fast-hash dump is effectively plaintext within hours.
7
+
8
+ ## Algorithm choice
9
+
10
+ | Algorithm | Status | Notes |
11
+ |---|---|---|
12
+ | **argon2id** | **Preferred** | Memory-hard; resists GPU/ASIC cracking. Tunable memory + time + parallelism. |
13
+ | **bcrypt** | Acceptable | Mature, ubiquitous. 72-byte input cap; pre-hash long inputs with SHA-256. |
14
+ | **scrypt** | Acceptable | Memory-hard; fine if argon2 unavailable. |
15
+ | **PBKDF2** | Last resort | Only when FIPS compliance forces it; use a high iteration count. |
16
+ | MD5 / SHA-* | **Never** | Fast = crackable. Not password hashes. |
17
+
18
+ ## Salting and peppering
19
+
20
+ - **Salt** — a unique random value per password that defeats rainbow tables and stops identical
21
+ passwords producing identical hashes. argon2 and bcrypt generate and embed the salt for you; do not
22
+ add your own or store it separately.
23
+ - **Pepper** — an optional secret key mixed in (or used to encrypt the hash) and stored *outside* the
24
+ database (e.g. in a secrets manager / HSM). It means a DB-only leak isn't enough to start cracking.
25
+ Manage it like any other secret (see the `secrets-management` skill).
26
+
27
+ ## Tuning cost parameters
28
+
29
+ Pick parameters so a single verification takes roughly **250–500 ms** on your production hardware — slow
30
+ enough to hurt attackers, fast enough not to DoS your own login endpoint.
31
+
32
+ ```ts
33
+ import argon2 from 'argon2';
34
+
35
+ const hash = await argon2.hash(password, {
36
+ type: argon2.argon2id,
37
+ memoryCost: 19456, // ~19 MiB; raise as hardware allows (OWASP baseline)
38
+ timeCost: 2, // iterations
39
+ parallelism: 1,
40
+ });
41
+ ```
42
+
43
+ Cap input length (e.g. 128 chars) before hashing so an attacker can't submit a multi-megabyte password
44
+ to exhaust CPU/memory — a cheap denial-of-service otherwise.
45
+
46
+ ## Rehash on login (parameter migration)
47
+
48
+ Cost targets rise as hardware improves. On each successful login, check whether the stored hash used
49
+ weaker parameters than your current policy and transparently rehash:
50
+
51
+ ```ts
52
+ if (await argon2.verify(stored, attempt)) {
53
+ if (argon2.needsRehash(stored, currentOpts)) {
54
+ const fresh = await argon2.hash(attempt, currentOpts);
55
+ await users.update(id, { passwordHash: fresh });
56
+ }
57
+ }
58
+ ```
59
+
60
+ This lets you upgrade an entire user base's hash strength gradually without a forced reset.
61
+
62
+ ## Checklist
63
+
64
+ - [ ] argon2id (or bcrypt) — never a fast/general-purpose hash
65
+ - [ ] Per-password salt (library-managed), not reused or hand-rolled
66
+ - [ ] Verification tuned to ~250–500 ms on prod hardware
67
+ - [ ] Input length capped before hashing (DoS guard)
68
+ - [ ] Breached-password screening at registration (`scripts/check-password-policy.mjs`)
69
+ - [ ] Rehash-on-login to migrate parameters over time
70
+ - [ ] Optional pepper stored outside the database
@@ -0,0 +1,69 @@
1
+ # Sessions vs JWT — Storage, Expiry, Refresh, Revocation
2
+
3
+ Both answer "how do we remember a logged-in user across stateless HTTP requests?" The decisive
4
+ difference is **revocation**: server-side sessions are trivial to revoke; signed JWTs are not.
5
+
6
+ ## Server-side sessions (stateful)
7
+
8
+ The server issues a random, high-entropy **opaque** session ID and stores the session state
9
+ (user, expiry, CSRF token) in a database or Redis. The ID travels in a cookie.
10
+
11
+ ```http
12
+ Set-Cookie: session=Gd8...n2; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
13
+ ```
14
+
15
+ - **Revocation:** delete the server-side record — instant logout-everywhere.
16
+ - **Expiry:** enforce both an idle timeout and an absolute lifetime.
17
+ - **Trade-off:** requires shared session storage; a tiny lookup per request.
18
+ - **Best for:** classic server-rendered web apps; anything needing reliable forced logout.
19
+
20
+ ## Stateless JWT
21
+
22
+ A signed token (header.payload.signature) the client carries, usually in the `Authorization: Bearer`
23
+ header. The server validates the signature and claims with no lookup.
24
+
25
+ - **Cannot be un-issued.** Once signed, a JWT is valid until `exp`. There is no "delete the row."
26
+ - **Expiry is your main control** — keep access tokens **short (5–15 min)**.
27
+ - **Best for:** APIs and microservices that must validate auth without shared session state.
28
+
29
+ ### Access + refresh token pattern
30
+
31
+ Pair a short-lived access token with a longer-lived **refresh token** that *is* tracked server-side so
32
+ it can be revoked:
33
+
34
+ 1. Login issues a short access token (~10 min) + a refresh token (~days/weeks), refresh stored server-side.
35
+ 2. Client uses the access token until it expires.
36
+ 3. Client exchanges the refresh token for a new access token (and a **new** refresh token — rotation).
37
+ 4. Logout / compromise: delete the refresh token record; access dies within minutes.
38
+
39
+ ### Refresh token rotation + reuse detection
40
+
41
+ On each refresh, issue a new refresh token and invalidate the old one. If an *already-used* (rotated-out)
42
+ refresh token is presented again, treat it as theft: revoke the entire token family and force re-login.
43
+ This catches a stolen refresh token because either the attacker or the victim will replay a consumed one.
44
+
45
+ ## Storage: where the token/cookie lives
46
+
47
+ | Location | XSS-readable? | CSRF-exposed? | Verdict |
48
+ |---|---|---|---|
49
+ | `HttpOnly` cookie | No | Yes (mitigate w/ SameSite + token) | **Preferred** for browser auth |
50
+ | `localStorage` / `sessionStorage` | **Yes** | No | Avoid — any XSS exfiltrates it |
51
+ | In-memory (JS variable) | Only during session | No | OK for short access token in SPA |
52
+
53
+ The headline rule: **never put a long-lived credential in `localStorage`.** `HttpOnly` cookies keep the
54
+ token out of reach of injected JavaScript.
55
+
56
+ ## Quick decision guide
57
+
58
+ - Need easy forced logout / session revocation, server-rendered app → **server-side sessions**.
59
+ - Stateless API across many services, comfortable with short tokens + refresh rotation → **JWT**.
60
+ - Hybrid is common and good: opaque session cookie for the web app, short JWT for downstream service calls.
61
+
62
+ ## Checklist
63
+
64
+ - [ ] Access tokens short-lived (5–15 min)
65
+ - [ ] Refresh tokens server-side, rotated on use, with reuse detection
66
+ - [ ] Cookies set `HttpOnly; Secure; SameSite`
67
+ - [ ] No long-lived credential in `localStorage`
68
+ - [ ] Session ID regenerated on login (anti session-fixation)
69
+ - [ ] Logout actually revokes server-side state, not just clears the client
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ // Runnable check: validates a candidate password against a configurable policy
3
+ // (minimum length, character-class diversity, and a small breached/common-password
4
+ // denylist). Zero dependencies — Node built-ins only.
5
+ //
6
+ // This is a *policy linter*, not a strength meter. It mirrors the kind of
7
+ // server-side validation you should run at registration / password-change time,
8
+ // BEFORE hashing with argon2id or bcrypt (see references/password-hashing.md).
9
+ //
10
+ // Usage:
11
+ // node check-password-policy.mjs '<password>' # check one password
12
+ // echo '<password>' | node check-password-policy.mjs # check from stdin
13
+ // node check-password-policy.mjs --selftest # run built-in test cases
14
+ //
15
+ // Exit code: 0 = password satisfies the policy, 1 = it does not (or selftest failed).
16
+
17
+ // --- Policy configuration ---------------------------------------------------
18
+ // NIST SP 800-63B favors length over composition rules and screening against
19
+ // known-breached passwords. We keep a modest class requirement for demos but the
20
+ // breached-list screen is the most valuable check in practice.
21
+ const POLICY = Object.freeze({
22
+ minLength: 12,
23
+ maxLength: 128, // cap to avoid DoS on the hashing function
24
+ minClasses: 3, // of: lowercase, uppercase, digit, symbol
25
+ });
26
+
27
+ // A tiny stand-in for a real breached-password corpus (e.g. HaveIBeenPwned's
28
+ // k-anonymity range API or a local Pwned Passwords bloom filter). These are
29
+ // obviously fake/illustrative entries used only to exercise the check.
30
+ const BREACHED_COMMON = new Set([
31
+ 'password', 'password1', 'password123', '123456', '12345678', '123456789',
32
+ 'qwerty', 'qwerty123', 'letmein', 'admin', 'admin123', 'welcome', 'iloveyou',
33
+ 'monkey', 'dragon', 'football', 'changeme', 'passw0rd', 'p@ssw0rd', 'test1234',
34
+ ]);
35
+
36
+ // --- Validation logic -------------------------------------------------------
37
+ export function countClasses(pw) {
38
+ let n = 0;
39
+ if (/[a-z]/.test(pw)) n++;
40
+ if (/[A-Z]/.test(pw)) n++;
41
+ if (/[0-9]/.test(pw)) n++;
42
+ if (/[^A-Za-z0-9]/.test(pw)) n++;
43
+ return n;
44
+ }
45
+
46
+ export function validatePassword(pw, policy = POLICY) {
47
+ const failures = [];
48
+ if (typeof pw !== 'string') {
49
+ return { ok: false, failures: ['password must be a string'] };
50
+ }
51
+ if (pw.length < policy.minLength) {
52
+ failures.push(`too short: ${pw.length} < ${policy.minLength} chars`);
53
+ }
54
+ if (pw.length > policy.maxLength) {
55
+ failures.push(`too long: ${pw.length} > ${policy.maxLength} chars`);
56
+ }
57
+ const classes = countClasses(pw);
58
+ if (classes < policy.minClasses) {
59
+ failures.push(`not enough character classes: ${classes} < ${policy.minClasses}`);
60
+ }
61
+ // Case-insensitive membership check against the breached/common denylist.
62
+ if (BREACHED_COMMON.has(pw.toLowerCase())) {
63
+ failures.push('matches a known breached/common password');
64
+ }
65
+ return { ok: failures.length === 0, failures };
66
+ }
67
+
68
+ // --- CLI plumbing -----------------------------------------------------------
69
+ function check(label, pw) {
70
+ const { ok, failures } = validatePassword(pw);
71
+ if (ok) {
72
+ console.log(`OK ${label}: password satisfies policy`);
73
+ } else {
74
+ console.error(`FAIL ${label}:`);
75
+ failures.forEach((f) => console.error(` - ${f}`));
76
+ }
77
+ return ok;
78
+ }
79
+
80
+ function selftest() {
81
+ // Each case asserts the *expected* verdict so the selftest catches regressions
82
+ // in either direction (a strong password wrongly rejected, or a weak one passed).
83
+ const cases = [
84
+ // name, password (obviously fake), shouldPass
85
+ ['strong_passphrase', 'Tr0ubadour&Horse-Battery!', true],
86
+ ['strong_mixed', 'X9k!mQ2$wLp7vZ', true],
87
+ ['weak_short', 'Ab1!', false],
88
+ ['weak_common', 'password123', false],
89
+ ['weak_one_class', 'aaaaaaaaaaaaaaaa', false],
90
+ ['weak_two_classes', 'lowercaseonly123', false],
91
+ ];
92
+ let allExpected = true;
93
+ for (const [name, pw, shouldPass] of cases) {
94
+ const { ok } = validatePassword(pw);
95
+ const correct = ok === shouldPass;
96
+ allExpected = allExpected && correct;
97
+ const mark = correct ? 'OK' : 'XX';
98
+ console.log(
99
+ `${mark} selftest ${name}: ${ok ? 'accepted' : 'rejected'} (expected ${shouldPass ? 'accepted' : 'rejected'})`
100
+ );
101
+ }
102
+ process.exit(allExpected ? 0 : 1);
103
+ }
104
+
105
+ const arg = process.argv[2];
106
+ if (arg === '--selftest') {
107
+ selftest();
108
+ } else if (arg !== undefined) {
109
+ process.exit(check('password', arg) ? 0 : 1);
110
+ } else {
111
+ let buf = '';
112
+ process.stdin.on('data', (c) => (buf += c));
113
+ process.stdin.on('end', () => process.exit(check('stdin', buf.replace(/\r?\n$/, '')) ? 0 : 1));
114
+ }