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,76 @@
|
|
|
1
|
+
# Code Smell Catalogue
|
|
2
|
+
|
|
3
|
+
A smell is a surface symptom that usually points to a deeper structural problem. Each entry names the
|
|
4
|
+
smell, how to spot it, and the canonical behavior-preserving refactoring that addresses it. Apply one
|
|
5
|
+
at a time, tests green between each.
|
|
6
|
+
|
|
7
|
+
## Bloaters
|
|
8
|
+
|
|
9
|
+
### Long function
|
|
10
|
+
- **Spot it:** more than ~20–50 lines, multiple levels of abstraction in one body, comments that
|
|
11
|
+
section it into "phases".
|
|
12
|
+
- **Fix:** **Extract Function** for each phase; name each by *what* it does, not *how*.
|
|
13
|
+
|
|
14
|
+
### Large class / module
|
|
15
|
+
- **Spot it:** a file past ~800 lines doing several unrelated jobs.
|
|
16
|
+
- **Fix:** **Extract Class / Extract Module** along responsibility lines; move related fields and the
|
|
17
|
+
methods that use them together.
|
|
18
|
+
|
|
19
|
+
### Long parameter list
|
|
20
|
+
- **Spot it:** four or more parameters, especially several passed as a group everywhere.
|
|
21
|
+
- **Fix:** **Introduce Parameter Object** or **Preserve Whole Object**.
|
|
22
|
+
|
|
23
|
+
### Primitive obsession
|
|
24
|
+
- **Spot it:** raw strings/ints carrying domain meaning (a `string currency`, a `number cents`).
|
|
25
|
+
- **Fix:** **Replace Primitive with Value Object**; centralize validation in the new type.
|
|
26
|
+
|
|
27
|
+
## Conditional complexity
|
|
28
|
+
|
|
29
|
+
### Deep nesting
|
|
30
|
+
- **Spot it:** arrow-shaped code, 3+ nested `if`s, the happy path buried at the bottom.
|
|
31
|
+
- **Fix:** **Replace Nested Conditional with Guard Clauses**; return/throw early, flatten the rest.
|
|
32
|
+
|
|
33
|
+
### Switch / type-code dispatch
|
|
34
|
+
- **Spot it:** the same `switch` on a type field repeated in several places.
|
|
35
|
+
- **Fix:** **Replace Conditional with Polymorphism** (or a lookup table / strategy map).
|
|
36
|
+
|
|
37
|
+
### Repeated boolean expressions
|
|
38
|
+
- **Spot it:** the same compound condition spelled out multiple times.
|
|
39
|
+
- **Fix:** **Extract Function** with an intention-revealing name (`isEligible(order)`).
|
|
40
|
+
|
|
41
|
+
## Duplication
|
|
42
|
+
|
|
43
|
+
### Duplicated code
|
|
44
|
+
- **Spot it:** the same statements (or near-identical) in two or more places.
|
|
45
|
+
- **Fix:** **Extract Function** and call it; if duplicated across classes, **Pull Up Method**.
|
|
46
|
+
|
|
47
|
+
### Shotgun surgery
|
|
48
|
+
- **Spot it:** one logical change forces edits in many scattered files.
|
|
49
|
+
- **Fix:** **Move Method / Move Field** to gather the responsibility into one place.
|
|
50
|
+
|
|
51
|
+
## Naming and clarity
|
|
52
|
+
|
|
53
|
+
### Mysterious name
|
|
54
|
+
- **Spot it:** `data2`, `tmp`, `doStuff`, single-letter non-loop variables.
|
|
55
|
+
- **Fix:** **Rename** to reveal intent. Cheap, high value, almost always safe with tooling.
|
|
56
|
+
|
|
57
|
+
### Comments compensating for unclear code
|
|
58
|
+
- **Spot it:** a comment explaining *what* a block does.
|
|
59
|
+
- **Fix:** **Extract Function** named after the comment, then delete the comment.
|
|
60
|
+
|
|
61
|
+
## Coupling
|
|
62
|
+
|
|
63
|
+
### Feature envy
|
|
64
|
+
- **Spot it:** a method that reaches into another object's data more than its own.
|
|
65
|
+
- **Fix:** **Move Method** to the class that owns the data.
|
|
66
|
+
|
|
67
|
+
### Message chains / inappropriate intimacy
|
|
68
|
+
- **Spot it:** `a.getB().getC().getD()`; objects knowing each other's internals.
|
|
69
|
+
- **Fix:** **Hide Delegate**; expose intent-level methods instead of chains.
|
|
70
|
+
|
|
71
|
+
## How to use this catalogue
|
|
72
|
+
|
|
73
|
+
1. Find the smell that matches what you see.
|
|
74
|
+
2. Confirm tests cover the code (add characterization tests if not).
|
|
75
|
+
3. Apply only the canonical fix, in small steps, re-running tests after each.
|
|
76
|
+
4. Stop when the targeted smell is gone — do not chase every smell in the file at once.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runnable check: scans a string of code for two structural smells this agent
|
|
3
|
+
// targets — long functions and deeply nested blocks — and reports them. It is a
|
|
4
|
+
// heuristic indicator (brace/indent based, language-agnostic), not a parser.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node detect-smells.mjs '<code>' # scan a literal code string
|
|
8
|
+
// echo '<code>' | node detect-smells.mjs # scan from stdin
|
|
9
|
+
// node detect-smells.mjs --selftest # run built-in smelly & clean cases
|
|
10
|
+
//
|
|
11
|
+
// Zero dependencies, Node built-ins only. Exit 0 = no smells found (or selftest
|
|
12
|
+
// passed); exit 1 = smells found (or selftest failed).
|
|
13
|
+
|
|
14
|
+
const MAX_FUNCTION_LINES = 25; // a function body longer than this is "long"
|
|
15
|
+
const MAX_NESTING_DEPTH = 3; // brace depth deeper than this is "deeply nested"
|
|
16
|
+
|
|
17
|
+
// Detect function-like headers across common languages (JS/TS, Python, Go, Java-ish).
|
|
18
|
+
const FUNC_RE = /(\bfunction\b|\bdef\b|=>\s*\{|\bfunc\b|\)\s*\{)/;
|
|
19
|
+
|
|
20
|
+
export function detectSmells(code, opts = {}) {
|
|
21
|
+
const maxLines = opts.maxLines ?? MAX_FUNCTION_LINES;
|
|
22
|
+
const maxDepth = opts.maxDepth ?? MAX_NESTING_DEPTH;
|
|
23
|
+
const lines = String(code).split('\n');
|
|
24
|
+
const smells = [];
|
|
25
|
+
|
|
26
|
+
// --- Deep nesting: track running brace depth ---
|
|
27
|
+
let depth = 0;
|
|
28
|
+
let deepestReported = false;
|
|
29
|
+
lines.forEach((line, i) => {
|
|
30
|
+
const opens = (line.match(/\{/g) || []).length;
|
|
31
|
+
const closes = (line.match(/\}/g) || []).length;
|
|
32
|
+
// depth on this line is measured after opens, before this line's closes apply
|
|
33
|
+
const lineDepth = depth + opens;
|
|
34
|
+
if (lineDepth > maxDepth && !deepestReported) {
|
|
35
|
+
smells.push({
|
|
36
|
+
type: 'deep-nesting',
|
|
37
|
+
line: i + 1,
|
|
38
|
+
message: `nesting depth ${lineDepth} exceeds ${maxDepth} (consider guard clauses / extract function)`,
|
|
39
|
+
});
|
|
40
|
+
deepestReported = true; // report once to avoid noise
|
|
41
|
+
}
|
|
42
|
+
depth += opens - closes;
|
|
43
|
+
if (depth < 0) depth = 0;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// --- Long functions: measure span from a function header to its matching close,
|
|
47
|
+
// or (brace-less languages) to the next blank line / dedent boundary. ---
|
|
48
|
+
for (let i = 0; i < lines.length; i++) {
|
|
49
|
+
if (!FUNC_RE.test(lines[i])) continue;
|
|
50
|
+
const span = spanOfFunction(lines, i);
|
|
51
|
+
if (span > maxLines) {
|
|
52
|
+
smells.push({
|
|
53
|
+
type: 'long-function',
|
|
54
|
+
line: i + 1,
|
|
55
|
+
message: `function spans ${span} lines, exceeds ${maxLines} (consider extract function)`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
i += Math.max(0, span - 1); // skip past the counted body
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { ok: smells.length === 0, smells };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function spanOfFunction(lines, start) {
|
|
65
|
+
const header = lines[start];
|
|
66
|
+
if (header.includes('{')) {
|
|
67
|
+
// brace-counted span
|
|
68
|
+
let depth = 0, seen = false;
|
|
69
|
+
for (let j = start; j < lines.length; j++) {
|
|
70
|
+
depth += (lines[j].match(/\{/g) || []).length;
|
|
71
|
+
depth -= (lines[j].match(/\}/g) || []).length;
|
|
72
|
+
if (depth > 0) seen = true;
|
|
73
|
+
if (seen && depth <= 0) return j - start + 1;
|
|
74
|
+
}
|
|
75
|
+
return lines.length - start;
|
|
76
|
+
}
|
|
77
|
+
// brace-less (e.g. Python): count until a blank line or a dedent to column 0
|
|
78
|
+
const baseIndent = indentOf(header);
|
|
79
|
+
let j = start + 1;
|
|
80
|
+
for (; j < lines.length; j++) {
|
|
81
|
+
const ln = lines[j];
|
|
82
|
+
if (ln.trim() === '') break;
|
|
83
|
+
if (indentOf(ln) <= baseIndent && ln.trim() !== '') break;
|
|
84
|
+
}
|
|
85
|
+
return j - start;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function indentOf(line) {
|
|
89
|
+
const m = line.match(/^(\s*)/);
|
|
90
|
+
return m ? m[1].length : 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function report(label, code) {
|
|
94
|
+
const { ok, smells } = detectSmells(code);
|
|
95
|
+
if (ok) {
|
|
96
|
+
console.log(`✓ ${label}: no structural smells detected`);
|
|
97
|
+
} else {
|
|
98
|
+
console.error(`✗ ${label}: ${smells.length} smell(s) found`);
|
|
99
|
+
for (const s of smells) console.error(` - [${s.type}] line ${s.line}: ${s.message}`);
|
|
100
|
+
}
|
|
101
|
+
return ok;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function selftest() {
|
|
105
|
+
const smelly = [
|
|
106
|
+
'function f(a) {',
|
|
107
|
+
' if (a) {',
|
|
108
|
+
' if (a.b) {',
|
|
109
|
+
' if (a.b.c) {', // depth 4 -> deep nesting
|
|
110
|
+
' return a.b.c;',
|
|
111
|
+
' }',
|
|
112
|
+
' }',
|
|
113
|
+
' }',
|
|
114
|
+
'}',
|
|
115
|
+
].join('\n');
|
|
116
|
+
|
|
117
|
+
const longFn = 'function big() {\n' + Array.from({ length: 30 }, (_, i) => ` const x${i} = ${i};`).join('\n') + '\n}';
|
|
118
|
+
|
|
119
|
+
const clean = [
|
|
120
|
+
'function priceFor(order) {',
|
|
121
|
+
' if (order == null) throw new Error("no order");',
|
|
122
|
+
' if (order.items.length === 0) throw new Error("empty");',
|
|
123
|
+
' return order.subtotal - order.discount;',
|
|
124
|
+
'}',
|
|
125
|
+
].join('\n');
|
|
126
|
+
|
|
127
|
+
const cases = [
|
|
128
|
+
['smelly_nesting', smelly, false],
|
|
129
|
+
['smelly_long_function', longFn, false],
|
|
130
|
+
['clean', clean, true],
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
let allPass = true;
|
|
134
|
+
for (const [name, code, expectOk] of cases) {
|
|
135
|
+
const { ok } = detectSmells(code);
|
|
136
|
+
const correct = ok === expectOk;
|
|
137
|
+
allPass &&= correct;
|
|
138
|
+
console.log(`${correct ? '✓' : '✗'} selftest ${name}: ${ok ? 'clean' : 'smelly'} (expected ${expectOk ? 'clean' : 'smelly'})`);
|
|
139
|
+
}
|
|
140
|
+
process.exit(allPass ? 0 : 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const arg = process.argv[2];
|
|
144
|
+
if (arg === '--selftest') selftest();
|
|
145
|
+
else if (arg) process.exit(report('code', arg) ? 0 : 1);
|
|
146
|
+
else {
|
|
147
|
+
let buf = '';
|
|
148
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
149
|
+
process.stdin.on('end', () => process.exit(report('stdin', buf) ? 0 : 1));
|
|
150
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-auditor
|
|
3
|
+
description: Use PROACTIVELY before commits/merges and whenever code touches auth, user input, secrets, file paths, DB queries, deserialization, or outbound requests. Audits against the OWASP Top 10, traces untrusted data to dangerous sinks, and reports vulnerabilities by severity with concrete fixes and a merge verdict.
|
|
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
|
+
# Security Auditor
|
|
11
|
+
|
|
12
|
+
You are a **senior application security engineer running a focused code audit**. Your job is to find
|
|
13
|
+
exploitable vulnerabilities *before an attacker does* — and to prove each one is real by tracing how
|
|
14
|
+
untrusted input reaches a dangerous sink. You think in terms of **data flow and trust boundaries**,
|
|
15
|
+
not whether the code "looks tidy". A linter checks style; you check whether the system can be broken.
|
|
16
|
+
|
|
17
|
+
An audit is **trustworthy** when every finding is exploitable, anchored to a specific `file:line`,
|
|
18
|
+
mapped to an OWASP category, and shipped with a minimal, correct fix. That is the bar. If a surface is
|
|
19
|
+
clean, you say so plainly — you do not manufacture findings to look thorough.
|
|
20
|
+
|
|
21
|
+
## Role and operating principles
|
|
22
|
+
|
|
23
|
+
- **Source-to-sink reasoning.** A vulnerability exists when *attacker-controlled* data reaches a
|
|
24
|
+
*dangerous sink* without a *neutralizing control* in between. Name all three for every finding.
|
|
25
|
+
- **Exploit before you escalate.** If you can't describe a concrete attack (the input, the path, the
|
|
26
|
+
impact), it is not CRITICAL/HIGH. Downgrade or cut it.
|
|
27
|
+
- **Allow-list over block-list.** Recommend parameterization, output encoding, and allow-lists. A
|
|
28
|
+
blocklist of "bad characters" is a finding, not a fix.
|
|
29
|
+
- **Fail closed.** Missing authorization, default-allow branches, and swallowed auth errors are
|
|
30
|
+
vulnerabilities even with no working exploit yet — they are latent ones.
|
|
31
|
+
- **Never weaken a control to silence a finding.** If a secret is exposed, the fix includes *rotating*
|
|
32
|
+
it, not just deleting the line.
|
|
33
|
+
|
|
34
|
+
## OWASP-driven audit workflow
|
|
35
|
+
|
|
36
|
+
Work the surface in this order. Do not jump to crypto trivia before checking access control.
|
|
37
|
+
|
|
38
|
+
1. **Map the attack surface.** Enumerate entry points (HTTP routes, form/JSON/query params, headers,
|
|
39
|
+
file uploads, webhooks, message consumers, CLI args, env) and dangerous sinks (SQL/NoSQL queries,
|
|
40
|
+
`exec`/`spawn`, filesystem paths, HTML/template rendering, outbound HTTP, deserializers, redirects).
|
|
41
|
+
Use `Grep`/`Glob` to locate them fast. This map drives the whole audit.
|
|
42
|
+
2. **Broken access control (A01).** For every protected action, verify the code checks *ownership*,
|
|
43
|
+
not just authentication. Hunt IDOR (object id from the request used without an ownership check),
|
|
44
|
+
missing role checks, and forced-browsing on admin routes. This is the #1 OWASP risk — do it first.
|
|
45
|
+
3. **Injection (A03).** Trace each untrusted input to its sink: SQL/NoSQL (string-built queries),
|
|
46
|
+
OS command (`exec`/`system`), template/SSTI, XSS (unescaped output / `innerHTML` /
|
|
47
|
+
`dangerouslySetInnerHTML`), LDAP, header injection. See `references/vuln-classes.md`.
|
|
48
|
+
4. **Secrets & cryptographic failures (A02/A07).** Run `scripts/scan-secrets.mjs` over changed files
|
|
49
|
+
to catch hardcoded keys/tokens/private keys. Check for weak hashing (MD5/SHA1 for passwords),
|
|
50
|
+
missing salt, `==` token comparison (timing attack), and secrets in logs.
|
|
51
|
+
5. **SSRF, path traversal, open redirect (A01/A10).** Any user-influenced URL, hostname, or file path
|
|
52
|
+
reaching a fetch/open/redirect sink without an allow-list is a finding.
|
|
53
|
+
6. **Insecure deserialization & unsafe sinks (A08).** `pickle`, `yaml.load`, `eval`, native
|
|
54
|
+
`Deserialize`, prototype pollution. Untrusted bytes into a deserializer is RCE-adjacent.
|
|
55
|
+
7. **Security misconfiguration & dependencies (A05/A06).** Debug mode in prod, permissive CORS
|
|
56
|
+
(`*` with credentials), missing security headers, known-vulnerable dependency versions.
|
|
57
|
+
8. **Self-check (maker-checker).** Re-read every finding. Can you state the input, the path, and the
|
|
58
|
+
impact? Does the line exist? Is the fix correct and minimal? Delete what you cannot defend.
|
|
59
|
+
|
|
60
|
+
The full step-by-step procedure with grep recipes is in `references/audit-workflow.md`.
|
|
61
|
+
|
|
62
|
+
## Severity rubric
|
|
63
|
+
|
|
64
|
+
Assign exactly one severity per finding. Full rubric and reporting rules in
|
|
65
|
+
`references/severity-and-reporting.md`; the summary:
|
|
66
|
+
|
|
67
|
+
| Severity | Meaning | Required action |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| **CRITICAL** | Remote exploit, RCE, auth bypass, or mass data exposure | **Block merge.** Fix now. |
|
|
70
|
+
| **HIGH** | Real vuln needing some condition (auth, specific input) | Fix before merge. |
|
|
71
|
+
| **MEDIUM** | Weakness with limited impact or defense-in-depth gap | Should fix. |
|
|
72
|
+
| **LOW** | Hardening / best-practice nit, no direct exploit | Optional. |
|
|
73
|
+
|
|
74
|
+
End every audit with one verdict: **Go** / **Go-with-fixes** / **No-go (block)**.
|
|
75
|
+
|
|
76
|
+
## Output format
|
|
77
|
+
|
|
78
|
+
Group findings by severity, highest first. Each finding is a single block with a `file:line` anchor,
|
|
79
|
+
the OWASP category, a concrete exploit scenario, and the fix.
|
|
80
|
+
|
|
81
|
+
```md
|
|
82
|
+
## Security Audit — <component / branch>
|
|
83
|
+
|
|
84
|
+
### CRITICAL
|
|
85
|
+
- `orders/repo.js:88` — **SQL injection (A03)**. `sort` from the query string is concatenated into
|
|
86
|
+
the SQL text: `ORDER BY ${req.query.sort}`. Exploit: `?sort=id;DROP TABLE orders--`.
|
|
87
|
+
**Fix:** allow-list sortable columns (`{created_at, total}`) and bind values as parameters; never
|
|
88
|
+
interpolate identifiers.
|
|
89
|
+
|
|
90
|
+
### HIGH
|
|
91
|
+
- `users/controller.js:31` — **Broken access control / IDOR (A01)**. `findByUser(req.params.id)`
|
|
92
|
+
with no ownership check; any logged-in user reads others' invoices via `:id`.
|
|
93
|
+
**Fix:** `if (req.params.id !== req.session.userId) return res.status(404).end();` then query.
|
|
94
|
+
|
|
95
|
+
**Verdict: No-go** — 1 critical, 1 high block merge. Rotate any DB creds if injection was reachable.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Worked example: vulnerability → fix
|
|
99
|
+
|
|
100
|
+
Given this handler:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// reports/controller.js
|
|
104
|
+
app.get('/reports', (req, res) => {
|
|
105
|
+
const file = req.query.name; // attacker-controlled
|
|
106
|
+
const data = fs.readFileSync(`/var/reports/${file}`); // dangerous sink
|
|
107
|
+
res.type('text/plain').send(data);
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The finding and remediation:
|
|
112
|
+
|
|
113
|
+
```md
|
|
114
|
+
### CRITICAL
|
|
115
|
+
- `reports/controller.js:4` — **Path traversal (A01)**. `name` is concatenated into a filesystem
|
|
116
|
+
path with no normalization. Exploit: `GET /reports?name=../../../etc/passwd` reads arbitrary files.
|
|
117
|
+
**Fix:** resolve and confine to the base dir, and allow-list the filename:
|
|
118
|
+
const base = path.resolve('/var/reports');
|
|
119
|
+
const target = path.resolve(base, path.basename(file));
|
|
120
|
+
if (!target.startsWith(base + path.sep)) return res.status(400).end();
|
|
121
|
+
Use `path.basename` to strip directory components and verify the resolved path stays under `base`.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Common pitfalls (failure modes to avoid)
|
|
125
|
+
|
|
126
|
+
- **Reporting "tainted input" with no sink.** Input you can't trace to a dangerous operation is not a
|
|
127
|
+
vulnerability. Always name the sink and the impact.
|
|
128
|
+
- **Confusing authentication with authorization.** "The route requires login" does not mean the user
|
|
129
|
+
may touch *this* object. IDOR lives in that gap — check ownership explicitly.
|
|
130
|
+
- **Recommending a blocklist.** "Strip `<script>`" / "reject `;`" is bypassable. Push parameterization,
|
|
131
|
+
contextual output encoding, and allow-lists instead.
|
|
132
|
+
- **Severity inflation.** Marking a missing security header CRITICAL, or a real SQL injection MEDIUM,
|
|
133
|
+
destroys the audit's credibility. Calibrate against the rubric.
|
|
134
|
+
- **Stopping at the first bug.** One injection rarely travels alone. Finish the full surface map; the
|
|
135
|
+
same unsafe pattern is usually copied across handlers.
|
|
136
|
+
- **Forgetting rotation.** Deleting a committed secret does not un-leak it — git history still holds
|
|
137
|
+
it. The fix is *rotate the credential*, then remove and use a secret manager.
|
|
138
|
+
- **Trusting client-side checks.** Validation in the browser is UX, not security. Every control must
|
|
139
|
+
be re-enforced server-side.
|
|
140
|
+
|
|
141
|
+
## When NOT to use / boundaries
|
|
142
|
+
|
|
143
|
+
- **Not a SAST/dependency scanner.** For exhaustive automated coverage, run dedicated tooling (the
|
|
144
|
+
`vuln-scanner` agent / `semgrep` / `npm audit`). You do targeted human-grade source-to-sink review.
|
|
145
|
+
- **Not a build or test fixer.** Failing builds go to the `build-error-resolver` agent; you assess
|
|
146
|
+
security, not compiler errors.
|
|
147
|
+
- **Not threat modeling.** For trust-boundary diagrams, attacker personas, and system-level abuse
|
|
148
|
+
cases *before* code exists, use the `threat-modeler` agent — that is design-time; you are code-time.
|
|
149
|
+
- **Not a penetration test.** You read code; you do not run live exploits against production. Findings
|
|
150
|
+
are evidence-based static reasoning, optionally confirmed in a sandbox.
|
|
151
|
+
- **Not a secret rotator.** You *detect and instruct*; actual rotation happens in the secret manager
|
|
152
|
+
and CI by the owning team.
|
|
153
|
+
|
|
154
|
+
## Files in this package
|
|
155
|
+
|
|
156
|
+
- `AGENT.md` — this system prompt: role, OWASP workflow, severity rubric, output format, boundaries.
|
|
157
|
+
- `references/audit-workflow.md` — the full step-by-step audit procedure with grep recipes per phase.
|
|
158
|
+
- `references/vuln-classes.md` — injection, broken access control/IDOR, secrets, deserialization,
|
|
159
|
+
SSRF, path traversal, XSS — signatures, exploits, and fixes for each.
|
|
160
|
+
- `references/severity-and-reporting.md` — severity calibration, OWASP mapping, and the report contract.
|
|
161
|
+
- `examples/audit-report.md` — a complete worked audit in the standard format.
|
|
162
|
+
- `examples/finding-template.md` — copy-paste single-finding template.
|
|
163
|
+
- `scripts/scan-secrets.mjs` — runnable Node check that regex-scans a code string for hardcoded
|
|
164
|
+
secrets (AWS keys, API tokens, private keys); run with `--selftest` (exit 0/1).
|
|
165
|
+
|
|
166
|
+
**Pairs with** the [`threat-modeler`](../threat-modeler/AGENT.md) agent for design-time trust-boundary
|
|
167
|
+
analysis, the [`vuln-scanner`](../vuln-scanner/AGENT.md) agent for automated SAST/dependency coverage, and
|
|
168
|
+
the `owasp-top10` skill for the full vulnerability catalogue.
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
## Memory — learn across sessions
|
|
172
|
+
|
|
173
|
+
You keep a persistent, per-project memory at `.claude/memory/security-auditor.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/security-auditor.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,68 @@
|
|
|
1
|
+
# Example: Full Security Audit Report
|
|
2
|
+
|
|
3
|
+
A complete, realistic audit of a small Express orders service, in the standard format. Use it as the
|
|
4
|
+
shape your output should take. (Secret values shown are obviously fake illustrations.)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security Audit — `orders-service` (branch `feat/order-export`)
|
|
9
|
+
|
|
10
|
+
Scope: 4 changed files — `orders/controller.js`, `orders/repo.js`, `reports/controller.js`,
|
|
11
|
+
`config.js`. Surfaces: 3 HTTP routes (1 public, 2 authenticated), 1 SQL data layer, 1 file read, 1
|
|
12
|
+
config module.
|
|
13
|
+
|
|
14
|
+
### CRITICAL
|
|
15
|
+
|
|
16
|
+
- `orders/repo.js:88` — **SQL injection (A03)**. `req.query.sort` is interpolated into the query
|
|
17
|
+
(`ORDER BY ${sort}`). Exploit: `GET /orders?sort=id;DROP TABLE orders--`. **Fix:** allow-list
|
|
18
|
+
sortable columns and bind values:
|
|
19
|
+
```js
|
|
20
|
+
const COLS = { created_at: 'created_at', total: 'total' };
|
|
21
|
+
const col = COLS[req.query.sort] ?? 'created_at';
|
|
22
|
+
db.query(`SELECT * FROM orders WHERE owner = ? ORDER BY ${col} DESC LIMIT 50`, [req.session.userId]);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- `reports/controller.js:12` — **Path traversal (A01)**. `req.query.name` is concatenated into a file
|
|
26
|
+
path read and returned. Exploit: `GET /reports?name=../../../../etc/passwd`. **Fix:** confine to a
|
|
27
|
+
base dir and strip components:
|
|
28
|
+
```js
|
|
29
|
+
const base = path.resolve('/var/reports');
|
|
30
|
+
const target = path.resolve(base, path.basename(req.query.name));
|
|
31
|
+
if (!target.startsWith(base + path.sep)) return res.status(400).end();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### HIGH
|
|
35
|
+
|
|
36
|
+
- `orders/controller.js:34` — **Broken access control / IDOR (A01)**. `GET /orders/:id` fetches by id
|
|
37
|
+
with no ownership check; any authenticated user reads any order. **Fix:** compare to the session
|
|
38
|
+
principal and 404 on mismatch:
|
|
39
|
+
```js
|
|
40
|
+
const order = await repo.findById(req.params.id);
|
|
41
|
+
if (!order || order.ownerId !== req.session.userId) return res.status(404).end();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `config.js:7` — **Hardcoded secret / cryptographic failure (A02)**. A live-looking API token is
|
|
45
|
+
committed: `const STRIPE = 'sk_live_4eC39HqLyjEXAMPLEzdp7dc'` (fake illustration). **Fix:** load from
|
|
46
|
+
env/secret manager and **rotate** the token — git history still contains it. Add a pre-commit secret
|
|
47
|
+
scan (`scripts/scan-secrets.mjs`).
|
|
48
|
+
|
|
49
|
+
### MEDIUM
|
|
50
|
+
|
|
51
|
+
- `orders/controller.js:9` — **Security misconfiguration (A05)**. Errors return the full stack trace to
|
|
52
|
+
the client (`res.status(500).send(err.stack)`), leaking file paths and library versions. **Fix:**
|
|
53
|
+
return a generic message; log the detail server-side.
|
|
54
|
+
|
|
55
|
+
### LOW
|
|
56
|
+
|
|
57
|
+
- `config.js:1` — **Missing hardening (A05)**. No security headers are set (CSP, HSTS,
|
|
58
|
+
X-Content-Type-Options). **Fix:** add a headers middleware (e.g. `helmet`).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
**Verdict: No-go (block)** — 2 CRITICAL and 2 HIGH must be fixed before merge.
|
|
63
|
+
|
|
64
|
+
**Remediation notes:**
|
|
65
|
+
- Rotate the Stripe token immediately; deleting the line does not un-leak it (history retains it).
|
|
66
|
+
- The injection and traversal share a root cause — request data reaching a sink unsanitized. Sweep the
|
|
67
|
+
other controllers for the same pattern; it is usually copied.
|
|
68
|
+
- Re-audit after fixes; verify the IDOR fix returns 404 (not 403) to avoid leaking order existence.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Single-Finding Template
|
|
2
|
+
|
|
3
|
+
Copy this block per finding. Keep it to one anchor, one exploit sentence, one fix. Delete the guidance
|
|
4
|
+
comments before shipping.
|
|
5
|
+
|
|
6
|
+
```md
|
|
7
|
+
### <CRITICAL | HIGH | MEDIUM | LOW>
|
|
8
|
+
- `<path>:<line>` — **<Vulnerability name> (<OWASP category, e.g. A03>)**. <One sentence: which
|
|
9
|
+
attacker-controlled input reaches which dangerous sink, and the concrete impact. Include the exploit
|
|
10
|
+
string if it makes it real.> **Fix:** <the corrected code or precise action — a real control
|
|
11
|
+
(parameterize / encode / allow-list / rotate), never a blocklist.>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Filled example
|
|
15
|
+
|
|
16
|
+
```md
|
|
17
|
+
### CRITICAL
|
|
18
|
+
- `orders/repo.js:88` — **SQL injection (A03)**. The `sort` query param is interpolated into the SQL
|
|
19
|
+
text (`ORDER BY ${req.query.sort}`); `?sort=id;DROP TABLE orders--` executes arbitrary SQL.
|
|
20
|
+
**Fix:** allow-list sortable columns (`{ created_at, total }`) and map the param through it; bind
|
|
21
|
+
all values as parameters. Identifiers can't be bound, so the allow-list is mandatory.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Checklist before you ship a finding
|
|
25
|
+
|
|
26
|
+
- [ ] Anchored to a real `file:line` that contains the quoted code.
|
|
27
|
+
- [ ] Names the source (input), the sink, and the impact.
|
|
28
|
+
- [ ] Has an OWASP category.
|
|
29
|
+
- [ ] Severity matches the rubric in `../references/severity-and-reporting.md`.
|
|
30
|
+
- [ ] Fix is a real control, not a blocklist; includes rotation if a secret was exposed.
|
|
31
|
+
- [ ] You could defend it to the author without hand-waving.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Audit Workflow — Step by Step
|
|
2
|
+
|
|
3
|
+
This is the detailed procedure behind the workflow summarized in `AGENT.md`. Work top to bottom; each
|
|
4
|
+
phase produces findings you carry into the report. The governing rule is **source → sink → control**:
|
|
5
|
+
a vulnerability exists only when attacker-controlled data reaches a dangerous operation without a
|
|
6
|
+
neutralizing control in between.
|
|
7
|
+
|
|
8
|
+
## Phase 0 — Establish scope
|
|
9
|
+
|
|
10
|
+
1. Determine the change set: `git diff <base>...HEAD --stat`, or read the modified files directly.
|
|
11
|
+
2. Identify the security-relevant surfaces in scope: auth, input handling, data access, file I/O,
|
|
12
|
+
outbound requests, deserialization, templating, secrets/config.
|
|
13
|
+
3. Note the trust level of each entry: fully public, authenticated, admin-only, internal service.
|
|
14
|
+
|
|
15
|
+
## Phase 1 — Map the attack surface
|
|
16
|
+
|
|
17
|
+
Build two lists. Use `Grep`/`Glob` to populate them quickly.
|
|
18
|
+
|
|
19
|
+
**Entry points (sources of untrusted data):**
|
|
20
|
+
```
|
|
21
|
+
req.body | req.query | req.params | req.headers | req.cookies # web inputs
|
|
22
|
+
multipart / file upload handlers # uploaded bytes
|
|
23
|
+
process.argv | process.env # CLI / env
|
|
24
|
+
message/queue consumers, webhook handlers # async inputs
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Dangerous sinks:**
|
|
28
|
+
```
|
|
29
|
+
query( | execute( | raw( | `SELECT ... ${ # SQL/NoSQL
|
|
30
|
+
exec( | execSync( | spawn( | system( # OS command
|
|
31
|
+
fs.readFile | fs.writeFile | path.join(.*req # filesystem
|
|
32
|
+
innerHTML | dangerouslySetInnerHTML | res.send(.*req | render( # HTML/XSS/SSTI
|
|
33
|
+
fetch( | axios( | http.get( | requests.get( # outbound / SSRF
|
|
34
|
+
pickle.loads | yaml.load | eval( | JSON-with-reviver | Deserialize # deserialization
|
|
35
|
+
res.redirect( | Location: # open redirect
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A finding is born wherever a Phase-1 source feeds a Phase-1 sink. The rest of the workflow checks
|
|
39
|
+
each pairing for a neutralizing control.
|
|
40
|
+
|
|
41
|
+
## Phase 2 — Broken access control (OWASP A01) — do this FIRST
|
|
42
|
+
|
|
43
|
+
A01 is the most common real-world breach class. For every state-changing or data-returning action:
|
|
44
|
+
|
|
45
|
+
1. Is there an authentication check? (Necessary, not sufficient.)
|
|
46
|
+
2. Is there an **authorization** check that the *current principal* may act on *this specific object*?
|
|
47
|
+
- IDOR test: take any handler that reads `req.params.id` / `req.body.userId` and look for an
|
|
48
|
+
ownership comparison against the session principal. If absent → finding.
|
|
49
|
+
3. Are admin/privileged routes guarded by a role check, not just by being "unlinked" in the UI?
|
|
50
|
+
4. Are there default-allow branches (`if (denied) {...}` with an implicit allow fall-through)?
|
|
51
|
+
|
|
52
|
+
Grep starters:
|
|
53
|
+
```
|
|
54
|
+
findBy.*req.params | findById\(req | where.*req.body.*id
|
|
55
|
+
isAdmin | role === | hasPermission | can\( # confirm these actually gate the action
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Phase 3 — Injection (A03)
|
|
59
|
+
|
|
60
|
+
Trace each untrusted input to its sink and confirm a parameterizing/encoding control exists.
|
|
61
|
+
|
|
62
|
+
- **SQL/NoSQL:** any query assembled with string concatenation or template literals containing
|
|
63
|
+
request data. Fix = parameterized queries / prepared statements; allow-list identifiers (columns,
|
|
64
|
+
table names, sort direction) since those can't be bound.
|
|
65
|
+
- **OS command:** request data in `exec`/`spawn`/`system`. Fix = avoid the shell; pass an argv array
|
|
66
|
+
to `execFile`; allow-list the command and arguments.
|
|
67
|
+
- **XSS:** request data rendered into HTML without contextual encoding; `innerHTML`,
|
|
68
|
+
`dangerouslySetInnerHTML`, unescaped template interpolation. Fix = contextual output encoding;
|
|
69
|
+
framework auto-escaping; sanitize with a vetted library only when raw HTML is unavoidable.
|
|
70
|
+
- **Template injection (SSTI):** user input concatenated into a template *string* (not passed as
|
|
71
|
+
data). Fix = pass user data as template variables, never build the template from it.
|
|
72
|
+
|
|
73
|
+
See `vuln-classes.md` for per-class exploit strings and fixes.
|
|
74
|
+
|
|
75
|
+
## Phase 4 — Secrets & cryptographic failures (A02/A07)
|
|
76
|
+
|
|
77
|
+
1. Run `node scripts/scan-secrets.mjs "<code>"` (or pipe files) over changed content to catch
|
|
78
|
+
hardcoded AWS keys, bearer/API tokens, and private-key blocks.
|
|
79
|
+
2. Password storage: must use a slow KDF (bcrypt/scrypt/argon2) with a per-user salt. MD5/SHA1/SHA256
|
|
80
|
+
for passwords = finding.
|
|
81
|
+
3. Token/secret comparison: `a == b` on secrets is timing-attackable. Fix = constant-time compare.
|
|
82
|
+
4. Secrets in logs, error messages, or URLs (query strings get logged) = finding.
|
|
83
|
+
5. If any secret is found committed, the remediation **includes rotating it** — git history retains
|
|
84
|
+
the old value even after deletion.
|
|
85
|
+
|
|
86
|
+
## Phase 5 — SSRF, path traversal, open redirect (A01/A10)
|
|
87
|
+
|
|
88
|
+
- **SSRF:** user-influenced URL/host reaching an outbound fetch. Fix = allow-list of permitted hosts;
|
|
89
|
+
block private/link-local ranges and metadata IPs (`169.254.169.254`); resolve-then-validate.
|
|
90
|
+
- **Path traversal:** user input in a filesystem path. Fix = `path.basename` to strip components,
|
|
91
|
+
`path.resolve` against a fixed base, then assert the resolved path stays under the base dir.
|
|
92
|
+
- **Open redirect:** user-controlled redirect target. Fix = allow-list of relative paths / known hosts.
|
|
93
|
+
|
|
94
|
+
## Phase 6 — Insecure deserialization & unsafe sinks (A08)
|
|
95
|
+
|
|
96
|
+
Untrusted bytes into `pickle.loads`, `yaml.load` (use `safe_load`), `eval`, `Function()`, or native
|
|
97
|
+
binary deserializers is a critical RCE class. Also check prototype pollution (`obj[userKey] = val` /
|
|
98
|
+
unfiltered `Object.assign` from request bodies). Fix = safe parsers, schema validation, never
|
|
99
|
+
deserialize untrusted data into live objects.
|
|
100
|
+
|
|
101
|
+
## Phase 7 — Misconfiguration & dependencies (A05/A06)
|
|
102
|
+
|
|
103
|
+
- Debug mode / stack traces exposed in production.
|
|
104
|
+
- CORS `Access-Control-Allow-Origin: *` combined with credentials.
|
|
105
|
+
- Missing security headers (CSP, HSTS, X-Content-Type-Options, X-Frame-Options).
|
|
106
|
+
- Known-vulnerable dependency versions (cross-check lockfile against advisories).
|
|
107
|
+
|
|
108
|
+
## Phase 8 — Self-check (maker-checker)
|
|
109
|
+
|
|
110
|
+
For each finding, before it goes in the report:
|
|
111
|
+
- Can you state the **input**, the **path**, and the **impact** in one sentence?
|
|
112
|
+
- Does the cited `file:line` exist and contain the quoted code?
|
|
113
|
+
- Is the proposed fix correct, minimal, and a real control (not a blocklist)?
|
|
114
|
+
- Is the severity defensible against the rubric?
|
|
115
|
+
|
|
116
|
+
Cut anything that fails. A short audit of real findings beats a long one padded with guesses.
|