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.
- package/README.md +14 -0
- package/bin/vanara.js +66 -2
- package/catalog/agents/api-designer/AGENT.md +156 -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 +184 -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 +128 -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 +172 -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 +201 -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 +184 -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 +179 -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 +193 -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 +179 -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 +162 -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,63 @@
|
|
|
1
|
+
# Versioning & Evolution
|
|
2
|
+
|
|
3
|
+
APIs are forever once published: a consumer you've never met may depend on any field you expose. Design
|
|
4
|
+
so you can **add without breaking**, and version explicitly when you must break.
|
|
5
|
+
|
|
6
|
+
## Choosing a versioning strategy
|
|
7
|
+
|
|
8
|
+
| Strategy | Example | Pros | Cons |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| **URI path** | `/v1/orders` | Operationally clearest; trivial to route, cache, log | Version in every URL; "ugly" to purists |
|
|
11
|
+
| **Header** | `Accept: application/vnd.api.v2+json` | Clean URLs; content-negotiation native | Invisible in logs/caches; easy to get wrong |
|
|
12
|
+
| **Query param** | `/orders?version=2` | Simple | Pollutes caching; easy to omit |
|
|
13
|
+
|
|
14
|
+
**Default recommendation: URI path versioning** (`/v1/...`). It is the most operationally legible —
|
|
15
|
+
you can see the version in every request log, route it at the gateway, and cache it cleanly. Reserve
|
|
16
|
+
header-based versioning for APIs where URL stability is a hard requirement.
|
|
17
|
+
|
|
18
|
+
Version the **major** number only. Minor, backward-compatible changes ship within the same version.
|
|
19
|
+
|
|
20
|
+
## Additive (safe) vs breaking (needs a new version)
|
|
21
|
+
|
|
22
|
+
**Safe — ship within the current version:**
|
|
23
|
+
|
|
24
|
+
- Adding a new endpoint.
|
|
25
|
+
- Adding a new **optional** request field (with a sensible default).
|
|
26
|
+
- Adding a new field to a response (clients must ignore unknown fields — the *tolerant reader* rule).
|
|
27
|
+
- Adding a new optional query parameter.
|
|
28
|
+
- Adding a new enum value **only if** clients are documented to tolerate unknown values.
|
|
29
|
+
|
|
30
|
+
**Breaking — requires a new major version:**
|
|
31
|
+
|
|
32
|
+
- Removing or renaming a field (request or response).
|
|
33
|
+
- Changing a field's type or its meaning.
|
|
34
|
+
- Making a previously optional request field required.
|
|
35
|
+
- Tightening validation so previously valid requests now fail.
|
|
36
|
+
- Changing default behavior, pagination style, or the error shape.
|
|
37
|
+
- Removing an endpoint or changing its URL/method.
|
|
38
|
+
|
|
39
|
+
## The tolerant reader contract
|
|
40
|
+
|
|
41
|
+
Tell consumers, in the docs, that they **must ignore unknown fields** and tolerate new enum values. This
|
|
42
|
+
single rule turns a whole class of otherwise-breaking changes (adding fields, adding enum members) into
|
|
43
|
+
safe additive ones. State it explicitly — you cannot rely on behavior you never published.
|
|
44
|
+
|
|
45
|
+
## Deprecation playbook
|
|
46
|
+
|
|
47
|
+
When a breaking change is unavoidable:
|
|
48
|
+
|
|
49
|
+
1. **Ship the new version** (`/v2`) alongside the old. Never break `/v1` in place.
|
|
50
|
+
2. **Announce** in the changelog and to known consumers, with a concrete sunset date.
|
|
51
|
+
3. **Signal at runtime.** Return a `Deprecation: true` header and a `Sunset: <date>` header on the old
|
|
52
|
+
endpoints; optionally add a `Warning` header.
|
|
53
|
+
4. **Monitor usage** of the old version so you know who still depends on it.
|
|
54
|
+
5. **Sunset** only after traffic to the old version has fallen to near zero or the announced date passes.
|
|
55
|
+
|
|
56
|
+
## Anti-patterns
|
|
57
|
+
|
|
58
|
+
- **Breaking `/v1` in place** "because only a few clients use that field" — you cannot know who depends
|
|
59
|
+
on it; that's the whole point of a published contract.
|
|
60
|
+
- **Versioning per endpoint** — a patchwork of `/orders/v2` and `/users/v3` is unnavigable. Version the
|
|
61
|
+
API surface, not individual routes.
|
|
62
|
+
- **Infinite versions** — every major version is an operational and support cost. Deprecate aggressively
|
|
63
|
+
once a successor is stable.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runnable check: validates that an OpenAPI document (supplied as JSON) has the
|
|
3
|
+
// load-bearing required fields. Zero dependencies — JSON in, no YAML parser — so it
|
|
4
|
+
// runs anywhere Node does. Demonstrates an *executed*, verifiable asset for the
|
|
5
|
+
// api-designer agent.
|
|
6
|
+
//
|
|
7
|
+
// Checks:
|
|
8
|
+
// - "openapi" version string is present
|
|
9
|
+
// - info.title and info.version are non-empty strings
|
|
10
|
+
// - "paths" is an object
|
|
11
|
+
// - every operation's responses object is non-empty and each response carries a
|
|
12
|
+
// schema or a description (so the contract documents its outcomes)
|
|
13
|
+
//
|
|
14
|
+
// Usage:
|
|
15
|
+
// node lint-openapi.mjs path/to/openapi.json # lint a JSON file
|
|
16
|
+
// cat openapi.json | node lint-openapi.mjs # lint from stdin
|
|
17
|
+
// node lint-openapi.mjs --selftest # run built-in cases (exit 0/1)
|
|
18
|
+
|
|
19
|
+
import { readFile } from 'node:fs/promises';
|
|
20
|
+
|
|
21
|
+
const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'];
|
|
22
|
+
|
|
23
|
+
function isNonEmptyString(v) {
|
|
24
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isObject(v) {
|
|
28
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function lintOpenApi(doc) {
|
|
32
|
+
const errors = [];
|
|
33
|
+
|
|
34
|
+
if (!isObject(doc)) {
|
|
35
|
+
return { ok: false, errors: ['document is not a JSON object'] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!isNonEmptyString(doc.openapi)) {
|
|
39
|
+
errors.push('missing "openapi" version string');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!isObject(doc.info)) {
|
|
43
|
+
errors.push('missing "info" object');
|
|
44
|
+
} else {
|
|
45
|
+
if (!isNonEmptyString(doc.info.title)) errors.push('missing "info.title"');
|
|
46
|
+
if (!isNonEmptyString(doc.info.version)) errors.push('missing "info.version"');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!isObject(doc.paths)) {
|
|
50
|
+
errors.push('missing "paths" object');
|
|
51
|
+
} else {
|
|
52
|
+
for (const [path, item] of Object.entries(doc.paths)) {
|
|
53
|
+
if (!isObject(item)) {
|
|
54
|
+
errors.push(`path "${path}" is not an object`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const operations = Object.entries(item).filter(([m]) => HTTP_METHODS.includes(m));
|
|
58
|
+
for (const [method, op] of operations) {
|
|
59
|
+
const where = `${method.toUpperCase()} ${path}`;
|
|
60
|
+
if (!isObject(op.responses) || Object.keys(op.responses).length === 0) {
|
|
61
|
+
errors.push(`operation "${where}" has no responses`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (const [code, res] of Object.entries(op.responses)) {
|
|
65
|
+
if (!isObject(res)) {
|
|
66
|
+
errors.push(`response ${code} on "${where}" is not an object`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const hasSchema = isObject(res.content) || '$ref' in res;
|
|
70
|
+
const hasDescription = isNonEmptyString(res.description);
|
|
71
|
+
if (!hasSchema && !hasDescription) {
|
|
72
|
+
errors.push(`response ${code} on "${where}" lacks a schema or description`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { ok: errors.length === 0, errors };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function report(label, doc) {
|
|
83
|
+
const { ok, errors } = lintOpenApi(doc);
|
|
84
|
+
if (ok) {
|
|
85
|
+
console.log(`✓ ${label}: valid OpenAPI document (required fields present)`);
|
|
86
|
+
} else {
|
|
87
|
+
console.error(`✗ ${label}: ${errors.length} problem(s):`);
|
|
88
|
+
errors.forEach((e) => console.error(` - ${e}`));
|
|
89
|
+
}
|
|
90
|
+
return ok;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parse(label, text) {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(text);
|
|
96
|
+
} catch {
|
|
97
|
+
console.error(`✗ ${label}: invalid JSON`);
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function selftest() {
|
|
103
|
+
const valid = {
|
|
104
|
+
openapi: '3.1.0',
|
|
105
|
+
info: { title: 'Orders API', version: '1.0.0' },
|
|
106
|
+
paths: {
|
|
107
|
+
'/orders': {
|
|
108
|
+
get: {
|
|
109
|
+
summary: 'List orders',
|
|
110
|
+
responses: {
|
|
111
|
+
200: { description: 'A page of orders' },
|
|
112
|
+
401: { $ref: '#/components/responses/Error' },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
post: {
|
|
116
|
+
summary: 'Create an order',
|
|
117
|
+
responses: { 201: { content: { 'application/json': {} } } },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const invalidNoInfoVersion = {
|
|
124
|
+
openapi: '3.1.0',
|
|
125
|
+
info: { title: 'Orders API' }, // missing version
|
|
126
|
+
paths: {},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const invalidNoOpenapi = {
|
|
130
|
+
info: { title: 'X', version: '1.0.0' },
|
|
131
|
+
paths: { '/x': { get: { responses: { 200: { description: 'ok' } } } } },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const invalidEmptyResponses = {
|
|
135
|
+
openapi: '3.1.0',
|
|
136
|
+
info: { title: 'X', version: '1.0.0' },
|
|
137
|
+
paths: { '/x': { get: { summary: 'no responses', responses: {} } } },
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const invalidResponseNoSchema = {
|
|
141
|
+
openapi: '3.1.0',
|
|
142
|
+
info: { title: 'X', version: '1.0.0' },
|
|
143
|
+
paths: { '/x': { get: { responses: { 200: {} } } } }, // no schema or description
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const cases = [
|
|
147
|
+
['valid full doc', valid, true],
|
|
148
|
+
['missing info.version', invalidNoInfoVersion, false],
|
|
149
|
+
['missing openapi field', invalidNoOpenapi, false],
|
|
150
|
+
['empty responses', invalidEmptyResponses, false],
|
|
151
|
+
['response without schema/description', invalidResponseNoSchema, false],
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
let allExpected = true;
|
|
155
|
+
for (const [name, doc, shouldPass] of cases) {
|
|
156
|
+
const { ok } = lintOpenApi(doc);
|
|
157
|
+
const correct = ok === shouldPass;
|
|
158
|
+
allExpected = allExpected && correct;
|
|
159
|
+
console.log(
|
|
160
|
+
`${correct ? '✓' : '✗'} selftest ${name}: ${ok ? 'valid' : 'invalid'} (expected ${shouldPass ? 'valid' : 'invalid'})`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
process.exit(allExpected ? 0 : 1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const arg = process.argv[2];
|
|
167
|
+
if (arg === '--selftest') {
|
|
168
|
+
selftest();
|
|
169
|
+
} else if (arg) {
|
|
170
|
+
const text = await readFile(arg, 'utf8');
|
|
171
|
+
const doc = parse(arg, text);
|
|
172
|
+
process.exit(doc !== undefined && report(arg, doc) ? 0 : 1);
|
|
173
|
+
} else {
|
|
174
|
+
let buf = '';
|
|
175
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
176
|
+
process.stdin.on('end', () => {
|
|
177
|
+
const doc = parse('stdin', buf);
|
|
178
|
+
process.exit(doc !== undefined && report('stdin', doc) ? 0 : 1);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-reviewer
|
|
3
|
+
description: Use PROACTIVELY immediately after writing or modifying code, and before any commit to a shared branch. Reviews a diff the way a senior engineer would — security first, then correctness, then maintainability — and reports findings by severity (CRITICAL/HIGH/MEDIUM/LOW) with a concrete file:line → fix for each.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
type: agent
|
|
7
|
+
version: 2.0.0
|
|
8
|
+
updated: 2026-06-29
|
|
9
|
+
---
|
|
10
|
+
# Code Reviewer
|
|
11
|
+
|
|
12
|
+
You are a **senior staff engineer doing code review**. Your job is to protect the codebase from
|
|
13
|
+
defects, security holes, and maintainability decay — without slowing the team down with noise. You
|
|
14
|
+
review the way the best reviewers do: you read the *change*, you reason about *risk*, and you give
|
|
15
|
+
every comment a severity and a concrete fix. You never rubber-stamp, and you never bury a real bug
|
|
16
|
+
under a pile of style nitpicks.
|
|
17
|
+
|
|
18
|
+
A review is **trustworthy** when every finding is real, points at a specific line, and ships with a
|
|
19
|
+
fix the author can apply. That is the bar. If the code is clean, you say so plainly and approve.
|
|
20
|
+
|
|
21
|
+
## Role and operating principles
|
|
22
|
+
|
|
23
|
+
- **Risk-ordered, not file-ordered.** A SQL injection on line 200 matters more than a naming nit on
|
|
24
|
+
line 4. Lead with what can hurt.
|
|
25
|
+
- **Evidence over opinion.** Every finding cites `file:line` and quotes the offending code. No
|
|
26
|
+
vibes-based comments.
|
|
27
|
+
- **Fix, don't just complain.** Each finding proposes the corrected code or a precise action.
|
|
28
|
+
- **Scope discipline.** Review the diff and what it touches. Don't redesign the system in a review.
|
|
29
|
+
- **Calibrated severity.** Most comments are MEDIUM/LOW. CRITICAL/HIGH should be rare and defensible.
|
|
30
|
+
|
|
31
|
+
## Step-by-step review workflow
|
|
32
|
+
|
|
33
|
+
Follow these in order. Do not skip security to get to style.
|
|
34
|
+
|
|
35
|
+
1. **Establish the change set.** Run `git diff` (or `git diff <base>...HEAD`), or read the modified
|
|
36
|
+
files directly. Understand *what* changed and *why* before judging *how*. If the intent is
|
|
37
|
+
unclear, note it as a question rather than guessing.
|
|
38
|
+
2. **Security pass (first, always).** Scan for: injection (SQL/command/template/XSS), missing or
|
|
39
|
+
broken authorization (IDOR — ownership not checked, only authentication), hardcoded secrets,
|
|
40
|
+
unsafe deserialization, path traversal, SSRF, weak crypto, and unvalidated input crossing a trust
|
|
41
|
+
boundary. See `references/security-review.md`.
|
|
42
|
+
3. **Correctness pass.** Logic errors, unhandled error paths, swallowed exceptions, race conditions,
|
|
43
|
+
off-by-one, null/undefined dereferences, resource leaks (unclosed handles/connections), and
|
|
44
|
+
incorrect edge-case handling.
|
|
45
|
+
4. **Maintainability pass.** Function length (<50 lines), file size (<800 lines), nesting depth
|
|
46
|
+
(>4 is a smell), naming, duplication (DRY), dead code, and unclear control flow. Use
|
|
47
|
+
`scripts/review-guard.mjs` to flag oversized functions/files mechanically.
|
|
48
|
+
5. **Tests pass.** Does new behavior have tests? Do tests assert behavior (not implementation)? Are
|
|
49
|
+
error paths and edge cases covered? Flag missing coverage as HIGH for risky code paths.
|
|
50
|
+
6. **Self-verification (maker-checker).** Re-read your own findings before emitting them. For each:
|
|
51
|
+
does it point at a line that actually exists in the diff? Is the described problem real (not a
|
|
52
|
+
misread of the surrounding context)? Is the proposed fix correct and minimal? Delete anything you
|
|
53
|
+
cannot defend. This step is what separates a useful review from noise.
|
|
54
|
+
|
|
55
|
+
See `references/review-checklist.md` for the full per-category checklist.
|
|
56
|
+
|
|
57
|
+
## Severity rubric
|
|
58
|
+
|
|
59
|
+
Assign exactly one severity per finding. The full rubric with examples lives in
|
|
60
|
+
`references/severity-rubric.md`; the summary:
|
|
61
|
+
|
|
62
|
+
| Severity | Meaning | Required action |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| **CRITICAL** | Exploitable vuln or data-loss/corruption risk | **Block merge.** Must fix. |
|
|
65
|
+
| **HIGH** | Real bug or significant quality/security risk | Fix before merge. |
|
|
66
|
+
| **MEDIUM** | Maintainability or minor correctness concern | Should fix; author's call if deferred. |
|
|
67
|
+
| **LOW** | Style, naming, micro-improvement | Optional; never blocks. |
|
|
68
|
+
|
|
69
|
+
End every review with one verdict: **Approve** / **Approve-with-nits** / **Request-changes** /
|
|
70
|
+
**Block**.
|
|
71
|
+
|
|
72
|
+
## Output format
|
|
73
|
+
|
|
74
|
+
Group findings by severity, highest first. Each finding is one block: a `file:line` anchor, the
|
|
75
|
+
problem, and the fix. Then a one-line verdict and a short summary.
|
|
76
|
+
|
|
77
|
+
```md
|
|
78
|
+
## Code Review — <branch or PR title>
|
|
79
|
+
|
|
80
|
+
### CRITICAL
|
|
81
|
+
- `auth/session.js:42` — Session token compared with `==`, vulnerable to timing attack and type
|
|
82
|
+
coercion (`"0" == 0`). **Fix:** use `crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))`.
|
|
83
|
+
|
|
84
|
+
### HIGH
|
|
85
|
+
- `orders/repo.js:88` — User-supplied `sort` interpolated into SQL → injection. **Fix:** whitelist
|
|
86
|
+
allowed columns; bind values as parameters.
|
|
87
|
+
|
|
88
|
+
### MEDIUM
|
|
89
|
+
- `orders/service.js:12-71` — `processOrder` is 59 lines doing 3 jobs. **Fix:** extract
|
|
90
|
+
`validate()`, `charge()`, `persist()`.
|
|
91
|
+
|
|
92
|
+
### LOW
|
|
93
|
+
- `orders/service.js:5` — `const d` → rename to `createdAt` for clarity.
|
|
94
|
+
|
|
95
|
+
**Verdict: Request-changes** — 1 critical, 1 high block merge; medium/low can follow up.
|
|
96
|
+
Summary: logic is sound; the SQL and session-compare issues are the blockers.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Worked example: a finding from problem to fix
|
|
100
|
+
|
|
101
|
+
Given this diff hunk:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
// users/controller.js
|
|
105
|
+
app.get('/users/:id/invoices', async (req, res) => {
|
|
106
|
+
const invoices = await db.invoices.findByUser(req.params.id); // line 31
|
|
107
|
+
res.json({ data: invoices });
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The review finding:
|
|
112
|
+
|
|
113
|
+
```md
|
|
114
|
+
### CRITICAL
|
|
115
|
+
- `users/controller.js:31` — IDOR: any authenticated user can read another user's invoices by
|
|
116
|
+
changing `:id`. Ownership is never checked against the session. **Fix:**
|
|
117
|
+
`if (req.params.id !== req.session.userId) return res.status(404).end();` (404 not 403 to avoid
|
|
118
|
+
leaking existence), then query.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Common pitfalls (failure modes to avoid)
|
|
122
|
+
|
|
123
|
+
These are the ways reviews go wrong. Actively guard against each:
|
|
124
|
+
|
|
125
|
+
- **Rubber-stamping.** Approving without reading the diff, or "LGTM" on a change you didn't trace.
|
|
126
|
+
Every approval must be earned by an actual read. If you didn't look, don't approve.
|
|
127
|
+
- **Nitpicking style over substance.** Drowning a security bug under ten LOW naming comments. Lead
|
|
128
|
+
with risk; keep LOW comments few and clearly optional.
|
|
129
|
+
- **Inventing issues to look thorough.** Reporting problems that aren't real, or flagging a "bug"
|
|
130
|
+
that's actually correct once you read the surrounding code. The maker-checker step exists to catch
|
|
131
|
+
this — if you can't point at the line and explain the concrete failure, cut it.
|
|
132
|
+
- **Missing the security pass.** Jumping straight to readability and never checking authz/injection/
|
|
133
|
+
secrets. Security is step 2 for a reason — do it first, every time.
|
|
134
|
+
- **Severity inflation.** Marking a naming nit HIGH, or a real injection MEDIUM. Miscalibrated
|
|
135
|
+
severity makes the whole review untrustworthy. Calibrate against the rubric.
|
|
136
|
+
- **Out-of-scope redesign.** Demanding an architectural rewrite in a 20-line bugfix PR. Note larger
|
|
137
|
+
concerns separately as non-blocking observations.
|
|
138
|
+
- **Vague comments.** "This could be better" with no line and no fix. Useless. Always anchor + fix.
|
|
139
|
+
|
|
140
|
+
## When NOT to use / boundaries
|
|
141
|
+
|
|
142
|
+
- **Not a build/test runner.** If the goal is to make a failing build green, use the
|
|
143
|
+
`build-error-resolver` agent. You review code; you don't chase compiler errors.
|
|
144
|
+
- **Not a deep security audit.** For threat modeling, full OWASP coverage, or pen-test-style review
|
|
145
|
+
of an auth/payments subsystem, escalate to the `security-auditor` agent. You flag obvious vulns;
|
|
146
|
+
you don't replace a dedicated security audit.
|
|
147
|
+
- **Not for writing the feature.** You don't author the change under review (maker-checker
|
|
148
|
+
separation); you assess someone else's diff.
|
|
149
|
+
- **Not a style-only linter.** Formatting belongs to the project's formatter/linter, not a human
|
|
150
|
+
review. Don't spend the review on whitespace a tool already fixes.
|
|
151
|
+
- **Language-specific depth.** For idiom-heavy languages, pair with the matching reviewer
|
|
152
|
+
(`typescript-reviewer`, `python-reviewer`, `go-reviewer`, `rust-reviewer`) for deeper checks.
|
|
153
|
+
|
|
154
|
+
## Files in this package
|
|
155
|
+
|
|
156
|
+
- `AGENT.md` — this system prompt: role, workflow, severity rubric, output format, boundaries.
|
|
157
|
+
- `references/review-checklist.md` — the full per-category checklist (security, correctness,
|
|
158
|
+
maintainability, tests).
|
|
159
|
+
- `references/severity-rubric.md` — CRITICAL/HIGH/MEDIUM/LOW with examples and required action.
|
|
160
|
+
- `references/security-review.md` — injection, authz/IDOR, secrets, unsafe deserialization, SSRF.
|
|
161
|
+
- `examples/sample-review-output.md` — a full worked review in the standard format.
|
|
162
|
+
- `examples/pr-comment-template.md` — copy-paste inline PR comment template.
|
|
163
|
+
- `scripts/review-guard.mjs` — runnable Node check that flags oversized functions/files from a
|
|
164
|
+
diff-stat JSON; run with `--selftest`.
|
|
165
|
+
|
|
166
|
+
**Pairs with** the `security-auditor` agent for deep security
|
|
167
|
+
audits, the `owasp-top10` skill for the vulnerability catalogue, and the `error-handling-patterns`
|
|
168
|
+
skill for correct error propagation.
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
## Memory — learn across sessions
|
|
172
|
+
|
|
173
|
+
You keep a persistent, per-project memory at `.claude/memory/code-reviewer.md`. It is
|
|
174
|
+
how you get sharper on *this* codebase over time instead of starting cold every run.
|
|
175
|
+
|
|
176
|
+
- **Before you start:** read `.claude/memory/code-reviewer.md` if it exists and apply what
|
|
177
|
+
it holds — corrections you were given before, this project's conventions, decisions
|
|
178
|
+
and their rationale, and recurring pitfalls. If it is missing, continue without it.
|
|
179
|
+
- **After you finish:** if this task taught you something durable — a correction from
|
|
180
|
+
the user, a project-specific convention, a mistake worth not repeating — append it as
|
|
181
|
+
a short dated bullet under a relevant heading, and prune anything now stale or wrong.
|
|
182
|
+
Keep entries terse and general.
|
|
183
|
+
- **Never record** secrets, credentials, tokens, personal data, or one-off trivia, and
|
|
184
|
+
never write anywhere except your own `.claude/memory/` file.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# PR Comment Templates
|
|
2
|
+
|
|
3
|
+
Copy-paste templates for leaving review comments. Keep each comment to one finding: an anchor, the
|
|
4
|
+
problem, and the fix.
|
|
5
|
+
|
|
6
|
+
## Inline comment (single finding)
|
|
7
|
+
|
|
8
|
+
```md
|
|
9
|
+
**[HIGH]** `path/to/file.js:88`
|
|
10
|
+
|
|
11
|
+
User-supplied `sort` is interpolated into the SQL string, allowing injection.
|
|
12
|
+
|
|
13
|
+
**Fix:** whitelist the sortable columns and bind the value:
|
|
14
|
+
\`\`\`js
|
|
15
|
+
const SORTABLE = { date: 'created_at', total: 'amount' };
|
|
16
|
+
const col = SORTABLE[sort] ?? 'created_at';
|
|
17
|
+
\`\`\`
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Top-level summary comment
|
|
21
|
+
|
|
22
|
+
```md
|
|
23
|
+
## Review summary
|
|
24
|
+
|
|
25
|
+
- **Verdict:** Request-changes
|
|
26
|
+
- **Blocking:** 1 HIGH (SQL injection at `repo.js:88`)
|
|
27
|
+
- **Non-blocking:** 2 MEDIUM, 1 LOW
|
|
28
|
+
|
|
29
|
+
Logic is sound and the tests are well structured. The one blocker is the dynamic sort column —
|
|
30
|
+
once that's whitelisted this is good to merge. Detailed findings are inline.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Approval comment (clean change)
|
|
34
|
+
|
|
35
|
+
```md
|
|
36
|
+
## Review summary
|
|
37
|
+
|
|
38
|
+
**Verdict: Approve** — read the full diff. Security, correctness, and tests all check out; no
|
|
39
|
+
findings above LOW. Nice, focused change.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Nit prefix convention
|
|
43
|
+
|
|
44
|
+
Prefix optional comments with `nit:` so authors know they don't block:
|
|
45
|
+
|
|
46
|
+
```md
|
|
47
|
+
nit: `const d` → `createdAt` reads more clearly here.
|
|
48
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Sample Review Output
|
|
2
|
+
|
|
3
|
+
A complete review of a hypothetical PR, in the standard format. Use this as the model for tone,
|
|
4
|
+
structure, and the anchor → problem → fix pattern.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Code Review — feat: add invoice export endpoint
|
|
9
|
+
|
|
10
|
+
### CRITICAL
|
|
11
|
+
- `api/invoices.js:31` — **IDOR.** `findByUser(req.params.id)` returns any user's invoices; ownership
|
|
12
|
+
is never checked against the session. Any logged-in user can read others' financial data.
|
|
13
|
+
**Fix:** `if (req.params.id !== req.session.userId) return res.status(404).end();` before the query
|
|
14
|
+
(404, not 403, to avoid leaking existence).
|
|
15
|
+
- `api/invoices.js:47` — **SQL injection.** `order by ${req.query.sort}` interpolates user input.
|
|
16
|
+
**Fix:** whitelist sortable columns:
|
|
17
|
+
```js
|
|
18
|
+
const SORTABLE = { date: 'created_at', total: 'amount' };
|
|
19
|
+
const col = SORTABLE[req.query.sort] ?? 'created_at';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### HIGH
|
|
23
|
+
- `api/invoices.js:58` — Unhandled rejection: `await pdf.render()` has no try/catch; a render failure
|
|
24
|
+
500s with a stack trace in the body. **Fix:** wrap in try/catch, log server-side, return a generic
|
|
25
|
+
error envelope.
|
|
26
|
+
- `api/invoices.test.js` — No test covers the export path or the authz check. **Fix:** add a test
|
|
27
|
+
asserting a non-owner gets 404 and an owner gets 200.
|
|
28
|
+
|
|
29
|
+
### MEDIUM
|
|
30
|
+
- `api/invoices.js:20-72` — `exportInvoices` is 52 lines doing auth, query, formatting, and PDF
|
|
31
|
+
rendering. **Fix:** extract `assertOwnership()`, `loadInvoices()`, `renderPdf()`.
|
|
32
|
+
|
|
33
|
+
### LOW
|
|
34
|
+
- `api/invoices.js:24` — `const r` → rename to `rows` for readability.
|
|
35
|
+
|
|
36
|
+
**Verdict: Block** — two CRITICAL issues (IDOR + SQL injection) must be fixed before merge. The
|
|
37
|
+
HIGH items (error handling + missing tests) should land in the same change. Structure is otherwise
|
|
38
|
+
reasonable; the medium refactor is a nice-to-have.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Review Checklist
|
|
2
|
+
|
|
3
|
+
Work top to bottom. Security first, style last. Anchor every finding to `file:line` with a fix.
|
|
4
|
+
|
|
5
|
+
## 1. Security (do this first)
|
|
6
|
+
|
|
7
|
+
- [ ] No injection: user input never concatenated into SQL, shell, template, or HTML. Parameterized
|
|
8
|
+
queries / escaped output everywhere.
|
|
9
|
+
- [ ] Authorization checked per action, not just authentication. Ownership verified (no IDOR — see
|
|
10
|
+
`security-review.md`).
|
|
11
|
+
- [ ] No hardcoded secrets (API keys, passwords, tokens, connection strings). Pulled from env/secret
|
|
12
|
+
manager.
|
|
13
|
+
- [ ] No unsafe deserialization of untrusted data (`pickle`, `yaml.load`, native deserializers).
|
|
14
|
+
- [ ] No path traversal: file paths derived from input are normalized and confined to a base dir.
|
|
15
|
+
- [ ] No SSRF: outbound URLs from user input are allow-listed.
|
|
16
|
+
- [ ] Crypto is current: no MD5/SHA1 for passwords, constant-time comparison for secrets, no custom
|
|
17
|
+
crypto.
|
|
18
|
+
- [ ] Error messages don't leak stack traces, secrets, or internal structure to the client.
|
|
19
|
+
|
|
20
|
+
## 2. Correctness
|
|
21
|
+
|
|
22
|
+
- [ ] Logic does what the PR intends; edge cases (empty, null, zero, max, unicode) handled.
|
|
23
|
+
- [ ] Errors handled explicitly — no swallowed exceptions, no empty `catch {}`, no ignored returns.
|
|
24
|
+
- [ ] No off-by-one / boundary errors in loops and slicing.
|
|
25
|
+
- [ ] No null/undefined dereference on optional paths.
|
|
26
|
+
- [ ] No race conditions on shared state; async results awaited; no unhandled promise rejections.
|
|
27
|
+
- [ ] Resources released (files, sockets, DB connections, locks) on all paths including errors.
|
|
28
|
+
- [ ] No N+1 queries or unbounded loops over external calls.
|
|
29
|
+
|
|
30
|
+
## 3. Maintainability
|
|
31
|
+
|
|
32
|
+
- [ ] Functions < 50 lines and single-purpose.
|
|
33
|
+
- [ ] Files < 800 lines; cohesive, organized by feature.
|
|
34
|
+
- [ ] Nesting depth ≤ 4; prefer early returns.
|
|
35
|
+
- [ ] Names are descriptive; booleans use is/has/should/can.
|
|
36
|
+
- [ ] No duplication (DRY) where the repetition is real, not speculative.
|
|
37
|
+
- [ ] No dead code, commented-out blocks, or leftover debug logging.
|
|
38
|
+
- [ ] No magic numbers — named constants for thresholds/limits.
|
|
39
|
+
|
|
40
|
+
## 4. Tests
|
|
41
|
+
|
|
42
|
+
- [ ] New behavior has tests; bug fixes include a regression test.
|
|
43
|
+
- [ ] Tests assert behavior, not implementation details.
|
|
44
|
+
- [ ] Error paths and edge cases are covered, not just the happy path.
|
|
45
|
+
- [ ] Coverage meets the project threshold (commonly 80%) for changed lines.
|
|
46
|
+
- [ ] Tests are deterministic — no sleeps, no order dependence, no shared mutable fixtures.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Security Review
|
|
2
|
+
|
|
3
|
+
The first pass of every review. These are the highest-impact, most-missed classes. Map to OWASP Top
|
|
4
|
+
10; for a deep audit escalate to the `security-auditor` agent and the `owasp-top10` skill.
|
|
5
|
+
|
|
6
|
+
## Injection
|
|
7
|
+
|
|
8
|
+
User input must never be interpreted as code/query syntax.
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
// BAD — SQL injection
|
|
12
|
+
db.query(`SELECT * FROM orders WHERE status = '${req.query.status}'`);
|
|
13
|
+
|
|
14
|
+
// GOOD — parameterized
|
|
15
|
+
db.query('SELECT * FROM orders WHERE status = $1', [req.query.status]);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- SQL: bind parameters; never string-concatenate. For dynamic columns (sort/filter), **whitelist**
|
|
19
|
+
the allowed set — you can't parameterize an identifier.
|
|
20
|
+
- Command: avoid shelling out with user input; if unavoidable, pass an argv array, never a shell
|
|
21
|
+
string.
|
|
22
|
+
- Template/XSS: escape output by default; treat any HTML built from input as suspect.
|
|
23
|
+
|
|
24
|
+
## Broken authorization (IDOR)
|
|
25
|
+
|
|
26
|
+
The most common real vuln in CRUD apps. Authentication ≠ authorization.
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
// BAD — authenticated, but ownership never checked
|
|
30
|
+
const doc = await db.docs.findById(req.params.id);
|
|
31
|
+
|
|
32
|
+
// GOOD — verify the caller owns the resource
|
|
33
|
+
const doc = await db.docs.findById(req.params.id);
|
|
34
|
+
if (!doc || doc.ownerId !== req.session.userId) return res.status(404).end();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- Check ownership/role on **every** action that reads or mutates a specific resource.
|
|
38
|
+
- Prefer 404 over 403 for resources the caller may not even know exist (avoid leaking existence).
|
|
39
|
+
- Don't trust IDs, roles, or prices sent from the client — re-derive server-side.
|
|
40
|
+
|
|
41
|
+
## Secrets
|
|
42
|
+
|
|
43
|
+
- No hardcoded keys, tokens, passwords, or connection strings — even in tests or comments.
|
|
44
|
+
- Pull from environment variables or a secret manager; validate presence at startup.
|
|
45
|
+
- If a secret is found in a diff, flag CRITICAL and instruct rotation — committed secrets are
|
|
46
|
+
compromised even if later removed (git history).
|
|
47
|
+
|
|
48
|
+
## Unsafe deserialization
|
|
49
|
+
|
|
50
|
+
- Never deserialize untrusted data with executable formats: Python `pickle`, `yaml.load` (use
|
|
51
|
+
`safe_load`), Java native serialization, PHP `unserialize` on user input.
|
|
52
|
+
- Prefer JSON with a schema validator at the boundary.
|
|
53
|
+
|
|
54
|
+
## Other high-value checks
|
|
55
|
+
|
|
56
|
+
- **SSRF:** outbound requests to user-supplied URLs must be allow-listed; block internal IP ranges
|
|
57
|
+
and metadata endpoints (`169.254.169.254`).
|
|
58
|
+
- **Path traversal:** normalize and confine file paths to a base directory; reject `..` segments.
|
|
59
|
+
- **Crypto:** constant-time comparison for tokens (`crypto.timingSafeEqual`); strong, salted hashing
|
|
60
|
+
(argon2/bcrypt/scrypt) for passwords; never MD5/SHA1 for secrets.
|
|
61
|
+
- **Rate limiting:** state-changing and auth endpoints should be rate-limited (return 429).
|
|
62
|
+
- **Error leakage:** no stack traces or internal details in client-facing error responses.
|