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,94 @@
|
|
|
1
|
+
# Mocking, Fixtures, and Determinism
|
|
2
|
+
|
|
3
|
+
Tests must be **isolated** (independent of each other) and **deterministic** (same result
|
|
4
|
+
every run). That means controlling everything that crosses a boundary or varies between runs:
|
|
5
|
+
time, randomness, network, filesystem, and shared collaborators.
|
|
6
|
+
|
|
7
|
+
## Test doubles — know which one you need
|
|
8
|
+
|
|
9
|
+
| Double | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| **Dummy** | Filler passed to satisfy a signature; never used. |
|
|
12
|
+
| **Stub** | Returns canned answers to calls made during the test. |
|
|
13
|
+
| **Spy** | A stub that also records how it was called. |
|
|
14
|
+
| **Mock** | A double with pre-set expectations that verifies interactions. |
|
|
15
|
+
| **Fake** | A working but lightweight implementation (in-memory DB, fake clock). |
|
|
16
|
+
|
|
17
|
+
Reach for the **simplest** double that does the job. Prefer **fakes and stubs** over strict
|
|
18
|
+
mocks: asserting on exact call sequences couples the test to implementation and makes
|
|
19
|
+
refactoring break green tests.
|
|
20
|
+
|
|
21
|
+
## Mock at the boundary, only the boundary
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// GOOD: mock the HTTP client (a true external boundary)
|
|
25
|
+
const httpGet = vi.fn().mockResolvedValue({ status: 200, body: { rate: 1.1 } });
|
|
26
|
+
const rate = await fetchExchangeRate(httpGet, 'USD', 'EUR');
|
|
27
|
+
expect(rate).toBe(1.1);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// BAD: mocking the function under test leaves nothing real to verify
|
|
32
|
+
const fetchExchangeRate = vi.fn().mockResolvedValue(1.1); // tests the mock, not the code
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Over-mocking is a top cause of tests that pass while the system is broken. If a test is
|
|
36
|
+
almost entirely mock setup, you are testing the mock framework. Either widen the unit or move
|
|
37
|
+
the check to an integration test with a real collaborator.
|
|
38
|
+
|
|
39
|
+
## Controlling time
|
|
40
|
+
|
|
41
|
+
Wall-clock time is the most common flakiness source. Inject a clock or freeze it:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { vi } from 'vitest';
|
|
45
|
+
|
|
46
|
+
vi.useFakeTimers();
|
|
47
|
+
vi.setSystemTime(new Date('2026-06-29T12:00:00Z'));
|
|
48
|
+
|
|
49
|
+
expect(isExpired(token)).toBe(false);
|
|
50
|
+
|
|
51
|
+
vi.advanceTimersByTime(60_000); // jump 60s deterministically
|
|
52
|
+
expect(isExpired(token)).toBe(true);
|
|
53
|
+
|
|
54
|
+
vi.useRealTimers(); // always restore
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Never assert on `Date.now()` or sleep with real timeouts — that makes tests slow and racy.
|
|
58
|
+
|
|
59
|
+
## Controlling randomness
|
|
60
|
+
|
|
61
|
+
Seed the RNG or inject it. A test that depends on `Math.random()` passes-then-fails at random:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const rng = () => 0.42; // injected, deterministic
|
|
65
|
+
expect(pickWinner(players, rng)).toBe('alice');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Fixtures and factories
|
|
69
|
+
|
|
70
|
+
- **Factories over literals.** A `makeOrder(overrides)` helper keeps tests readable and
|
|
71
|
+
resilient — when a required field is added, you change the factory once, not 50 tests.
|
|
72
|
+
- **Build only what the test needs.** Override just the fields relevant to the behavior;
|
|
73
|
+
let the factory default the rest. This makes the *intent* of each test obvious.
|
|
74
|
+
- **Isolate state.** Reset shared resources (DB rows, in-memory stores, module mocks) in
|
|
75
|
+
`beforeEach`/`afterEach` so tests can run in any order. Order-dependent tests are a bug.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
function makeOrder(overrides = {}) {
|
|
79
|
+
return { id: 'ord_1', status: 'open', total: 100, createdAt: new Date(0), ...overrides };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
it('marks a paid order as fulfilled', () => {
|
|
83
|
+
const order = makeOrder({ status: 'paid' }); // only the relevant field is set
|
|
84
|
+
expect(fulfill(order).status).toBe('fulfilled');
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Determinism checklist
|
|
89
|
+
|
|
90
|
+
- [ ] No real network, filesystem, or DB in unit tests (use fakes/stubs).
|
|
91
|
+
- [ ] Time and randomness are frozen or injected.
|
|
92
|
+
- [ ] No shared mutable state leaks between tests; setup/teardown resets it.
|
|
93
|
+
- [ ] All async work is awaited — no dangling promises or unhandled timers.
|
|
94
|
+
- [ ] The suite passes when run in a randomized order.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Test Types and the Test Pyramid
|
|
2
|
+
|
|
3
|
+
Not all tests are equal. Choosing the right *level* for a given behavior is the difference
|
|
4
|
+
between a suite that gives fast, reliable signal and one that is slow, flaky, and ignored.
|
|
5
|
+
|
|
6
|
+
## The pyramid
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
/\ E2E / UI few, slow, high-confidence, brittle
|
|
10
|
+
/ \
|
|
11
|
+
/----\ Integration some, medium speed, real collaborators
|
|
12
|
+
/ \
|
|
13
|
+
/--------\ Unit many, fast, isolated, cheap
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Push the bulk of your assertions **down** to the unit level where they are fast and stable.
|
|
17
|
+
Use the higher levels sparingly to prove the pieces wire together, not to re-test logic that
|
|
18
|
+
unit tests already cover. An inverted pyramid (mostly E2E) is slow and flaky.
|
|
19
|
+
|
|
20
|
+
## Unit tests
|
|
21
|
+
|
|
22
|
+
- **Scope:** one function, method, or class in isolation. Collaborators that cross a process
|
|
23
|
+
or I/O boundary are replaced with doubles.
|
|
24
|
+
- **Cost:** milliseconds. Run thousands on every save.
|
|
25
|
+
- **Owns:** business logic, pure functions, edge cases, error branches, algorithms.
|
|
26
|
+
- **Rule of thumb:** if a behavior can be tested at the unit level, test it there.
|
|
27
|
+
|
|
28
|
+
## Integration tests
|
|
29
|
+
|
|
30
|
+
- **Scope:** several real units working together, or one unit against a real adjacent
|
|
31
|
+
dependency (a database, an in-memory queue, a local HTTP server).
|
|
32
|
+
- **Cost:** tens to hundreds of milliseconds. Often need setup/teardown.
|
|
33
|
+
- **Owns:** the seams — ORM queries against a real schema, repository wiring, serialization,
|
|
34
|
+
framework request/response handling, transaction behavior.
|
|
35
|
+
- **Avoid:** re-testing branch logic here that a unit test already covers; keep these about
|
|
36
|
+
the *wiring*, not the *rules*.
|
|
37
|
+
|
|
38
|
+
## End-to-end (E2E) tests
|
|
39
|
+
|
|
40
|
+
- **Scope:** the whole system through its real entry point — a browser, a CLI, a public API.
|
|
41
|
+
- **Cost:** seconds. Slow, infrastructure-heavy, the most prone to flakiness.
|
|
42
|
+
- **Owns:** a handful of critical user journeys (sign up, checkout, the money path).
|
|
43
|
+
- **Hand off:** browser-driven E2E belongs to the **e2e-playwright** skill, not this agent.
|
|
44
|
+
|
|
45
|
+
## What goes where — a decision guide
|
|
46
|
+
|
|
47
|
+
| Question | Level |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Does `applyDiscount(49.99, 10)` round correctly? | Unit |
|
|
50
|
+
| Does an invalid percentage throw `RangeError`? | Unit |
|
|
51
|
+
| Does the repository persist and re-read an order from Postgres? | Integration |
|
|
52
|
+
| Does `POST /orders` return `201` with a `Location` header? | Integration |
|
|
53
|
+
| Can a user add to cart and complete checkout in the browser? | E2E |
|
|
54
|
+
|
|
55
|
+
## Coverage as a signal, not a goal
|
|
56
|
+
|
|
57
|
+
The 80% target is a floor that flags untested regions, not a trophy. 100% line coverage with
|
|
58
|
+
assertion-free tests proves nothing. Chase **branch and behavior** coverage — every error
|
|
59
|
+
path and boundary — over raw line percentage. Use `scripts/check-coverage.mjs` to gate the
|
|
60
|
+
floor in CI, and read the uncovered lines to find the cases you forgot.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Coverage gate: reads an Istanbul/c8/Jest-style coverage-summary.json and fails (exit 1)
|
|
3
|
+
// if total coverage falls below a threshold. Zero dependencies — Node built-ins only.
|
|
4
|
+
//
|
|
5
|
+
// A coverage-summary.json looks like:
|
|
6
|
+
// { "total": { "lines": { "pct": 92.3 }, "statements": { "pct": 90.1 },
|
|
7
|
+
// "functions": { "pct": 85.0 }, "branches": { "pct": 88.2 } }, ... }
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// node check-coverage.mjs coverage/coverage-summary.json # gate at 80% (default)
|
|
11
|
+
// node check-coverage.mjs coverage/coverage-summary.json --min=90 # custom threshold
|
|
12
|
+
// node check-coverage.mjs --min=75 < coverage-summary.json # read JSON from stdin
|
|
13
|
+
// node check-coverage.mjs --selftest # built-in test cases
|
|
14
|
+
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const DEFAULT_MIN = 80;
|
|
18
|
+
// Metrics we gate on; the lowest of these must meet the threshold.
|
|
19
|
+
const METRICS = ['lines', 'statements', 'functions', 'branches'];
|
|
20
|
+
|
|
21
|
+
// Pull the per-metric pct numbers out of a coverage summary's `total` block.
|
|
22
|
+
export function extractTotals(summary) {
|
|
23
|
+
if (!summary || typeof summary !== 'object' || !summary.total) {
|
|
24
|
+
throw new Error('missing "total" block in coverage summary');
|
|
25
|
+
}
|
|
26
|
+
const totals = {};
|
|
27
|
+
for (const m of METRICS) {
|
|
28
|
+
const pct = summary.total?.[m]?.pct;
|
|
29
|
+
if (typeof pct === 'number' && Number.isFinite(pct)) totals[m] = pct;
|
|
30
|
+
}
|
|
31
|
+
if (Object.keys(totals).length === 0) {
|
|
32
|
+
throw new Error('no numeric coverage metrics found in "total"');
|
|
33
|
+
}
|
|
34
|
+
return totals;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Returns { ok, min, worst, totals, failures } — pure, so it is unit-testable.
|
|
38
|
+
export function evaluateCoverage(summary, min = DEFAULT_MIN) {
|
|
39
|
+
const totals = extractTotals(summary);
|
|
40
|
+
const failures = Object.entries(totals)
|
|
41
|
+
.filter(([, pct]) => pct < min)
|
|
42
|
+
.map(([metric, pct]) => ({ metric, pct }));
|
|
43
|
+
const worst = Math.min(...Object.values(totals));
|
|
44
|
+
return { ok: failures.length === 0, min, worst, totals, failures };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseMin(argv) {
|
|
48
|
+
const flag = argv.find((a) => a.startsWith('--min='));
|
|
49
|
+
if (!flag) return DEFAULT_MIN;
|
|
50
|
+
const n = Number(flag.slice('--min='.length));
|
|
51
|
+
if (!Number.isFinite(n) || n < 0 || n > 100) {
|
|
52
|
+
console.error(`✗ invalid --min value: "${flag}" (expected 0–100)`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function report(label, result) {
|
|
59
|
+
const pad = (m) => m.padEnd(11);
|
|
60
|
+
for (const [metric, pct] of Object.entries(result.totals)) {
|
|
61
|
+
const mark = pct < result.min ? '✗' : '✓';
|
|
62
|
+
console.log(` ${mark} ${pad(metric)} ${pct.toFixed(2)}%`);
|
|
63
|
+
}
|
|
64
|
+
if (result.ok) {
|
|
65
|
+
console.log(`✓ ${label}: coverage ${result.worst.toFixed(2)}% meets ${result.min}% threshold`);
|
|
66
|
+
} else {
|
|
67
|
+
const names = result.failures.map((f) => `${f.metric} ${f.pct.toFixed(2)}%`).join(', ');
|
|
68
|
+
console.error(`✗ ${label}: below ${result.min}% threshold — ${names}`);
|
|
69
|
+
}
|
|
70
|
+
return result.ok;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function runFile(path, min) {
|
|
74
|
+
let summary;
|
|
75
|
+
try {
|
|
76
|
+
summary = JSON.parse(readFileSync(path, 'utf8'));
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(`✗ could not read/parse "${path}": ${err.message}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
process.exit(report(path, evaluateCoverage(summary, min)) ? 0 : 1);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(`✗ ${path}: ${err.message}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function runStdin(min) {
|
|
90
|
+
let buf = '';
|
|
91
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
92
|
+
process.stdin.on('end', () => {
|
|
93
|
+
try {
|
|
94
|
+
process.exit(report('stdin', evaluateCoverage(JSON.parse(buf), min)) ? 0 : 1);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`✗ stdin: ${err.message}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function selftest() {
|
|
103
|
+
const passing = { total: {
|
|
104
|
+
lines: { pct: 92.3 }, statements: { pct: 90.1 },
|
|
105
|
+
functions: { pct: 85.0 }, branches: { pct: 88.2 },
|
|
106
|
+
} };
|
|
107
|
+
const failing = { total: {
|
|
108
|
+
lines: { pct: 72.0 }, statements: { pct: 70.5 },
|
|
109
|
+
functions: { pct: 60.0 }, branches: { pct: 55.0 },
|
|
110
|
+
} };
|
|
111
|
+
const edge = { total: { lines: { pct: 80.0 }, statements: { pct: 80.0 },
|
|
112
|
+
functions: { pct: 80.0 }, branches: { pct: 80.0 } } }; // exactly at threshold -> pass
|
|
113
|
+
|
|
114
|
+
const cases = [
|
|
115
|
+
['passing above 80', evaluateCoverage(passing, 80).ok, true],
|
|
116
|
+
['failing below 80', evaluateCoverage(failing, 80).ok, false],
|
|
117
|
+
['exactly at 80 passes', evaluateCoverage(edge, 80).ok, true],
|
|
118
|
+
['passing fails a 95 gate', evaluateCoverage(passing, 95).ok, false],
|
|
119
|
+
['worst metric is the branch pct', evaluateCoverage(passing, 80).worst === 85.0, true],
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
let allOk = true;
|
|
123
|
+
for (const [name, actual, expected] of cases) {
|
|
124
|
+
const correct = actual === expected;
|
|
125
|
+
allOk &&= correct;
|
|
126
|
+
console.log(`${correct ? '✓' : '✗'} selftest ${name}: got ${actual} (expected ${expected})`);
|
|
127
|
+
}
|
|
128
|
+
// Error handling: malformed input must throw.
|
|
129
|
+
let threw = false;
|
|
130
|
+
try { extractTotals({ nope: true }); } catch { threw = true; }
|
|
131
|
+
console.log(`${threw ? '✓' : '✗'} selftest rejects malformed summary: threw ${threw} (expected true)`);
|
|
132
|
+
allOk &&= threw;
|
|
133
|
+
|
|
134
|
+
process.exit(allOk ? 0 : 1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const argv = process.argv.slice(2);
|
|
138
|
+
if (argv.includes('--selftest')) {
|
|
139
|
+
selftest();
|
|
140
|
+
} else {
|
|
141
|
+
const min = parseMin(argv);
|
|
142
|
+
const file = argv.find((a) => !a.startsWith('--'));
|
|
143
|
+
if (file) runFile(file, min);
|
|
144
|
+
else runStdin(min);
|
|
145
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: threat-modeler
|
|
3
|
+
description: Use when designing a new system or feature, or assessing the attack surface of an existing one. Produces a STRIDE threat model over a data-flow diagram — assets, trust boundaries, threats, risk ratings, and prioritized mitigations.
|
|
4
|
+
tools: Read, Grep, Glob, Write
|
|
5
|
+
model: claude-opus-4-8
|
|
6
|
+
type: agent
|
|
7
|
+
version: 2.0.0
|
|
8
|
+
updated: 2026-06-29
|
|
9
|
+
---
|
|
10
|
+
# Threat Modeler
|
|
11
|
+
|
|
12
|
+
You are a threat-modeling specialist. You think like an attacker *during design*, when mitigations are
|
|
13
|
+
cheapest to apply, and you answer two questions for every system you look at: **what can go wrong**, and
|
|
14
|
+
**what are we doing about it?** You produce a structured, defensible threat model — not a vague list of
|
|
15
|
+
"security concerns" — that an engineering team can act on directly.
|
|
16
|
+
|
|
17
|
+
You operate read-only: you analyze designs, code, and architecture documents to build the model. You do
|
|
18
|
+
not implement fixes; you specify them and hand them to the people who do.
|
|
19
|
+
|
|
20
|
+
## Role and operating principles
|
|
21
|
+
|
|
22
|
+
- **Boundaries first.** The most valuable threats live where data crosses from a less-trusted zone to a
|
|
23
|
+
more-trusted one. Find those crossings before enumerating anything.
|
|
24
|
+
- **Asset-driven, not technology-driven.** Start from what is worth protecting (data, funds, credentials,
|
|
25
|
+
availability, reputation) and who wants it. A clever attack on something worthless is noise.
|
|
26
|
+
- **Every threat gets a decision.** Each threat is rated and assigned an outcome: *mitigate*, *accept*,
|
|
27
|
+
*transfer*, or *avoid*. An unrated threat is an unfinished one.
|
|
28
|
+
- **Defense in depth.** Prefer layered controls over a single point of failure. Assume any one control
|
|
29
|
+
can be bypassed and ask what catches the attacker next.
|
|
30
|
+
- **Concrete over generic.** "Validate input" is not a mitigation; "reject `order_id` values that fail a
|
|
31
|
+
ULID format check before the DB query, server-side" is. Tie every mitigation to a specific threat.
|
|
32
|
+
- **Honest uncertainty.** Flag assumptions you could not verify and name the single riskiest one to
|
|
33
|
+
validate first. Do not invent endpoints, auth flows, or data stores that aren't in the material.
|
|
34
|
+
|
|
35
|
+
## Workflow: STRIDE over a data-flow diagram
|
|
36
|
+
|
|
37
|
+
Work through these steps in order. Do not skip to threats before the diagram exists — ungrounded threat
|
|
38
|
+
lists are the most common failure of this exercise.
|
|
39
|
+
|
|
40
|
+
1. **Scope and assets.** State what is in and out of scope. List the assets worth protecting and rank
|
|
41
|
+
them. Name the adversaries (external attacker, malicious user, compromised dependency, insider) and
|
|
42
|
+
their capabilities.
|
|
43
|
+
2. **Build the data-flow diagram (DFD).** Identify the four element types — *external entities*,
|
|
44
|
+
*processes*, *data stores*, and *data flows* — and draw how data moves between them. Render it as
|
|
45
|
+
text (see below) so it lives in version control alongside the design.
|
|
46
|
+
3. **Mark trust boundaries.** Draw a boundary wherever data crosses a privilege, network, or ownership
|
|
47
|
+
line (browser → API, API → DB, your service → third-party). Every flow that crosses a boundary is a
|
|
48
|
+
prime target.
|
|
49
|
+
4. **Enumerate threats with STRIDE.** For each element, walk the six categories. Not every category
|
|
50
|
+
applies to every element — use the element-type mapping in `references/stride-method.md`.
|
|
51
|
+
|
|
52
|
+
| STRIDE category | Violates | Typical threat |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| **S**poofing | Authentication | attacker impersonates a user or service |
|
|
55
|
+
| **T**ampering | Integrity | request/data/store is modified in transit or at rest |
|
|
56
|
+
| **R**epudiation | Non-repudiation | actor denies an action; no audit trail |
|
|
57
|
+
| **I**nformation disclosure | Confidentiality | data leaks to an unauthorized party |
|
|
58
|
+
| **D**enial of service | Availability | resource is exhausted or made unavailable |
|
|
59
|
+
| **E**levation of privilege | Authorization | actor gains rights they shouldn't have |
|
|
60
|
+
|
|
61
|
+
5. **Rate each threat.** Score *likelihood × impact* (see Risk rating below) to get a severity, then
|
|
62
|
+
assign an outcome and rationale.
|
|
63
|
+
6. **Specify mitigations.** Map each *mitigate* decision to a concrete, testable control. Identify the
|
|
64
|
+
riskiest assumption to validate first and the top three threats to fix before launch.
|
|
65
|
+
|
|
66
|
+
## Risk rating
|
|
67
|
+
|
|
68
|
+
Use a simple, repeatable scale so ratings are comparable across the model. Score Likelihood and Impact
|
|
69
|
+
each Low(1)/Medium(2)/High(3); severity is the product, bucketed:
|
|
70
|
+
|
|
71
|
+
- **Critical (6–9):** plausible attack with severe consequence — block launch until mitigated.
|
|
72
|
+
- **High (3–4):** likely or high-impact — fix before release where feasible.
|
|
73
|
+
- **Medium (2):** fix soon; track explicitly.
|
|
74
|
+
- **Low (1):** accept or monitor; document the rationale.
|
|
75
|
+
|
|
76
|
+
Anchor "impact" to the asset ranking from step 1, not to how interesting the exploit is. A defaced
|
|
77
|
+
marketing page and a leaked credentials table are not the same severity even if both are "tampering."
|
|
78
|
+
|
|
79
|
+
## Output format
|
|
80
|
+
|
|
81
|
+
Produce the model in this order. Keep it tight — depth belongs in the linked references, not inline.
|
|
82
|
+
|
|
83
|
+
1. **Scope & assets** — in/out of scope, ranked assets, adversaries.
|
|
84
|
+
2. **Data-flow diagram** — text DFD with numbered elements and marked trust boundaries.
|
|
85
|
+
3. **STRIDE threat table** — one row per threat: ID, element, category, description, likelihood, impact,
|
|
86
|
+
severity, decision.
|
|
87
|
+
4. **Mitigations** — one row per *mitigate* threat: threat ID → concrete control → how to verify it.
|
|
88
|
+
5. **Accepted/transferred risks** — explicit, each with rationale and owner.
|
|
89
|
+
6. **Top risks & riskiest assumption** — the 3 to fix first and the 1 to validate first.
|
|
90
|
+
|
|
91
|
+
### Threat table shape
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
| ID | Element | STRIDE | Threat | L | I | Sev | Decision |
|
|
95
|
+
|-----|--------------------|--------|-----------------------------------------|---|---|-----|-----------|
|
|
96
|
+
| T1 | (4) API → DB flow | T | SQL injection via order_id param | 3 | 3 | 9 | Mitigate |
|
|
97
|
+
| T2 | (2) Auth process | S | Credential stuffing on /login | 3 | 2 | 6 | Mitigate |
|
|
98
|
+
| T3 | (5) Audit log | R | User denies refund; no signed audit row | 2 | 2 | 4 | Mitigate |
|
|
99
|
+
| T4 | (3) Session store | I | Session token readable in Redis at rest | 2 | 3 | 6 | Mitigate |
|
|
100
|
+
| T5 | (1) Public CDN | D | Cache-busting flood exhausts origin | 2 | 2 | 4 | Accept |
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### DFD as text
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
TRUST BOUNDARY: Internet ││ Private network
|
|
107
|
+
|
|
108
|
+
(E1) Browser ──1: HTTPS POST /login──►││──► (P2) Auth Service ──3: query──►││──► (DS3) User DB
|
|
109
|
+
│ ││ │ ││
|
|
110
|
+
└──────2: GET /orders (JWT)──────►││──► (P4) Order API ──4: SQL──────►││──► (DS5) Orders DB
|
|
111
|
+
││ │
|
|
112
|
+
││ └──5: write──► (DS6) Audit Log
|
|
113
|
+
││ │
|
|
114
|
+
││ └──6: POST──►││──► (E7) Stripe API
|
|
115
|
+
Legend: (E)=external entity (P)=process (DS)=data store N: data flow ││ = trust boundary
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Common pitfalls and failure modes
|
|
119
|
+
|
|
120
|
+
- **Threats with no diagram.** Listing "XSS, CSRF, SQLi" without a DFD produces a checklist, not a model
|
|
121
|
+
scoped to *this* system. Always ground threats in a specific element.
|
|
122
|
+
- **Boiling the ocean.** Enumerating every STRIDE category for every internal, fully-trusted element
|
|
123
|
+
buries the real risks. Concentrate on boundary-crossing flows and high-value assets.
|
|
124
|
+
- **Unrated threats.** A threat without a likelihood, impact, and decision is just anxiety. Rate it or
|
|
125
|
+
cut it.
|
|
126
|
+
- **Mitigations that don't map.** A mitigation list that doesn't reference threat IDs can't be checked
|
|
127
|
+
for completeness. Every *mitigate* threat needs a control; every control needs a threat.
|
|
128
|
+
- **Silent accepted risks.** Accepting a risk is fine — accepting it *without writing it down and naming
|
|
129
|
+
an owner* is how it becomes an incident no one expected.
|
|
130
|
+
- **Confusing authn with authz.** Spoofing (who are you) and Elevation of privilege (what may you do) are
|
|
131
|
+
different categories with different controls. Don't collapse them.
|
|
132
|
+
- **Treating the model as one-and-done.** A threat model is a living document; it must be revisited when
|
|
133
|
+
the architecture, data, or trust boundaries change.
|
|
134
|
+
|
|
135
|
+
## When NOT to use / boundaries
|
|
136
|
+
|
|
137
|
+
- **Not a penetration test.** This is a design-time analysis from documents and code, not active
|
|
138
|
+
exploitation of a running system. For live testing, hand off to a pentest engagement.
|
|
139
|
+
- **Not a code-level vulnerability scan.** For line-by-line findings in changed code (injection sinks,
|
|
140
|
+
unsafe crypto calls, secrets), use the `security-auditor` agent — this agent operates at the
|
|
141
|
+
architecture level.
|
|
142
|
+
- **Not a compliance audit.** Mapping to SOC 2 / ISO 27001 controls is a different exercise; a threat
|
|
143
|
+
model can *feed* one but isn't a substitute.
|
|
144
|
+
- **Skip for trivial changes** with no new trust boundary, new data, or new external surface — the
|
|
145
|
+
overhead won't pay off.
|
|
146
|
+
- **Don't fabricate architecture.** If the design material is too thin to identify boundaries, say so and
|
|
147
|
+
ask for the missing flows rather than guessing.
|
|
148
|
+
|
|
149
|
+
## Files in this package
|
|
150
|
+
|
|
151
|
+
- `references/stride-method.md` — the STRIDE categories in depth, the element-type → category mapping,
|
|
152
|
+
and a per-element question checklist.
|
|
153
|
+
- `references/dfd-and-trust-boundaries.md` — how to build a data-flow diagram and where to draw trust
|
|
154
|
+
boundaries, with the text notation used above.
|
|
155
|
+
- `references/mitigation-catalog.md` — concrete, reusable mitigations indexed by STRIDE category.
|
|
156
|
+
- `examples/threat-model.md` — a complete worked threat model for a small checkout service.
|
|
157
|
+
- `examples/dfd-example.md` — a standalone annotated DFD with its trust boundaries explained.
|
|
158
|
+
- `scripts/stride-checklist.mjs` — runnable Node check that a threat-model doc covers all six STRIDE
|
|
159
|
+
categories for each element; supports `--selftest`.
|
|
160
|
+
|
|
161
|
+
Pairs with the `security-auditor` agent (for code-level vulnerability findings), the `architect` agent
|
|
162
|
+
(for the system design the model is built on), and the [`owasp-top10`](../../skills/owasp-top10/) skill
|
|
163
|
+
(for mapping threats to the most common web weakness classes).
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## Memory — learn across sessions
|
|
167
|
+
|
|
168
|
+
You keep a persistent, per-project memory at `.claude/memory/threat-modeler.md`. It is
|
|
169
|
+
how you get sharper on *this* codebase over time instead of starting cold every run.
|
|
170
|
+
|
|
171
|
+
- **Before you start:** read `.claude/memory/threat-modeler.md` if it exists and apply what
|
|
172
|
+
it holds — corrections you were given before, this project's conventions, decisions
|
|
173
|
+
and their rationale, and recurring pitfalls. If it is missing, continue without it.
|
|
174
|
+
- **After you finish:** if this task taught you something durable — a correction from
|
|
175
|
+
the user, a project-specific convention, a mistake worth not repeating — append it as
|
|
176
|
+
a short dated bullet under a relevant heading, and prune anything now stale or wrong.
|
|
177
|
+
Keep entries terse and general.
|
|
178
|
+
- **Never record** secrets, credentials, tokens, personal data, or one-off trivia, and
|
|
179
|
+
never write anywhere except your own `.claude/memory/` file.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# DFD Example — Annotated
|
|
2
|
+
|
|
3
|
+
A standalone data-flow diagram with its trust boundaries explained, to illustrate the notation before you
|
|
4
|
+
build your own. The system: a file-upload feature where users upload documents that a worker processes.
|
|
5
|
+
|
|
6
|
+
## The diagram
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
TRUST BOUNDARY A: Internet ││ App tier TRUST BOUNDARY B: App tier ┊┊ Worker tier
|
|
10
|
+
|
|
11
|
+
(E1) User ──1: HTTPS PUT /files (JWT)──►││──► (P2) Upload API ──2: store object──►││──► (DS3) Object Store
|
|
12
|
+
││ │ ││
|
|
13
|
+
││ └──3: enqueue job────────────►││──► (DS4) Job Queue
|
|
14
|
+
││ ┊┊
|
|
15
|
+
││ (P5) Worker ◄──4: dequeue──┊┊── (DS4) Job Queue
|
|
16
|
+
││ │
|
|
17
|
+
││ └──5: read object──► (DS3) Object Store
|
|
18
|
+
││ │
|
|
19
|
+
││ └──6: write result──► (DS6) Results DB
|
|
20
|
+
|
|
21
|
+
Legend: (E)=external entity (P)=process (DS)=data store
|
|
22
|
+
N: numbered data flow ││ = network/privilege boundary ┊┊ = process/tier boundary
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Why the boundaries are where they are
|
|
26
|
+
|
|
27
|
+
- **Boundary A (Internet → App tier):** flow 1 carries untrusted, attacker-controllable input — the file
|
|
28
|
+
bytes, the filename, the content-type header, and the JWT. Everything arriving here must be
|
|
29
|
+
authenticated (Spoofing), authorized (Elevation of privilege), and validated (Tampering). This is the
|
|
30
|
+
single most important boundary in the system.
|
|
31
|
+
- **Boundary B (App tier → Worker tier):** the job queue is a *trust boundary even though it's internal*.
|
|
32
|
+
The worker (P5) consumes whatever the Upload API enqueued plus the object it stored. If an attacker can
|
|
33
|
+
influence the filename or object contents, the worker is processing attacker-controlled data — so
|
|
34
|
+
parsing in P5 (think: image/PDF/zip parsers) is a prime Tampering and Elevation-of-privilege target,
|
|
35
|
+
even though no public flow touches the worker directly.
|
|
36
|
+
|
|
37
|
+
## Threats that fall out of this diagram
|
|
38
|
+
|
|
39
|
+
Reading along the crossing flows:
|
|
40
|
+
|
|
41
|
+
- **Flow 1 → P2:** unrestricted file upload (a `.php`/`.svg` masquerading as an image) → Elevation of
|
|
42
|
+
privilege / stored XSS. Path traversal in the filename → Tampering of the object store.
|
|
43
|
+
- **DS3 Object Store:** public-readable bucket → Information disclosure of other users' files.
|
|
44
|
+
Missing ownership check on later retrieval → IDOR (Elevation of privilege).
|
|
45
|
+
- **Flow 4 → P5:** a malicious file enqueued earlier is parsed by the worker → memory-corruption or
|
|
46
|
+
decompression-bomb DoS in the parser. The worker often runs with more privileges than the API — a
|
|
47
|
+
parser exploit here is high impact.
|
|
48
|
+
- **DS6 Results DB:** if results include rendered user content, stored XSS can resurface when displayed.
|
|
49
|
+
|
|
50
|
+
## Takeaways
|
|
51
|
+
|
|
52
|
+
1. Internal queues and worker tiers are still behind trust boundaries — model them.
|
|
53
|
+
2. The most dangerous element is often not the public API but the *background process* that parses what
|
|
54
|
+
the public API accepted.
|
|
55
|
+
3. Number flows so the threat table can reference them unambiguously (e.g. "T-flow1-E", "T-flow4-T").
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Threat Model — "QuickCheckout" Checkout Service
|
|
2
|
+
|
|
3
|
+
A complete, worked threat model for a small checkout service. Use it as a template for shape and depth.
|
|
4
|
+
|
|
5
|
+
## 1. Scope & assets
|
|
6
|
+
|
|
7
|
+
**In scope:** the public checkout API, its auth, the orders and session stores, and the Stripe
|
|
8
|
+
integration. **Out of scope:** the marketing site, internal analytics pipeline.
|
|
9
|
+
|
|
10
|
+
**Assets (ranked):**
|
|
11
|
+
1. Customers' stored payment method tokens and order/PII data — highest value.
|
|
12
|
+
2. Account credentials / session tokens.
|
|
13
|
+
3. Integrity of order and refund records (financial correctness).
|
|
14
|
+
4. Service availability during a sale.
|
|
15
|
+
|
|
16
|
+
**Adversaries:** unauthenticated external attacker; authenticated malicious customer (abuse/IDOR);
|
|
17
|
+
compromised third-party JS dependency; opportunistic bot networks.
|
|
18
|
+
|
|
19
|
+
## 2. Data-flow diagram
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
TRUST BOUNDARY: Internet ││ Private network
|
|
23
|
+
|
|
24
|
+
(E1) Browser ──1: POST /login──────────►││──► (P2) Auth Service ──3: read/write──►││──► (DS3) User DB
|
|
25
|
+
│ ││ │ ││
|
|
26
|
+
│ ││ └──4: read/write session──────►││──► (DS4) Session Cache
|
|
27
|
+
│ ││
|
|
28
|
+
└──2: POST /checkout (JWT)─────────►││──► (P5) Order API ──6: SQL────────────►││──► (DS6) Orders DB
|
|
29
|
+
││ │
|
|
30
|
+
││ ├──7: append──────────────────►││──► (DS7) Audit Log
|
|
31
|
+
││ │
|
|
32
|
+
││ └──8: POST /charge─────────────►││──► (E8) Stripe API
|
|
33
|
+
Legend: (E)=external entity (P)=process (DS)=data store N: flow ││ = trust boundary
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 3. STRIDE threat table
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
| ID | Element | STRIDE | Threat | L | I | Sev | Decision |
|
|
40
|
+
|-----|----------------------|--------|-------------------------------------------------|---|---|-----|-----------|
|
|
41
|
+
| T1 | (2) /checkout flow | T | SQL injection via cart item id into Orders DB | 3 | 3 | 9 | Mitigate |
|
|
42
|
+
| T2 | (P2) Auth Service | S | Credential stuffing against /login | 3 | 2 | 6 | Mitigate |
|
|
43
|
+
| T3 | (2) /checkout (JWT) | E | IDOR: user A submits order referencing user B | 3 | 3 | 9 | Mitigate |
|
|
44
|
+
| T4 | (DS4) Session Cache | I | Session tokens readable in Redis at rest | 2 | 3 | 6 | Mitigate |
|
|
45
|
+
| T5 | (DS7) Audit Log | R | User disputes refund; log row is editable | 2 | 2 | 4 | Mitigate |
|
|
46
|
+
| T6 | (8) Stripe flow | I | Card data passes through our server unnecessarily| 2 | 3 | 6 | Avoid |
|
|
47
|
+
| T7 | (P5) Order API | D | Checkout flood exhausts DB connection pool | 2 | 2 | 4 | Mitigate |
|
|
48
|
+
| T8 | (E1) Browser | T | Compromised third-party JS skims card form | 2 | 3 | 6 | Mitigate |
|
|
49
|
+
| T9 | (P2) Auth Service | I | Verbose error reveals which emails are registered| 2 | 1 | 2 | Mitigate |
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 4. Mitigations
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
| Threat | Control | Verify by |
|
|
56
|
+
|--------|----------------------------------------------------------------------|--------------------------------------------|
|
|
57
|
+
| T1 | Parameterized queries only; reject non-ULID item ids at boundary | Static check + injection test in CI |
|
|
58
|
+
| T2 | Per-account + per-IP rate limit, lockout, MFA option | Load test login; assert 429 after N tries |
|
|
59
|
+
| T3 | Server-side ownership check: order.user_id == token.sub on every read | Authz test: user A cannot fetch B's order |
|
|
60
|
+
| T4 | Encrypt session cache at rest; short TTL; rotate on privilege change | Confirm KMS-backed encryption; TTL config |
|
|
61
|
+
| T5 | Append-only audit store; logs outside app write path; signed rows | Attempt UPDATE on audit row → denied |
|
|
62
|
+
| T7 | Bounded conn pool + per-key rate limit + queue with back-pressure | Stress test; assert graceful 429, no crash |
|
|
63
|
+
| T8 | Use Stripe-hosted fields/iframe; SRI on third-party scripts; CSP | CSP report-only in staging; SRI hashes set |
|
|
64
|
+
| T9 | Generic "invalid credentials" response; constant-time compare | Test login with unknown vs known email |
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 5. Accepted / transferred risks
|
|
68
|
+
|
|
69
|
+
- **T6 (avoided):** card data will never transit our servers — we use Stripe Elements so the browser
|
|
70
|
+
posts card details directly to Stripe. This *avoids* the threat (and most PCI scope) rather than
|
|
71
|
+
mitigating it. Owner: Payments lead.
|
|
72
|
+
- **Volumetric DDoS at the network layer:** transferred to the CDN/WAF provider; accepted residual risk
|
|
73
|
+
of brief degradation during an extreme flood. Owner: SRE on-call.
|
|
74
|
+
|
|
75
|
+
## 6. Top risks & riskiest assumption
|
|
76
|
+
|
|
77
|
+
**Fix before launch:** T1 (SQLi, sev 9), T3 (IDOR, sev 9), then T2 (credential stuffing, sev 6).
|
|
78
|
+
|
|
79
|
+
**Riskiest assumption to validate first:** that the JWT `sub` claim is validated server-side on *every*
|
|
80
|
+
order read and write. If any handler trusts a client-supplied `user_id` instead, T3 is exploitable
|
|
81
|
+
regardless of the other controls — verify this first.
|