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.
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 +140 -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 +168 -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 +112 -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 +156 -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 +185 -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 +168 -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 +163 -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 +177 -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 +163 -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 +146 -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,37 @@
1
+ # Choosing the right item: symptom → tool
2
+
3
+ When you're mid-feature and not sure what to reach for, match the symptom to the item.
4
+
5
+ | Symptom / task | Reach for | Why |
6
+ |---|---|---|
7
+ | "New feature touches auth, money, or PII" | `threat-modeler` + `owasp-top10` | Enumerate abuse cases before code exists |
8
+ | "Which OWASP risk applies here?" | `owasp-top10` | The per-risk defense reference |
9
+ | "I'm building login / sessions / MFA" | `secure-auth` | Vetted hashing, tokens, session handling |
10
+ | "I need to reset passwords / issue tokens" | `secure-auth` | Single-use, TTL'd, constant-time compared |
11
+ | "Where does this API key / credential go?" | `secrets-management` | Never in source; loaded from a store |
12
+ | "Diff is ready for review" | `security-auditor` + `owasp-top10` | Security-first code review before merge |
13
+ | "Is a dependency vulnerable?" | `vuln-scanner` | CVEs, including transitive, are invisible to review |
14
+ | "Did a secret get committed?" | `vuln-scanner` | Scans tree *and* git history |
15
+ | "A secret leaked — now what?" | `secrets-management` | Rotate, don't just delete |
16
+
17
+ ## Agent + skill pairings
18
+
19
+ Each agent has a natural skill partner. Use them together:
20
+
21
+ - `threat-modeler` + `owasp-top10` — enumerate threats *and* tag each with the risk category that
22
+ drives its defense.
23
+ - `security-auditor` + `owasp-top10` — the auditor reviews against a concrete Top 10 checklist rather
24
+ than a vibe.
25
+ - Building auth or handling secrets — apply `secure-auth` / `secrets-management` *while writing*, not
26
+ as an afterthought the auditor has to catch.
27
+
28
+ ## Escalation boundaries
29
+
30
+ - Regulatory/compliance evidence (GDPR, SOC 2, audit trails) → `compliance-auditor` and the
31
+ privacy/audit-logging skills, not this pack.
32
+ - Infrastructure, network, TLS, and cloud IAM hardening → DevOps/network specialists; this pack is
33
+ application-code security.
34
+ - Smart-contract vulnerabilities → `smart-contract-auditor` + `smart-contract-security`; the threat
35
+ surface differs from web app security.
36
+ - A CVE with no available patch → this is a risk-acceptance decision (document + mitigate), not
37
+ something `vuln-scanner` resolves for you.
@@ -0,0 +1,69 @@
1
+ # From threat model to remediation: the discipline playbook
2
+
3
+ A threat model only pays off if its threats become fixes. This playbook is the connective tissue
4
+ between `threat-modeler`'s output and a clean `security-auditor` pass — how a STRIDE finding turns
5
+ into a mitigation, a test, and an audited change.
6
+
7
+ ## Step 1 — Enumerate with STRIDE (`threat-modeler`)
8
+
9
+ Walk each trust boundary in the feature and ask the six STRIDE questions. For every "yes", write a
10
+ one-line abuse case in the attacker's voice:
11
+
12
+ ```text
13
+ Spoofing → "I replay another user's session token to act as them."
14
+ Tampering → "I change the price field in the checkout request."
15
+ Repudiation → "I perform a refund and there's no log tying it to me."
16
+ Info disclosure → "The error response tells me whether an email is registered."
17
+ DoS → "I hit the reset endpoint 10k times with no rate limit."
18
+ Elevation → "I call the admin endpoint with a normal user's token."
19
+ ```
20
+
21
+ ## Step 2 — Classify and prioritize (`owasp-top10`)
22
+
23
+ Tag each abuse case with its OWASP category and a severity driven by *impact × likelihood*:
24
+
25
+ | Abuse case | OWASP | Severity |
26
+ |---|---|---|
27
+ | Session token replay | A07 Auth failures | HIGH |
28
+ | Price tampering | A04 Insecure design / A08 | CRITICAL |
29
+ | Missing refund audit log | A09 Logging failures | MEDIUM |
30
+ | Email enumeration | A01 Access control | MEDIUM |
31
+ | No rate limit on reset | A04 / A07 | HIGH |
32
+ | Privilege escalation | A01 Broken access control | CRITICAL |
33
+
34
+ Fix CRITICAL/HIGH before shipping; schedule MEDIUM/LOW with an owner.
35
+
36
+ ## Step 3 — Design the mitigation (skills)
37
+
38
+ Each category has a known defense — reach for the skill, don't improvise:
39
+
40
+ - Access control (A01) → deny by default; authorize every request server-side against the *resource
41
+ owner*, not just "is logged in".
42
+ - Auth failures (A07) → `secure-auth`: strong hashing, single-use tokens, constant-time compare,
43
+ rotate on privilege change.
44
+ - Insecure design (A04) → re-derive trusted values (price, role) server-side; never trust the client.
45
+ - Sensitive data (A02) → `secrets-management` for keys; encrypt at rest; identical responses to
46
+ prevent enumeration.
47
+
48
+ ## Step 4 — Test the abuse case, then implement
49
+
50
+ Turn each abuse case into a failing test *first*, so the fix has a target and a regression guard:
51
+
52
+ ```text
53
+ ✗ a request with user B's token cannot mutate user A's resource (A01)
54
+ ✗ POST /checkout ignores a client-supplied price field (A04)
55
+ ✗ /password-reset returns 429 after N attempts per window (A07)
56
+ ```
57
+
58
+ Implement until green. The test now permanently encodes the threat.
59
+
60
+ ## Step 5 — Audit and scan (`security-auditor` + `vuln-scanner`)
61
+
62
+ Hand the diff to `security-auditor` with the stage-2 severity table as the agenda — it verifies each
63
+ mitigation is present and correct, citing `file:line`. Then `vuln-scanner` sweeps dependencies and
64
+ history. Ship only when the CRITICAL/HIGH rows are all closed and the scan is clean.
65
+
66
+ ## The through-line
67
+
68
+ Threat → OWASP class → mitigation skill → failing abuse-case test → audited fix. When a finding
69
+ skips a step — a threat with no test, or a fix with no audit — that's where regressions re-enter.
@@ -0,0 +1,56 @@
1
+ # The five-stage security workflow
2
+
3
+ Each stage has an owner (the primary item), the skills it applies, and an explicit **exit criterion**
4
+ — you don't advance until the criterion is met. This is what keeps security from collapsing into a
5
+ single rushed review at the end.
6
+
7
+ ## Stage 1 — Model
8
+
9
+ - **Owner:** `threat-modeler` · **Applies:** `owasp-top10`
10
+ - Draw the trust boundaries (client, server, third parties, data store). For each boundary, walk
11
+ STRIDE — Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of
12
+ privilege — and write down the concrete abuse cases for *this* feature.
13
+ - Map each threat to its OWASP Top 10 category so the audit later has a checklist.
14
+ - **Exit criterion:** a written list of threats, each tagged with an OWASP category and a planned
15
+ mitigation, that the build and audit stages can reference.
16
+
17
+ ## Stage 2 — Build
18
+
19
+ - **Owners:** `secure-auth`, `secrets-management` (as the feature demands)
20
+ - Implement the risk-bearing parts against a vetted construction, not from memory. Auth: correct
21
+ hashing, single-use tokens, constant-time comparison, no user enumeration. Secrets: loaded from a
22
+ store or environment, never committed, never shipped to the client.
23
+ - Write the abuse-case tests from stage 1 alongside the implementation.
24
+ - **Exit criterion:** the risky paths are implemented per the skills' guidance and the abuse-case
25
+ tests pass.
26
+
27
+ ## Stage 3 — Audit
28
+
29
+ - **Owner:** `security-auditor` · **Applies:** `owasp-top10`
30
+ - Review the diff security-first, using the stage-1 threat list as the agenda: broken access control
31
+ (IDOR/authorization), injection, auth failures, unsafe crypto, sensitive-data exposure. Every
32
+ finding cites `file:line`, a severity, and a concrete fix.
33
+ - **Exit criterion:** no CRITICAL or HIGH finding remains open.
34
+
35
+ ## Stage 4 — Scan
36
+
37
+ - **Owner:** `vuln-scanner`
38
+ - Scan dependencies (including transitive) for known CVEs, and scan the working tree **and git
39
+ history** for secrets. A secret found in history is treated as exposed even if HEAD is clean.
40
+ - **Exit criterion:** no unaddressed CVE at High/Critical severity; no secret in tree or history
41
+ (removal alone is insufficient for a real leak — see below).
42
+
43
+ ## Stage 5 — Ship
44
+
45
+ - **Gate:** no open CRITICAL/HIGH audit finding, clean dependency scan, no committed secret.
46
+ - If a secret was ever exposed, it is **rotated**, not merely deleted. If a CVE has no patch,
47
+ document the risk acceptance or apply a mitigating control before shipping.
48
+ - **Exit criterion:** all gates green; any accepted risk is written down with an owner.
49
+
50
+ ## Why order matters
51
+
52
+ The threat model is the shared dependency of every later stage — it sets what to build carefully in
53
+ stage 2 and what to look for in stage 3. Building before modeling means you protect the wrong things.
54
+ Auditing before scanning means you can pass human review while a vulnerable dependency or a leaked key
55
+ sits in the tree. Skipping or reordering stages is the single biggest source of shipped
56
+ vulnerabilities.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: api-pagination
3
+ description: Implement correct, fast API pagination — cursor vs offset trade-offs, opaque cursor encoding, stable sort keys, page-size limits, total-count costs, and consumer-side iteration that survives inserts, deletes, and retries. With runnable cursor checks.
4
+ type: skill
5
+ version: 1.0.0
6
+ updated: 2026-07-10
7
+ ---
8
+ # API Pagination
9
+
10
+ Pagination looks trivial until data changes underneath the reader. Offset pagination silently
11
+ **skips or duplicates rows** when inserts land mid-iteration; cursor pagination survives churn but
12
+ demands a stable sort key and an opaque token contract. This skill covers both sides: designing the
13
+ API and consuming one correctly. Deep detail lives in `references/`; copy-paste material in
14
+ `examples/`; a runnable cursor round-trip check in `scripts/`.
15
+
16
+ ## Decision: offset vs cursor
17
+
18
+ | Situation | Use |
19
+ |---|---|
20
+ | Admin tables, small datasets, "jump to page 7" required | Offset (`LIMIT/OFFSET`) |
21
+ | Feeds, sync endpoints, anything users scroll | Cursor (keyset) |
22
+ | Data changes while clients iterate | Cursor — offset will skip/duplicate |
23
+ | Deep pages (offset > ~10k rows) | Cursor — `OFFSET n` scans and discards n rows |
24
+
25
+ Offset's failure mode is correctness, not just speed: a row inserted before your current position
26
+ shifts everything, so page 3 re-shows an item from page 2 (duplicate) or swallows one (skip).
27
+
28
+ ## Keyset mechanics (the part people get wrong)
29
+
30
+ The sort key must be **unique and immutable**. `created_at` alone is not unique — two rows with the
31
+ same timestamp make the cursor ambiguous and rows vanish. Always add a tiebreaker:
32
+
33
+ ```sql
34
+ -- WHERE clause for "next page after (2026-07-01T12:00:00Z, id 4711)" descending
35
+ SELECT * FROM items
36
+ WHERE (created_at, id) < ('2026-07-01T12:00:00Z', 4711)
37
+ ORDER BY created_at DESC, id DESC
38
+ LIMIT 51; -- page_size + 1 to detect has_more without COUNT(*)
39
+ ```
40
+
41
+ Fetch `page_size + 1` rows: if you get 51, there is a next page and the 51st row's key becomes the
42
+ next cursor. This avoids `COUNT(*)`, which on large tables costs more than the page itself
43
+ (see `references/counting-and-totals.md`).
44
+
45
+ ## Cursor token contract
46
+
47
+ Cursors are **opaque to clients** — never let them parse or build one. Encode the keyset values plus
48
+ the sort direction, base64url it, and (for public APIs) HMAC it so tampering is detectable. Include a
49
+ version byte so you can change the encoding later. Full recipe: `references/cursor-encoding.md`;
50
+ runnable round-trip: `scripts/check-cursor.mjs`.
51
+
52
+ ## Response envelope
53
+
54
+ ```json
55
+ {
56
+ "data": [ ... ],
57
+ "page_info": { "has_more": true, "next_cursor": "djEuMj..." }
58
+ }
59
+ ```
60
+
61
+ Rules: `next_cursor` is absent (not empty-string) on the last page; page size is capped server-side
62
+ (a `limit=10000` request gets the cap, documented, not an error); the default page size appears in
63
+ the docs and never changes silently. Consumer-side iteration rules — retries, cursor expiry,
64
+ resumption — live in `references/consuming-pages.md`.
65
+
66
+ ## Pitfalls
67
+
68
+ - **Mutable sort keys** (`updated_at`) — rows teleport across pages as they update. Sort on immutable
69
+ columns or accept re-delivery and dedupe client-side.
70
+ - **Cursor built from row position** instead of row values — it is offset with extra steps and
71
+ inherits every offset bug.
72
+ - **`COUNT(*)` on every page** — the count is stale by the time the client renders it; provide totals
73
+ on a separate, cacheable endpoint if the product truly needs them.
74
+ - **Breaking cursor encoding on deploy** — clients hold cursors across releases; version the token.
75
+ - **Missing composite index** — keyset needs an index on exactly `(created_at, id)` in sort order, or
76
+ every page is a scan.
77
+
78
+ ## Verification
79
+
80
+ Run `node scripts/check-cursor.mjs` — encodes, decodes, tampers, and versions a cursor and asserts
81
+ every property above. For the SQL side, `examples/keyset-queries.sql` includes an `EXPLAIN` you can
82
+ run to confirm the index is used. Pairs with `rest-api-design` (envelopes, limits) and
83
+ `sql-index-tuning` (composite indexes).
@@ -0,0 +1,38 @@
1
+ -- Keyset pagination: canonical queries (Postgres syntax).
2
+
3
+ -- The supporting index. Column order and direction must match the ORDER BY exactly.
4
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_items_created_id
5
+ ON items (created_at DESC, id DESC);
6
+
7
+ -- Page 1 (no cursor): newest first, page_size 50, +1 row to detect has_more.
8
+ SELECT id, created_at, title
9
+ FROM items
10
+ WHERE tenant_id = $1
11
+ ORDER BY created_at DESC, id DESC
12
+ LIMIT 51;
13
+
14
+ -- Page N (cursor decoded to ($2 = created_at, $3 = id) of the last row seen).
15
+ -- Row-value comparison keeps the composite ordering correct in one clause.
16
+ SELECT id, created_at, title
17
+ FROM items
18
+ WHERE tenant_id = $1
19
+ AND (created_at, id) < ($2, $3)
20
+ ORDER BY created_at DESC, id DESC
21
+ LIMIT 51;
22
+
23
+ -- Verify the index is used (look for "Index Scan ... idx_items_created_id",
24
+ -- no Sort node, and rows examined ≈ LIMIT):
25
+ EXPLAIN (ANALYZE, BUFFERS)
26
+ SELECT id, created_at, title
27
+ FROM items
28
+ WHERE tenant_id = $1 AND (created_at, id) < (now(), 2147483647)
29
+ ORDER BY created_at DESC, id DESC
30
+ LIMIT 51;
31
+
32
+ -- Ascending variant (jump-to-oldest without deep offsets): flip both the
33
+ -- comparison and the sort, same index scanned backwards.
34
+ SELECT id, created_at, title
35
+ FROM items
36
+ WHERE tenant_id = $1 AND (created_at, id) > ($2, $3)
37
+ ORDER BY created_at ASC, id ASC
38
+ LIMIT 51;
@@ -0,0 +1,51 @@
1
+ # Response Shapes
2
+
3
+ ## Cursor-paginated list (recommended default)
4
+
5
+ ```json
6
+ {
7
+ "data": [
8
+ { "id": "itm_9x2", "created_at": "2026-07-01T12:00:00.412Z", "title": "..." }
9
+ ],
10
+ "page_info": {
11
+ "has_more": true,
12
+ "next_cursor": "v1.eyJrIjpbIjIwMjYt...IsNDcxMV0sImQiOiJkZXNjIn0.3f9a1c22b0"
13
+ }
14
+ }
15
+ ```
16
+
17
+ Last page: `has_more: false` and **no** `next_cursor` key. Clients must treat absence — not
18
+ empty string, not null — as the terminal signal (document exactly one convention and test it).
19
+
20
+ ## Offset-paginated list (admin/small data only)
21
+
22
+ ```json
23
+ {
24
+ "data": [ ... ],
25
+ "page_info": { "page": 3, "per_page": 50, "total": 1204, "total_pages": 25 }
26
+ }
27
+ ```
28
+
29
+ Only ship `total`/`total_pages` when the dataset is small enough that COUNT is cheap and the
30
+ product genuinely offers page jumping.
31
+
32
+ ## Request parameters
33
+
34
+ ```
35
+ GET /v1/items?limit=50&cursor=v1.eyJr... # cursor flow
36
+ GET /v1/admin/items?page=3&per_page=50 # offset flow
37
+ ```
38
+
39
+ - `limit` capped server-side (e.g., max 200); over-cap requests get the cap, not an error.
40
+ - Filters/sort are request parameters, never encoded inside the cursor — but changing them
41
+ mid-iteration invalidates the cursor (return `CURSOR_INVALID`, client restarts).
42
+
43
+ ## Error shapes
44
+
45
+ ```json
46
+ { "error": { "code": "CURSOR_EXPIRED", "message": "Restart iteration from the beginning." } }
47
+ { "error": { "code": "CURSOR_INVALID", "message": "Cursor is malformed or filters changed." } }
48
+ ```
49
+
50
+ Both are 400s. Clients restart from their last durable checkpoint; idempotent processing absorbs
51
+ the overlap.
@@ -0,0 +1,39 @@
1
+ # Consuming Paginated APIs Correctly
2
+
3
+ The consumer side has its own failure modes: partial iteration on crash, duplicate processing on
4
+ retry, and silent stalls when a provider changes page semantics.
5
+
6
+ ## The iteration loop
7
+
8
+ ```js
9
+ let cursor = savedCheckpoint ?? null;
10
+ do {
11
+ const page = await get('/items', { cursor, limit: 100 });
12
+ for (const item of page.data) await processIdempotently(item);
13
+ cursor = page.page_info.next_cursor ?? null;
14
+ await saveCheckpoint(cursor); // AFTER processing the page
15
+ } while (cursor);
16
+ ```
17
+
18
+ Rules encoded above:
19
+
20
+ - **Checkpoint the cursor, not the item index.** Persist `next_cursor` after each fully processed
21
+ page; on restart, resume from the checkpoint instead of page one.
22
+ - **Process idempotently.** At-least-once is the reality: a crash between processing and
23
+ checkpointing replays the page. Dedupe on item ID exactly like a webhook consumer.
24
+ - **Loop on `next_cursor` presence, not on `data.length > 0`.** Some providers return short or
25
+ empty pages mid-stream (filtered rows); an empty page with a cursor is NOT the end.
26
+ - **Bound the loop.** A provider bug that returns the same cursor forever becomes your infinite
27
+ loop; track `pages_fetched` against a sane ceiling and alert past it.
28
+
29
+ ## Retry semantics
30
+
31
+ Page fetches are GETs — safe to retry with backoff. But a `CURSOR_EXPIRED` or `CURSOR_INVALID`
32
+ response is not retryable: restart the iteration from your last durable checkpoint (or from
33
+ scratch) and rely on idempotent processing to absorb the overlap.
34
+
35
+ ## Rate limits and burst shape
36
+
37
+ Full-table syncs hammer providers. Respect `Retry-After` on 429s, spread scheduled syncs with
38
+ jitter, and prefer provider-side incremental endpoints (`updated_since=`) over re-walking
39
+ everything — pagination is for the backfill, deltas are for steady state.
@@ -0,0 +1,41 @@
1
+ # Counting and Totals
2
+
3
+ `COUNT(*)` is the hidden cost center of pagination. On a large Postgres table it is a full index
4
+ or heap scan — routinely slower than fetching the page itself — and the number is stale the moment
5
+ it is computed.
6
+
7
+ ## Decision table
8
+
9
+ | Product need | Implementation |
10
+ |---|---|
11
+ | "Load more" / infinite scroll | No count at all — `has_more` from the +1-row trick |
12
+ | "~12,300 results" | Estimated count (below), clearly rounded in the UI |
13
+ | Exact count on small filtered sets (<10k rows) | Real `COUNT(*)`, acceptable |
14
+ | Exact count on big tables, hard requirement | Maintained counter (trigger or async rollup) |
15
+
16
+ ## Estimated counts (Postgres)
17
+
18
+ ```sql
19
+ -- Table-wide estimate: instant, from the planner's statistics
20
+ SELECT reltuples::bigint FROM pg_class WHERE relname = 'items';
21
+
22
+ -- Filtered estimate: run the query through EXPLAIN and parse rows=
23
+ EXPLAIN (FORMAT JSON) SELECT 1 FROM items WHERE status = 'active';
24
+ -- -> Plan.["Plan Rows"]
25
+ ```
26
+
27
+ Estimates drift after bulk writes until autovacuum/analyze runs — fine for "~N results", wrong for
28
+ billing or quotas.
29
+
30
+ ## Maintained counters
31
+
32
+ A trigger-updated counter table gives exact totals at O(1) read cost, but serializes writes on hot
33
+ rows (every insert contends on the same counter row). For high write rates, shard the counter
34
+ (N rows, sum on read) or roll up asynchronously and accept seconds of lag. Never compute quotas
35
+ or invoices from pagination counts — those need their own transactional bookkeeping.
36
+
37
+ ## The `total_pages` trap
38
+
39
+ Exposing `total_pages` invites clients to build page links (`?page=812`), which drags you back to
40
+ deep offsets. If the UI needs a jump-to-end affordance, offer a reversed sort (`?order=asc`)
41
+ instead — last page becomes first page, at first-page cost.
@@ -0,0 +1,34 @@
1
+ # Cursor Encoding
2
+
3
+ A cursor must carry enough to rebuild the keyset `WHERE` clause and nothing more: the sort-key
4
+ values, the direction, and a version byte. It must be opaque, tamper-evident (public APIs), and
5
+ survive encoding changes across deploys.
6
+
7
+ ## Layout
8
+
9
+ ```
10
+ v1 . base64url( json({ k: ["2026-07-01T12:00:00.000Z", 4711], d: "desc" }) ) . hmac10
11
+ ```
12
+
13
+ - **`v1` prefix** — bump when the payload shape changes; reject unknown versions with 400
14
+ `CURSOR_INVALID`, which clients treat as "restart iteration".
15
+ - **base64url**, not base64 — cursors travel in query strings; `+/=` break naive clients.
16
+ - **HMAC (first 10 bytes, hex)** over `version + payload` with a server-side key. Not secrecy —
17
+ integrity. Without it, clients will reverse-engineer the payload and build their own cursors,
18
+ and your encoding becomes an accidental public API you can never change.
19
+
20
+ ## Rules
21
+
22
+ - Timestamps in the payload must be **full precision** (milliseconds or better). Truncated seconds
23
+ re-introduce the duplicate-key ambiguity the tiebreaker was meant to solve.
24
+ - Never embed user IDs, filters, or auth state in the cursor — re-derive those from the request.
25
+ A cursor pasted into another user's session must not leak or authorize anything.
26
+ - Cursors may expire (e.g., if the underlying snapshot is gone). Expired cursor → 400 with a
27
+ distinct `CURSOR_EXPIRED` code so clients restart rather than retry.
28
+ - Treat decode failures as client errors (400), never 500 — fuzzers will send garbage.
29
+
30
+ ## Anti-pattern: the "readable" cursor
31
+
32
+ `?after_id=4711&after_ts=...` looks friendlier but freezes your sort semantics forever: adding a
33
+ tiebreaker, changing direction defaults, or switching the sort column becomes a breaking change.
34
+ Opaque tokens keep the contract at "pass back what we gave you".
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ // Runnable check for the opaque-cursor contract (encode -> decode -> tamper -> version).
3
+ // Usage: node scripts/check-cursor.mjs (exits 0 on pass, 1 on first failure)
4
+ import { createHmac } from 'node:crypto';
5
+
6
+ const KEY = 'demo-key-rotate-me';
7
+ const VERSION = 'v1';
8
+
9
+ const b64u = (buf) => Buffer.from(buf).toString('base64url');
10
+ const sign = (s) => createHmac('sha256', KEY).update(s).digest('hex').slice(0, 20);
11
+
12
+ export function encodeCursor(keyValues, direction = 'desc') {
13
+ const payload = b64u(JSON.stringify({ k: keyValues, d: direction }));
14
+ return `${VERSION}.${payload}.${sign(VERSION + payload)}`;
15
+ }
16
+
17
+ export function decodeCursor(token) {
18
+ const [version, payload, mac] = String(token).split('.');
19
+ if (version !== VERSION) return { error: 'CURSOR_INVALID' }; // unknown version
20
+ if (!payload || sign(version + payload) !== mac) return { error: 'CURSOR_INVALID' };
21
+ try {
22
+ const { k, d } = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
23
+ if (!Array.isArray(k) || !['asc', 'desc'].includes(d)) return { error: 'CURSOR_INVALID' };
24
+ return { keyValues: k, direction: d };
25
+ } catch {
26
+ return { error: 'CURSOR_INVALID' };
27
+ }
28
+ }
29
+
30
+ let failed = 0;
31
+ const check = (name, ok) => {
32
+ console.log(`${ok ? 'PASS' : 'FAIL'} ${name}`);
33
+ if (!ok) failed = 1;
34
+ };
35
+
36
+ // Round trip preserves full timestamp precision and the tiebreaker.
37
+ const token = encodeCursor(['2026-07-01T12:00:00.412Z', 4711]);
38
+ const out = decodeCursor(token);
39
+ check('round-trip', out.keyValues?.[1] === 4711 && out.keyValues?.[0].endsWith('.412Z'));
40
+ check('url-safe (no + / =)', !/[+/=]/.test(token));
41
+
42
+ // Tampered payload must be rejected, not half-parsed.
43
+ const [v, p, m] = token.split('.');
44
+ check('tamper detected', decodeCursor(`${v}.${p.slice(0, -2)}xx.${m}`).error === 'CURSOR_INVALID');
45
+
46
+ // Unknown version and garbage are client errors, never crashes.
47
+ check('unknown version rejected', decodeCursor(`v9.${p}.${m}`).error === 'CURSOR_INVALID');
48
+ check('garbage rejected', decodeCursor('not-a-cursor').error === 'CURSOR_INVALID');
49
+ check('empty rejected', decodeCursor('').error === 'CURSOR_INVALID');
50
+
51
+ process.exit(failed);