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,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runnable linter: scans source code for SWALLOWED errors — the #1 error-handling
|
|
3
|
+
// defect. Flags empty catch blocks, bare `except: pass`, no-op promise .catch
|
|
4
|
+
// handlers, and ignored Go errors. Use it in CI to fail builds that hide failures.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node lint-empty-catch.mjs <file> [<file>...] # lint files, exit 1 if any findings
|
|
8
|
+
// echo '<code>' | node lint-empty-catch.mjs # lint code from stdin
|
|
9
|
+
// node lint-empty-catch.mjs --selftest # run built-in test cases
|
|
10
|
+
//
|
|
11
|
+
// Exit code: 0 = clean, 1 = swallowed errors found (or a selftest case failed).
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
// Each rule: a label and a regex matching a swallow anti-pattern.
|
|
16
|
+
// Patterns are intentionally conservative to limit false positives.
|
|
17
|
+
const RULES = [
|
|
18
|
+
// JS/TS/Java/C#: catch with an empty or whitespace/comment-only body.
|
|
19
|
+
['empty catch block', /catch\s*(\([^)]*\))?\s*\{\s*(\/\/[^\n]*\s*|\/\*[\s\S]*?\*\/\s*)*\}/g],
|
|
20
|
+
// JS/TS: a promise .catch that does nothing.
|
|
21
|
+
['no-op .catch handler', /\.catch\s*\(\s*\(?\s*[\w$]*\s*\)?\s*=>\s*\{\s*\}\s*\)/g],
|
|
22
|
+
// JS/TS: .catch(() => undefined) style swallow.
|
|
23
|
+
['no-op .catch (returns nothing)', /\.catch\s*\(\s*\(?\s*[\w$]*\s*\)?\s*=>\s*(undefined|null|void 0)\s*\)/g],
|
|
24
|
+
// Python: bare except (optionally typed) whose only statement is pass.
|
|
25
|
+
['python except: pass', /except\b[^\n:]*:\s*(\n\s*)?pass\b/g],
|
|
26
|
+
// Python: except whose only statement is `...` (ellipsis stub left in prod).
|
|
27
|
+
['python except: ...', /except\b[^\n:]*:\s*(\n\s*)?\.\.\.\s*/g],
|
|
28
|
+
// Go: an `if err != nil` block that is empty.
|
|
29
|
+
['go empty err check', /if\s+err\s*!=\s*nil\s*\{\s*\}/g],
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function lint(source) {
|
|
33
|
+
const findings = [];
|
|
34
|
+
for (const [label, re] of RULES) {
|
|
35
|
+
re.lastIndex = 0;
|
|
36
|
+
let m;
|
|
37
|
+
while ((m = re.exec(source)) !== null) {
|
|
38
|
+
const line = source.slice(0, m.index).split('\n').length;
|
|
39
|
+
findings.push({ label, line });
|
|
40
|
+
if (m.index === re.lastIndex) re.lastIndex++; // guard against zero-width loops
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
findings.sort((a, b) => a.line - b.line);
|
|
44
|
+
return findings;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function lintLabeled(label, source) {
|
|
48
|
+
const findings = lint(source);
|
|
49
|
+
if (findings.length === 0) {
|
|
50
|
+
console.log(`✓ ${label}: no swallowed errors`);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
console.error(`✗ ${label}: ${findings.length} swallowed error(s)`);
|
|
54
|
+
for (const f of findings) console.error(` line ${f.line}: ${f.label}`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function selftest() {
|
|
59
|
+
// name starts with "clean" => expect 0 findings; otherwise expect >=1 finding.
|
|
60
|
+
const cases = {
|
|
61
|
+
bad_empty_catch: 'try { risky(); } catch (e) {}',
|
|
62
|
+
bad_catch_comment_only: 'try { risky(); } catch (e) {\n // ignore\n}',
|
|
63
|
+
bad_noop_promise_catch: 'doThing().catch(() => {});',
|
|
64
|
+
bad_noop_catch_undefined: 'doThing().catch(e => undefined);',
|
|
65
|
+
bad_python_except_pass: 'try:\n risky()\nexcept Exception:\n pass',
|
|
66
|
+
bad_python_except_ellipsis: 'try:\n risky()\nexcept ValueError:\n ...',
|
|
67
|
+
bad_go_empty_err: 'v, err := load()\nif err != nil {}\nuse(v)',
|
|
68
|
+
clean_handled_catch: 'try { risky(); } catch (e) {\n logger.error(e);\n throw e;\n}',
|
|
69
|
+
clean_handled_promise: 'doThing().catch(e => logger.error(e));',
|
|
70
|
+
clean_python_handled: 'try:\n risky()\nexcept Exception as e:\n log.error(e)\n raise',
|
|
71
|
+
clean_go_handled: 'v, err := load()\nif err != nil {\n return err\n}\nuse(v)',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let allExpected = true;
|
|
75
|
+
for (const [name, code] of Object.entries(cases)) {
|
|
76
|
+
const found = lint(code).length;
|
|
77
|
+
const shouldBeClean = name.startsWith('clean');
|
|
78
|
+
const isClean = found === 0;
|
|
79
|
+
const correct = isClean === shouldBeClean;
|
|
80
|
+
allExpected &&= correct;
|
|
81
|
+
console.log(
|
|
82
|
+
`${correct ? '✓' : '✗'} selftest ${name}: ${found} finding(s) ` +
|
|
83
|
+
`(expected ${shouldBeClean ? 'clean' : 'swallow'})`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
console.log(allExpected ? '\nAll selftest cases passed.' : '\nSelftest FAILED.');
|
|
87
|
+
process.exit(allExpected ? 0 : 1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- CLI -------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
const args = process.argv.slice(2);
|
|
93
|
+
if (args[0] === '--selftest') {
|
|
94
|
+
selftest();
|
|
95
|
+
} else if (args.length > 0) {
|
|
96
|
+
let clean = true;
|
|
97
|
+
for (const file of args) {
|
|
98
|
+
let src;
|
|
99
|
+
try {
|
|
100
|
+
src = readFileSync(file, 'utf8');
|
|
101
|
+
} catch {
|
|
102
|
+
console.error(`✗ ${file}: cannot read file`);
|
|
103
|
+
clean = false;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
clean = lintLabeled(file, src) && clean;
|
|
107
|
+
}
|
|
108
|
+
process.exit(clean ? 0 : 1);
|
|
109
|
+
} else {
|
|
110
|
+
let buf = '';
|
|
111
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
112
|
+
process.stdin.on('end', () => process.exit(lintLabeled('stdin', buf) ? 0 : 1));
|
|
113
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-collaboration-workflows
|
|
3
|
+
description: Run git collaboration that scales — trunk-based vs git-flow decided by deploy cadence, branch protection and required checks, PR sizing and review etiquette, monorepo vs polyrepo trade-offs, release branches and hotfixes, and history hygiene with disciplined merges.
|
|
4
|
+
type: skill
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
updated: 2026-07-10
|
|
7
|
+
---
|
|
8
|
+
# Git Collaboration Workflows
|
|
9
|
+
|
|
10
|
+
Git workflow debates are proxy wars over one variable: **how often you ship**. Choose the
|
|
11
|
+
branching model from your deploy cadence, enforce it with branch protection instead of
|
|
12
|
+
vigilance, and keep PRs small enough to review honestly. Deep detail in `references/`;
|
|
13
|
+
protection settings and a PR-review playbook in `examples/`; `scripts/check-branch-hygiene.mjs`
|
|
14
|
+
audits a repo's local hygiene.
|
|
15
|
+
|
|
16
|
+
## Choosing the branching model
|
|
17
|
+
|
|
18
|
+
| You deploy… | Model | Shape |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Continuously (SaaS default) | **Trunk-based** | Short-lived branches (<2 days) → main; release = deploy main; flags hide unfinished work |
|
|
21
|
+
| On a cadence (mobile, desktop) | Trunk + **release branches** | Cut `release/1.24` from main; only cherry-picked fixes land on it; tag ships |
|
|
22
|
+
| Multiple supported majors (enterprise, on-prem) | Trunk + long-lived release lines | Fix on main first, cherry-pick back (never the reverse) |
|
|
23
|
+
|
|
24
|
+
Full git-flow (develop + feature + release + hotfix branches) earns its complexity only in
|
|
25
|
+
the third row — adopted by SaaS teams it mostly adds merge ceremony and delays integration.
|
|
26
|
+
The deciding question is never taste; it's "what do we support in production simultaneously?"
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
trunk-based: main ──●──●──●──●──► release = deploy latest green main
|
|
30
|
+
cadence: main ──●──●──●──► release/1.24 ──●(cp)──tag 1.24.1
|
|
31
|
+
hotfix: branch from the SHIPPED TAG, fix, tag — then forward-port to main SAME DAY
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Mechanics of each and the hotfix path: `references/branching-models.md`.
|
|
35
|
+
|
|
36
|
+
## Short-lived branches or bust
|
|
37
|
+
|
|
38
|
+
Integration pain grows superlinearly with branch age — two weeks of divergence is a merge
|
|
39
|
+
project. Working rules:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
branch age < 2 days (rebase on main daily if longer is unavoidable)
|
|
43
|
+
PR size ≤ ~400 changed lines — review quality collapses beyond that
|
|
44
|
+
one PR one logical change (refactor ≠ behavior change ≠ formatting)
|
|
45
|
+
big features land dark in slices behind a flag (feature-flags-experimentation),
|
|
46
|
+
never in a long-lived feature branch
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Stacked PRs (chain of small dependent PRs) are the escape hatch when a change is genuinely
|
|
50
|
+
large: reviewable slices, one logical narrative — etiquette and mechanics in
|
|
51
|
+
`examples/pr-review-playbook.md`.
|
|
52
|
+
|
|
53
|
+
## Protection is policy-as-config
|
|
54
|
+
|
|
55
|
+
Trust the settings, not the culture doc — `examples/branch-protection.md` has the full
|
|
56
|
+
recommended set. The load-bearing ones: required status checks (build + tests + lint, the
|
|
57
|
+
same gate for everyone including admins), required review (1 for most teams, 2 for
|
|
58
|
+
money/security paths via CODEOWNERS), **stale-review dismissal** (a re-push invalidates old
|
|
59
|
+
approvals), and linear history or squash-only merges so `main` reads as a changelog.
|
|
60
|
+
Conventional commit messages (`conventional-commits`) make that changelog machine-readable.
|
|
61
|
+
|
|
62
|
+
## Merge strategy: pick one, encode it
|
|
63
|
+
|
|
64
|
+
- **Squash-merge** (default recommendation): PR = one commit on main; messy WIP commits
|
|
65
|
+
vanish; revert = one commit. Cost: intra-PR history lost — fine when PRs are small (rule
|
|
66
|
+
above).
|
|
67
|
+
- **Rebase-merge**: preserves commit series linearly — only worth it if authors curate
|
|
68
|
+
commits (interactive rebase discipline); otherwise you're preserving noise.
|
|
69
|
+
- **Merge commits**: keeps true topology; `main` becomes unreadable at scale. Reserve for
|
|
70
|
+
release-branch merges where the merge point *is* the information.
|
|
71
|
+
- Never mix by whim — one strategy per repo, enforced in settings. And **never rewrite
|
|
72
|
+
pushed shared history**; `revert` forward instead (`references/history-hygiene.md` covers
|
|
73
|
+
recovery, bisect, and blame-friendly practices).
|
|
74
|
+
|
|
75
|
+
## Monorepo vs polyrepo (an ownership decision)
|
|
76
|
+
|
|
77
|
+
Monorepo buys atomic cross-cutting changes, one dependency graph, and universal refactors —
|
|
78
|
+
at the price of tooling (selective CI via path filters/affected-graphs, CODEOWNERS, merge
|
|
79
|
+
queues at scale). Polyrepo buys independent pace and clear boundaries — at the price of
|
|
80
|
+
version-matrix coordination (`release-coordination` in `deployment-strategies`) and
|
|
81
|
+
cross-repo changes becoming N-PR projects. Rule of thumb: one product surface + one deploy
|
|
82
|
+
train → monorepo; genuinely independent products/teams → polyrepo. The failure mode is the
|
|
83
|
+
*unmanaged* middle: 40 repos with hand-maintained version matrices and no contract tests.
|
|
84
|
+
|
|
85
|
+
## Pitfalls
|
|
86
|
+
|
|
87
|
+
- Long-lived feature branches "to keep main stable" — main stays stable via checks and
|
|
88
|
+
flags; the branch just batches risk into one giant merge.
|
|
89
|
+
- `main` broken and normalized ("just skip that test") — a red main blocks everyone's
|
|
90
|
+
integration; fixing it outranks all feature work, and a merge queue prevents most of it.
|
|
91
|
+
- Force-push to shared branches — reflog archaeology for the whole team; protect against it
|
|
92
|
+
in settings.
|
|
93
|
+
- Review theater: 2,000-line PRs approved in 4 minutes — the size rule exists because
|
|
94
|
+
reviewers are human.
|
|
95
|
+
- Cherry-picking main→release without fixing main first — the fix vanishes in the next
|
|
96
|
+
release cut.
|
|
97
|
+
|
|
98
|
+
## Verification
|
|
99
|
+
|
|
100
|
+
Run `node scripts/check-branch-hygiene.mjs` inside any repo: it reports stale branches,
|
|
101
|
+
oversized undelivered work, and non-linear history segments. Then diff your repo settings
|
|
102
|
+
against `examples/branch-protection.md`. Pairs with `conventional-commits`,
|
|
103
|
+
`ci-pipeline-design`, `code-review` practices in `deployment-strategies`, and
|
|
104
|
+
`feature-flags-experimentation`.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Branch Protection — recommended settings
|
|
2
|
+
|
|
3
|
+
GitHub terminology; GitLab/Bitbucket equivalents in parentheses. Apply to `main` and all
|
|
4
|
+
`release/*` patterns.
|
|
5
|
+
|
|
6
|
+
## Required (the load-bearing set)
|
|
7
|
+
|
|
8
|
+
| Setting | Value | Why |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| Require a pull request before merging | on | No direct pushes, no exceptions |
|
|
11
|
+
| Required approvals | 1 (2 via CODEOWNERS for `/payments`, `/auth`, `/infra`) | Match rigor to blast radius |
|
|
12
|
+
| Dismiss stale approvals on new commits | **on** | Re-push invalidates what was reviewed |
|
|
13
|
+
| Require review from Code Owners | on where CODEOWNERS exists | Routing, not gatekeeping |
|
|
14
|
+
| Require status checks to pass | build, test, lint, typecheck | The same bar for everyone |
|
|
15
|
+
| Require branches to be up to date / merge queue | merge queue when >10 merges/day | Kills "two green PRs, red main" |
|
|
16
|
+
| Require linear history (or squash-only) | on | main reads as changelog; bisect stays fast |
|
|
17
|
+
| Include administrators (no bypass) | **on** | The bypass you allow is the one used at the worst moment |
|
|
18
|
+
| Allow force pushes / deletions | **off** | Non-negotiable on shared branches |
|
|
19
|
+
|
|
20
|
+
## Recommended
|
|
21
|
+
|
|
22
|
+
- **Require signed commits** on release branches (supply-chain posture; pairs with
|
|
23
|
+
provenance in `OPEN-CORE`-style distribution).
|
|
24
|
+
- **Conversation resolution required** — threads don't vanish on merge.
|
|
25
|
+
- Auto-delete head branches after merge — stale-branch hygiene by default.
|
|
26
|
+
- `CODEOWNERS` kept small and real: teams, not individuals (vacation-proof); every entry
|
|
27
|
+
answers "who must know about changes here", not "who wrote it once".
|
|
28
|
+
|
|
29
|
+
## The escape hatch, designed
|
|
30
|
+
|
|
31
|
+
Emergencies happen; design the fast path so nobody disables protection:
|
|
32
|
+
|
|
33
|
+
- A `hotfix` label + on-call approver satisfies the review requirement (a second human, fast).
|
|
34
|
+
- Checks stay required — a hotfix that doesn't build isn't fast, it's twice.
|
|
35
|
+
- Break-glass audit: any settings change to protection pages an owner (settings drift is
|
|
36
|
+
how "temporary" bypasses become permanent).
|
|
37
|
+
|
|
38
|
+
## CODEOWNERS sketch
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
# Default: any senior reviewer team
|
|
42
|
+
* @org/reviewers
|
|
43
|
+
# Blast-radius paths: two approvals via team + specialist
|
|
44
|
+
/services/payments/ @org/payments @org/security
|
|
45
|
+
/auth/ @org/security
|
|
46
|
+
/infra/ @org/platform
|
|
47
|
+
/.github/ @org/platform # CI is code; protect the protector
|
|
48
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# PR & Review Playbook
|
|
2
|
+
|
|
3
|
+
## Author side
|
|
4
|
+
|
|
5
|
+
- **Size**: ≤ ~400 changed lines. Bigger? Split: mechanical prep PRs (rename, extract,
|
|
6
|
+
format — trivially approvable) first, the behavior change last and small.
|
|
7
|
+
- **Stacked PRs** for large features: each PR one reviewable slice, description links the
|
|
8
|
+
stack ("2/5, builds on #341"). Land bottom-up; rebase the stack on each merge (tooling
|
|
9
|
+
helps; discipline suffices).
|
|
10
|
+
- **Description contract**: what + why + how-verified (test evidence, screenshots for UI),
|
|
11
|
+
plus rollback note if non-trivial ("revert cleanly" or the flag to flip). A reviewer
|
|
12
|
+
should know the intent before reading the diff.
|
|
13
|
+
- **Self-review first**: your own pass catches the debug prints and stray files; respect
|
|
14
|
+
reviewers' attention as the scarce resource it is.
|
|
15
|
+
- Draft PRs early for direction checks ("is this approach sane?") — cheaper than polished
|
|
16
|
+
wrongness.
|
|
17
|
+
|
|
18
|
+
## Reviewer side
|
|
19
|
+
|
|
20
|
+
- **First response < 4 working hours; small PRs same-day.** Review latency sets integration
|
|
21
|
+
speed for the whole team — it IS the constraint, treat it like on-call.
|
|
22
|
+
- Review for: correctness, tests actually asserting behavior, security on tainted paths,
|
|
23
|
+
and "will the next reader understand this" — not personal style (the linter owns style;
|
|
24
|
+
what the linter doesn't own isn't blocking).
|
|
25
|
+
- **Prefix comments by weight**: `blocking:` (must fix), `q:` (genuine question),
|
|
26
|
+
`nit:` (author's call, never blocks). Unprefixed comments read as blocking — inflation
|
|
27
|
+
by default.
|
|
28
|
+
- Approve with nits rather than round-tripping for trivia; re-request only when something
|
|
29
|
+
`blocking:` changed.
|
|
30
|
+
- Big PR arrives anyway? First comment: "split suggestion", not 45 line-notes on code
|
|
31
|
+
that'll be rewritten.
|
|
32
|
+
|
|
33
|
+
## Team mechanics
|
|
34
|
+
|
|
35
|
+
- **Review SLO on the dashboard** (median time-to-first-review) — what's measured gets
|
|
36
|
+
staffed.
|
|
37
|
+
- Rotate a daily "review goalie" who owns unblocking the queue before their own work.
|
|
38
|
+
- Disagreements past two round-trips move to a 10-minute call; write the resolution back
|
|
39
|
+
into the PR (the thread is the record).
|
|
40
|
+
- Merge is the author's job once approved (or auto-merge/queue) — approved-but-unmerged PRs
|
|
41
|
+
rot against main.
|
|
42
|
+
|
|
43
|
+
## Anti-patterns, named
|
|
44
|
+
|
|
45
|
+
LGTM-stamping (approval without evidence of reading — ask one substantive question per
|
|
46
|
+
review as a personal rule) · drive-by rewrites in review comments (open your own follow-up
|
|
47
|
+
PR) · review as status hierarchy (junior reviews senior code; fresh eyes are the point) ·
|
|
48
|
+
the 47-comment style war (fix the linter config, apologize, move on).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Branching Models in Detail
|
|
2
|
+
|
|
3
|
+
## Trunk-based (continuous deploy)
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
main ──●──●──●──●──●──●──► every merge deployable; deploy = promote latest green main
|
|
7
|
+
\feat-a (hours)
|
|
8
|
+
●──● → PR → squash → main
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- Unfinished work ships **dark** behind flags — integration happens daily even when release
|
|
12
|
+
doesn't.
|
|
13
|
+
- A **merge queue** (GitHub merge queue / Bors-style) serializes merges and tests each
|
|
14
|
+
against the true post-merge state — eliminates "two green PRs, red main" races; adopt it
|
|
15
|
+
once merges/day > ~10.
|
|
16
|
+
- Broken main = full stop: auto-revert the offending merge (bot or on-call) rather than
|
|
17
|
+
fix-forward under pressure; the author re-lands calmly.
|
|
18
|
+
|
|
19
|
+
## Release-branch overlay (cadence shipping)
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
main ──●──●──●──●──●──●──►
|
|
23
|
+
\release/1.24 ──●(cp)──●(cp)──tag 1.24.0──tag 1.24.1
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- Cut from main at code-freeze; the branch only receives **cherry-picks** of fixes that
|
|
27
|
+
landed on main first (main-first is the invariant that prevents regressions in 1.25).
|
|
28
|
+
- Tag builds from the release branch; the tag, not the branch tip, is what shipped.
|
|
29
|
+
- Delete the branch when the version leaves support; tags are permanent.
|
|
30
|
+
|
|
31
|
+
## Hotfix path (works in both models)
|
|
32
|
+
|
|
33
|
+
1. Branch from the **shipped tag** (not main — main has moved).
|
|
34
|
+
2. Fix, PR, tag `1.24.2` from the hotfix branch, ship.
|
|
35
|
+
3. **Forward-port to main immediately** — the #1 hotfix failure is the fix missing from the
|
|
36
|
+
next regular release. A required "forward-ported? link" field in the hotfix PR template
|
|
37
|
+
is cheap insurance.
|
|
38
|
+
|
|
39
|
+
## Multi-major support (enterprise/on-prem)
|
|
40
|
+
|
|
41
|
+
Long-lived `release/2.x`, `release/3.x` lines. Every fix: main first, then cherry-pick down
|
|
42
|
+
each supported line it applies to (automate with a cherry-pick bot + per-line labels).
|
|
43
|
+
Support-matrix discipline: each line has an EOL date published; EOL means the branch goes
|
|
44
|
+
read-only. The cost of each supported line is roughly a part-time engineer — price support
|
|
45
|
+
contracts accordingly.
|
|
46
|
+
|
|
47
|
+
## Migration notes (git-flow → trunk-based)
|
|
48
|
+
|
|
49
|
+
Teams rarely regret the direction; the sequence that works: (1) merge queue + required
|
|
50
|
+
checks on main, (2) flags for the two in-flight epics, (3) shrink PR-size norms via review
|
|
51
|
+
culture, (4) delete `develop` — it's by then a stale mirror. Do (4) last; deleting develop
|
|
52
|
+
first just moves the long-lived-branch problem to feature branches.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# History Hygiene
|
|
2
|
+
|
|
3
|
+
History is a debugging tool and an audit trail; hygiene is what keeps `bisect`, `blame`, and
|
|
4
|
+
`revert` usable at 3 a.m.
|
|
5
|
+
|
|
6
|
+
## The rules
|
|
7
|
+
|
|
8
|
+
- **Never rewrite pushed shared history.** `push --force` on main/release erases teammates'
|
|
9
|
+
reference points and CI provenance. Personal branches: force-push freely *before* review
|
|
10
|
+
starts; after review begins, prefer `--force-with-lease` (refuses if someone else pushed)
|
|
11
|
+
and only to your own branch.
|
|
12
|
+
- **Revert forward.** A bad merge on main gets `git revert -m 1 <merge>` (or revert of the
|
|
13
|
+
squash commit) — a new commit that undoes it. History stays true: the mistake happened,
|
|
14
|
+
the fix happened.
|
|
15
|
+
- **Atomic commits with why-messages.** Each commit builds and tests green (keeps bisect
|
|
16
|
+
honest); message body explains *why*, the diff already shows *what*
|
|
17
|
+
(`conventional-commits` for the format).
|
|
18
|
+
- Separate **mechanical churn from logic**: formatting/rename-only commits isolated (and
|
|
19
|
+
listed in `.git-blame-ignore-revs` so blame skips them — one config line saves years of
|
|
20
|
+
"blame says the formatter wrote everything").
|
|
21
|
+
|
|
22
|
+
## Bisect discipline
|
|
23
|
+
|
|
24
|
+
`git bisect run ./test.sh` finds the breaking commit in log₂(n) steps — but only if commits
|
|
25
|
+
build independently (atomic rule above) and main is linear-ish (squash/rebase merges). A
|
|
26
|
+
history of "wip", "fix", "fix2" turns bisect from minutes into an afternoon of manual
|
|
27
|
+
skipping. This is the practical argument that wins the squash-merge debate.
|
|
28
|
+
|
|
29
|
+
## Recovering from disasters
|
|
30
|
+
|
|
31
|
+
- Deleted branch / botched rebase: `git reflog` — every HEAD position for ~90 days; branch
|
|
32
|
+
from the lost SHA. Nothing pushed is ever really lost within the window.
|
|
33
|
+
- Committed secret: rotate the secret FIRST (history cleaning is not containment — clones
|
|
34
|
+
and CI logs exist), then scrub with `git filter-repo` and force-push with the whole
|
|
35
|
+
team coordinated. Treat as an incident (`secrets-management`), not a git trick.
|
|
36
|
+
- Wrong-branch commit: `git cherry-pick` it where it belongs, `git reset --keep` where it
|
|
37
|
+
doesn't (before push) or revert (after).
|
|
38
|
+
|
|
39
|
+
## Large repos / large files
|
|
40
|
+
|
|
41
|
+
Binary assets and datasets: LFS from day one — retrofitting LFS after 2GB of PSDs are in
|
|
42
|
+
history requires a full rewrite event. Generated artifacts never get committed (CI rebuilds
|
|
43
|
+
them); a `check-branch-hygiene` finding of build outputs in git is a config bug in
|
|
44
|
+
`.gitignore`, fix at the root.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Repo hygiene audit: stale branches, oversized in-flight work, non-linear
|
|
3
|
+
// history, and tracked build artifacts. Read-only. Run inside any git repo:
|
|
4
|
+
// node scripts/check-branch-hygiene.mjs [repo-path]
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const repo = process.argv[2] ?? process.cwd();
|
|
8
|
+
const git = (...args) => {
|
|
9
|
+
try { return execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim(); }
|
|
10
|
+
catch { return ''; }
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
if (!git('rev-parse', '--is-inside-work-tree')) {
|
|
14
|
+
console.error(`Not a git repo: ${repo}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const findings = [];
|
|
19
|
+
const DAY = 86_400_000;
|
|
20
|
+
const main = git('symbolic-ref', 'refs/remotes/origin/HEAD').split('/').pop() ||
|
|
21
|
+
(git('rev-parse', '--verify', 'main') ? 'main' : 'master');
|
|
22
|
+
|
|
23
|
+
// 1. Stale local branches (> 14 days since last commit, not merged into main)
|
|
24
|
+
const branches = git('for-each-ref', 'refs/heads', '--format=%(refname:short)|%(committerdate:iso8601)')
|
|
25
|
+
.split('\n').filter(Boolean).map((l) => { const [name, date] = l.split('|'); return { name, date: new Date(date) }; });
|
|
26
|
+
for (const b of branches) {
|
|
27
|
+
if (b.name === main) continue;
|
|
28
|
+
const ageDays = Math.floor((Date.now() - b.date) / DAY);
|
|
29
|
+
const merged = git('branch', '--merged', main).includes(b.name);
|
|
30
|
+
if (merged && b.name !== main) findings.push(`merged-but-undeleted branch: ${b.name} — delete it`);
|
|
31
|
+
else if (ageDays > 14) findings.push(`stale branch: ${b.name} (${ageDays}d old) — rebase & land in slices, or delete`);
|
|
32
|
+
else if (ageDays > 2) findings.push(`aging branch: ${b.name} (${ageDays}d) — branch-age rule is <2 days`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Oversized undelivered work on current branch vs main
|
|
36
|
+
const current = git('rev-parse', '--abbrev-ref', 'HEAD');
|
|
37
|
+
if (current && current !== main) {
|
|
38
|
+
const stat = git('diff', '--shortstat', `${main}...HEAD`);
|
|
39
|
+
const changed = [...stat.matchAll(/(\d+) (insertion|deletion)/g)].reduce((s, m) => s + Number(m[1]), 0);
|
|
40
|
+
if (changed > 400) findings.push(`current branch carries ${changed} changed lines vs ${main} — split (≤400/PR)`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Non-linear history in recent main (merge commits other than PR merges pattern)
|
|
44
|
+
const merges = git('log', main, '--merges', '--oneline', '-n', '200').split('\n').filter(Boolean);
|
|
45
|
+
if (merges.length > 50) findings.push(`${merges.length}/200 recent ${main} commits are merge commits — consider squash/linear policy`);
|
|
46
|
+
|
|
47
|
+
// 4. Tracked files that look like build artifacts or secrets-adjacent
|
|
48
|
+
const tracked = git('ls-files').split('\n');
|
|
49
|
+
const artifactRe = /^(dist|build|out|node_modules)\/|\.(log|tmp)$/;
|
|
50
|
+
const secretRe = /(^|\/)\.env(\.|$)|id_rsa|\.pem$/;
|
|
51
|
+
const artifacts = tracked.filter((f) => artifactRe.test(f)).slice(0, 5);
|
|
52
|
+
const secretish = tracked.filter((f) => secretRe.test(f) && !f.endsWith('.example')).slice(0, 5);
|
|
53
|
+
if (artifacts.length) findings.push(`build artifacts tracked: ${artifacts.join(', ')} — .gitignore + git rm --cached`);
|
|
54
|
+
if (secretish.length) findings.push(`SECRET-SHAPED tracked files: ${secretish.join(', ')} — rotate + scrub (see history-hygiene.md)`);
|
|
55
|
+
|
|
56
|
+
// Report
|
|
57
|
+
if (findings.length === 0) {
|
|
58
|
+
console.log(`Hygiene clean: ${branches.length} branches, ${main} linear-enough, no artifacts tracked.`);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`Hygiene findings (${findings.length}):\n` + findings.map((f) => ` - ${f}`).join('\n'));
|
|
61
|
+
}
|
|
62
|
+
process.exit(secretish.length ? 1 : 0); // only secret-shaped files fail the check hard
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: owasp-top10
|
|
3
|
+
description: A deep prevention reference for the OWASP Top 10 web risks — broken access control, injection, crypto failures, insecure design, SSRF and more — with vulnerable-vs-fixed code, edge cases, and a runnable naive-vulnerability scanner.
|
|
4
|
+
type: skill
|
|
5
|
+
version: 2.0.0
|
|
6
|
+
updated: 2026-06-28
|
|
7
|
+
---
|
|
8
|
+
# OWASP Top 10 Prevention
|
|
9
|
+
|
|
10
|
+
Most real-world breaches exploit a short, well-known list of weaknesses. This package is the deep
|
|
11
|
+
reference: each category gets its root cause, the default defense, and a vulnerable-vs-fixed example.
|
|
12
|
+
Category deep-dives live in `references/`, side-by-side fixes in `examples/`, and a runnable heuristic
|
|
13
|
+
scanner in `scripts/`.
|
|
14
|
+
|
|
15
|
+
> Based on the OWASP Top 10 (2021). The list shifts over time, but the underlying defenses are durable.
|
|
16
|
+
|
|
17
|
+
## The list (and the one-line defense for each)
|
|
18
|
+
|
|
19
|
+
| # | Category | Default defense |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| A01 | **Broken Access Control** | Enforce authorization server-side on every action; deny by default; check ownership (stop IDOR). |
|
|
22
|
+
| A02 | **Cryptographic Failures** | TLS in transit; encrypt sensitive data at rest; hash passwords with argon2/bcrypt; never roll your own crypto. |
|
|
23
|
+
| A03 | **Injection** (SQL/cmd/XSS) | Parameterize queries; context-aware output encoding; never concatenate untrusted input. |
|
|
24
|
+
| A04 | **Insecure Design** | Threat-model before building; secure-by-design defaults; abuse-case thinking. |
|
|
25
|
+
| A05 | **Security Misconfiguration** | Harden defaults; disable debug in prod; least-privilege; remove unused features. |
|
|
26
|
+
| A06 | **Vulnerable Components** | Inventory dependencies; patch on a schedule; scan for CVEs (see `vuln-scanner` agent). |
|
|
27
|
+
| A07 | **Auth Failures** | Strong session handling, MFA, rate-limit logins, no credential stuffing surface (see `secure-auth`). |
|
|
28
|
+
| A08 | **Software & Data Integrity** | Verify signatures; secure CI/CD; don't deserialize untrusted data. |
|
|
29
|
+
| A09 | **Logging & Monitoring Failures** | Log security events, alert on them, don't log secrets (see `audit-logging`). |
|
|
30
|
+
| A10 | **SSRF** | Allow-list outbound destinations; validate/resolve URLs; block internal ranges. |
|
|
31
|
+
|
|
32
|
+
## The two that cause the most damage
|
|
33
|
+
|
|
34
|
+
### A01 — Broken Access Control (the #1 risk)
|
|
35
|
+
The bug: the server checks *authentication* (who you are) but not *authorization* (whether you may do
|
|
36
|
+
this specific thing). Classic IDOR — changing an ID in the URL to read someone else's data.
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// VULNERABLE: any logged-in user can read any invoice by guessing an id
|
|
40
|
+
app.get('/invoices/:id', auth, async (req, res) => {
|
|
41
|
+
const invoice = await db.getInvoice(req.params.id);
|
|
42
|
+
res.json(invoice);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// FIXED: authorize against ownership, deny by default
|
|
46
|
+
app.get('/invoices/:id', auth, async (req, res) => {
|
|
47
|
+
const invoice = await db.getInvoice(req.params.id);
|
|
48
|
+
if (!invoice || invoice.ownerId !== req.user.id) return res.status(404).end(); // 404 hides existence
|
|
49
|
+
res.json(invoice);
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
Deep-dive: `references/access-control.md`.
|
|
53
|
+
|
|
54
|
+
### A03 — Injection
|
|
55
|
+
The bug: untrusted input is interpreted as code/query/markup. Defense is structural separation of code
|
|
56
|
+
from data.
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
// VULNERABLE: SQL injection
|
|
60
|
+
db.query(`SELECT * FROM users WHERE email = '${input}'`);
|
|
61
|
+
|
|
62
|
+
// FIXED: parameterized query — driver treats input strictly as data
|
|
63
|
+
db.query('SELECT * FROM users WHERE email = $1', [input]);
|
|
64
|
+
```
|
|
65
|
+
SQL, command, and XSS variants with fixes: `references/injection.md` and `examples/sql-injection-fix.md`,
|
|
66
|
+
`examples/xss-fix.md`.
|
|
67
|
+
|
|
68
|
+
## A worked SSRF case (A10)
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
// VULNERABLE: fetches any URL the user supplies -> attacker hits internal metadata service
|
|
72
|
+
const data = await fetch(req.query.url);
|
|
73
|
+
|
|
74
|
+
// FIXED: allow-list hosts and block internal ranges
|
|
75
|
+
const url = new URL(req.query.url);
|
|
76
|
+
if (!ALLOWED_HOSTS.has(url.hostname)) return res.status(400).json({ error: 'host not allowed' });
|
|
77
|
+
// ...plus resolve DNS and reject private IP ranges (169.254/16, 10/8, 127/8) to stop rebinding
|
|
78
|
+
```
|
|
79
|
+
Detail and the private-range checks: `references/ssrf-and-design.md`.
|
|
80
|
+
|
|
81
|
+
## Edge cases & gotchas
|
|
82
|
+
|
|
83
|
+
- **Blocklists fail; allow-lists work.** Trying to block "bad" input (e.g. stripping `<script>`) is
|
|
84
|
+
whack-a-mole — encode for the output context instead, and allow-list what's permitted.
|
|
85
|
+
- **404 vs 403 to avoid existence leakage.** For unauthorized access to a resource whose very existence
|
|
86
|
+
is sensitive, return `404`, not `403`, so attackers can't enumerate.
|
|
87
|
+
- **Mass assignment.** Binding request bodies straight to models lets attackers set fields like
|
|
88
|
+
`isAdmin`. Allow-list bindable fields.
|
|
89
|
+
- **Second-order injection.** Stored input that's safe on the way in can be unsafe when later used in a
|
|
90
|
+
different context (e.g. a stored value concatenated into a query). Defend at every sink.
|
|
91
|
+
- **Defense in depth.** No single control is enough — combine input validation, parameterization,
|
|
92
|
+
output encoding, authz checks, and monitoring.
|
|
93
|
+
|
|
94
|
+
## When the "fix" isn't enough
|
|
95
|
+
|
|
96
|
+
Input sanitization alone is not a substitute for parameterization/encoding — it's a fragile add-on.
|
|
97
|
+
And security review (this skill + the `security-auditor` agent) catches code-level bugs, but design-level
|
|
98
|
+
flaws (A04) need **threat modeling up front** (see the `threat-modeler` agent) — you can't audit your
|
|
99
|
+
way out of an insecure design.
|
|
100
|
+
|
|
101
|
+
## Files in this package
|
|
102
|
+
|
|
103
|
+
- `references/access-control.md` — A01 in depth: authz patterns, IDOR, mass assignment
|
|
104
|
+
- `references/injection.md` — A03: SQL/command/XSS with defenses per context
|
|
105
|
+
- `references/crypto-failures.md` — A02: hashing, encryption, key handling
|
|
106
|
+
- `references/ssrf-and-design.md` — A10 + A04: SSRF defense and secure design
|
|
107
|
+
- `examples/sql-injection-fix.md` — vulnerable vs parameterized, with edge cases
|
|
108
|
+
- `examples/xss-fix.md` — output encoding and safe rendering
|
|
109
|
+
- `scripts/scan-injection.mjs` — runnable heuristic scanner for naive injection patterns (selftest passes)
|
|
110
|
+
|
|
111
|
+
Pairs with the `security-auditor` agent, the `threat-modeler` agent, and the `secure-auth` and
|
|
112
|
+
`secrets-management` skills.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Example: SQL Injection — Vulnerable vs Fixed
|
|
2
|
+
|
|
3
|
+
## Vulnerable
|
|
4
|
+
```js
|
|
5
|
+
// User-controlled `email` concatenated directly into SQL
|
|
6
|
+
app.post('/login', (req, res) => {
|
|
7
|
+
const { email, password } = req.body;
|
|
8
|
+
const row = db.query(
|
|
9
|
+
`SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`
|
|
10
|
+
);
|
|
11
|
+
// input email = "' OR '1'='1' --" -> returns the first user, bypassing auth
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Fixed
|
|
16
|
+
```js
|
|
17
|
+
app.post('/login', async (req, res) => {
|
|
18
|
+
const { email, password } = req.body;
|
|
19
|
+
const row = await db.query(
|
|
20
|
+
'SELECT id, password_hash FROM users WHERE email = $1',
|
|
21
|
+
[email] // bound parameter — treated strictly as data
|
|
22
|
+
);
|
|
23
|
+
if (!row || !(await argon2.verify(row.password_hash, password))) {
|
|
24
|
+
return res.status(401).json({ error: { code: 'unauthorized', message: 'Invalid credentials' } });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What changed & edge cases
|
|
30
|
+
- **Parameterized query** — the driver sends the SQL and the data separately, so injected SQL can't execute.
|
|
31
|
+
- **No password in SQL** — verify a hash, never compare plaintext passwords in the query.
|
|
32
|
+
- **Generic error** — don't reveal whether the email or the password was wrong (avoids user enumeration).
|
|
33
|
+
- **Second-order:** even values you stored earlier must be parameterized when reused in a query.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Example: XSS — Vulnerable vs Fixed
|
|
2
|
+
|
|
3
|
+
## Vulnerable (DOM XSS)
|
|
4
|
+
```js
|
|
5
|
+
// Renders untrusted comment as HTML -> <img src=x onerror=stealCookies()> executes
|
|
6
|
+
commentEl.innerHTML = comment.text;
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Fixed
|
|
10
|
+
```js
|
|
11
|
+
// Assign as text — the browser never parses it as HTML
|
|
12
|
+
commentEl.textContent = comment.text;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Fixed (when you must render rich HTML)
|
|
16
|
+
```js
|
|
17
|
+
import DOMPurify from 'dompurify';
|
|
18
|
+
commentEl.innerHTML = DOMPurify.sanitize(comment.html); // vetted sanitizer, allow-list of tags
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Framework note & edge cases
|
|
22
|
+
- React/Vue auto-escape by default — the risk is the escape hatch (`dangerouslySetInnerHTML`, `v-html`).
|
|
23
|
+
- **Encode for the context:** HTML body vs attribute vs URL vs inline JS each need different encoding.
|
|
24
|
+
`textContent` handles the body case; a value placed into an `href` needs URL validation
|
|
25
|
+
(`javascript:` URLs are an XSS vector).
|
|
26
|
+
- **Don't regex-strip tags** — it's bypassable. Use a maintained sanitizer or encode for output.
|
|
27
|
+
- Pair with a **Content-Security-Policy** as defense-in-depth so injected scripts won't run.
|