Security Craft
LLM-judgment critique of security posture for TS/JS source — the ceiling counterpart to harness-security-scan (CVE/OWASP rule-based floor) and harness-security-reviewer (procedural review). Threat-modeling-as-skill rather than pattern-matching. Critiques whether trust boundaries are respected, where implicit privilege escalation lurks, whether the code defends in depth or just at the gate, whether principle of least authority is honored. Sixth non-design member of the craft-pipeline initiative (the final sub-project). Emits 3-axis findings (tier × impact × confidence per ADR 0019).
When to Use
- During PR review on a substantively-changed handler / middleware / privileged op
- After authoring a new endpoint, before exposing it to traffic
- When onboarding a new contributor (audit security-relevant code they introduced)
- Periodically (per-sprint or per-release) to catch security-shape drift
- For threat-modeling shape questions a CVE scanner doesn't address (trust boundaries, fail-closed, authz ordering)
- NOT for CVE / dependency scanning (use
harness-security-scan — rule-based floor)
- NOT for procedural review checklists (use
harness-security-reviewer)
- NOT for IaC critique (v1.x — Dockerfile / k8s / Terraform have different rubric vocabulary)
- NOT for secret detection (floor concern; existing regex/entropy scanners cover this)
- NOT for autofix / security rewriting (this is judgment-only; v1.x may add
align-security with aggressive safeguards)
- NOT for test files (v1 excludes — test security has a different shape; v1.x with dedicated rubrics)
Capability Roles
- Defines (Service Definition): the shared craft critique contract (
packages/cli/src/shared/craft/) — LlmProvider + finding/axes schema + run store — shared across all *-craft skills. This skill implements, and does not own, that contract.
- Provides (Provider): this skill — a security-posture critique implemented over the shared contract (
packages/cli/src/security-craft/).
- Consumes (Consumer):
craft-fleet (the craft-pipeline elevation sweep) and the harness natural-language router, which invoke every *-craft provider uniformly through the shared critique/finding shape
Process
Phase 1: DISCOVER — Find source files
- Walk
packages/*/src/ recursively.
- Include
*.{ts,tsx,js,jsx,mjs,cjs}; exclude test files (*.test.*, *.spec.*, tests/, __tests__/).
- Exclude generated / build / coverage dirs (
node_modules, dist, build, coverage, .next, .turbo, __snapshots__).
- Honor
--packages for explicit package scoping; --files overrides discovery.
Phase 2: SIGNAL — AST-driven security construct detection
For each source file, the TS Compiler API walks the AST once and emits SecuritySignals. Files with zero signals are skipped entirely — this is the FP-management strategy from the spec (no path-heuristic fallback).
Detected signal kinds:
| Kind |
What it matches |
http-handler |
(req, res) / (req, res, next) shapes; app.get/post/...; @Get/@Post/... |
middleware |
(req, res, next) => arrow / (ctx, next) shapes |
auth-api |
jwt.{sign,verify}, bcrypt.{hash,compare}, argon2.*, passport.*, req.session.*, res.cookie |
privileged-op |
child_process.{exec,spawn,...}, eval, new Function, vm.runIn*, fs.{writeFile,unlink,chmod,...} |
data-egress |
fetch, axios.*, http.request, https.request, net.connect |
raw-query |
*.query(\...${x}...`), *.raw(...), $queryRaw, $executeRaw` with SQL-shaped argument |
secret-handling |
Secret-named variable (token, password, apiKey, …) flowing into console.*, logger.*, JSON.stringify, template-literal sink |
AST awareness (not regex) avoids common false positives: exec in a comment, eval as a variable name, token in a CSS property name.
Phase 3: CRITIQUE — Per (file, signal, rubric) loop
8 seed rubrics, each declaring appliesToSignals so per-signal pre-filtering minimizes LLM cost:
| Rubric |
Title |
Applies to signals |
SEC-R001 |
Trust boundary respected |
http-handler, middleware, raw-query, privileged-op |
SEC-R002 |
Principle of least authority honored |
auth-api, privileged-op, http-handler |
SEC-R003 |
Defense in depth (not gate-only) |
auth-api, http-handler |
SEC-R004 |
Assumed adversary realistic for the deployment |
http-handler, middleware, auth-api |
SEC-R005 |
Data flow across trust boundaries is visible |
http-handler, raw-query, data-egress, privileged-op |
SEC-R006 |
Fail closed, not open |
auth-api, middleware, http-handler |
SEC-R007 |
Secrets carried in a shape that resists leakage |
secret-handling |
SEC-R008 |
Authorization check happens before the action |
http-handler, privileged-op |
For each (signal, rubric) pair where the rubric applies:
- Build a prompt with rubric description + file path + signal info + 1500-char window AROUND the signal line (not the whole file — security-critical context is local).
- Conservative-confidence system prompt biases the LLM toward
medium confidence by default; high requires a specific, named anti-pattern or visible missing guard.
- LLM returns fenced JSON:
null (rubric doesn't apply / code is fine) OR { tier, impact, confidence, message }.
- On non-null: emit a
SecurityFinding with cite.rubricId populated for ADR 0020 traceability.
Phase 4: REPORT — Aggregate + cost telemetry
Emit SecurityCraftOutput:
{
findings: SecurityFinding[];
summary: {
phaseRun: ['critique'];
mode: 'fast';
durationMs: number;
llmCalls: { provider, model, count, costUsd };
catalog: { rubricsApplied: string[] };
counts: { filesScanned, filesSkippedNoSignal, signalsDetected };
runId: string;
}
}
filesSkippedNoSignal is tracked separately so report consumers can see how aggressively the AST pre-filter trimmed the corpus.
Harness Integration
harness security-craft — CLI entry. --files <glob> / --packages <names> / --max-files <n> / --max-signals-per-file <n> / --json / --verbose.
mcp__harness__security_craft — MCP tool. Same input/output. Consumed by agents.
- Cross-cutting API:
critiqueSecurityInFile(file, opts) exported from packages/cli/src/security-craft/index.ts. Returns [] for files with no security signals (consistent with the orchestrator's FP-management strategy).
- Shared craft infrastructure:
LlmProvider, MockLlmProvider, derivePriority, 3-axis types all live in packages/cli/src/shared/craft/.
Success Criteria
See docs/changes/craft-pipeline/security-craft/proposal.md for the full 29 success criteria. Highlights:
- 8 seed rubrics ship at
catalog/rubrics/<id>.ts (file-per-rubric)
- AST detector emits signals for all 7 signal kinds; comment / string contents don't fire (AST-aware, not regex)
- Files with zero signals are skipped (
filesSkippedNoSignal tracked)
- Per-rubric
appliesToSignals pre-filter avoids irrelevant LLM calls
- 3-axis output preserved; confidence defaults to medium per the spec's confidence-defaults decision
cite.rubricId populated on every finding (ADR 0020)
critiqueSecurityInFile cross-cutting API works on a single file
Rationalizations to Reject
These are common rationalizations that sound reasonable but lead to incorrect results. When you catch yourself thinking any of these, stop and follow the documented process instead.
| Rationalization |
Why It Is Wrong |
"harness-security-scan found no CVEs, so this handler is secure." |
That scanner is the rule-based floor. Security-craft critiques shape: a CVE-clean handler can still load a document before authorizing (SEC-R008) or fail open on error (SEC-R006). |
"The file contains the words exec and token, so I'll critique it even with no signal." |
Detection is AST-driven, not regex. exec in a comment or token as a CSS property name emits no SecuritySignal; zero-signal files are skipped by design. Do not critique a string match. |
| "This looks dangerous but I can't name the exploit, so I'll mark it high confidence anyway." |
The system prompt biases to medium by default; high requires a specific named anti-pattern or a visibly missing guard. "Feels risky" without a named sink is medium at most. |
| "An auth check exists in the handler, so SEC-R003 (defense in depth) passes." |
A single check at the entrance is exactly gate-only defense. SEC-R003 asks whether the code defends in depth — one authz gate with no downstream validation is what the rubric flags. |
| "It's an internal-only endpoint, so the adversary model is moot and SEC-R004 passes." |
Assumed-adversary-realistic-for-the-deployment is not "internal, therefore trusted". SSRF, compromised neighbours, and confused-deputy paths all reach internal endpoints. State the realistic adversary; don't assume one away. |
Examples
Example: User input flowing into child_process
Input: packages/api/src/handlers/run-script.ts:
import { exec } from 'child_process';
import type { Request, Response } from 'express';
export function runScript(req: Request, res: Response): void {
const userScript = req.body.script;
exec(`bash -c "${userScript}"`, (err, stdout) => {
res.json({ output: stdout });
});
}
Output (mock LLM):
SEC-R001 [foundational/large/high] child_process.exec:5
User-controlled `req.body.script` flows directly into `bash -c "${userScript}"`.
This is a textbook command-injection sink. Either reject the entire pattern
(no user-supplied shell strings) or move to `execFile` with an allowlist of
binaries and pre-validated arg arrays. Never templated into a shell.
SEC-R005 [foundational/large/high] child_process.exec:5
Untrusted input (`req.body.script`) crosses the trust boundary into a
privileged sink without any visible validation or escaping step. The crossing
is invisible — the variable is named generically and goes straight to exec.
Example: Auth check after action
Input: packages/api/src/handlers/get-doc.ts:
export async function getDoc(req: Request, res: Response) {
const doc = await db.docs.findOne({ id: req.params.id });
if (doc.ownerId !== req.user.id) return res.status(403).send();
return res.json(doc);
}
Output:
SEC-R008 [foundational/medium/medium] req,res:1
The document is loaded BEFORE the authorization check. Even though the
response is denied, the load has already executed — observable side effects
(audit logs, rate-limit counters, cache populations) leak existence
information about documents the caller can't access. Authorize against the
identifier first (`req.params.id` + `req.user.id`), then load.
Example: File with no security signals
Input: A pure utility file with no http/auth/exec/fs/network constructs.
Output:
No security findings.
Summary: 0 findings across 0 files (12 skipped, 0 signals, 8 rubrics, 0 LLM calls, $0.0000, 4ms)
The 12 files were scanned for signals but skipped because none had security-relevant AST constructs — exactly the FP-management strategy at work.
Gates
- No autofix. Sibling
align-security deferred to v2 with aggressive FP safeguards (security rewrites have asymmetric downside).
- No IaC critique. Dockerfile / k8s / Terraform need different rubrics; v1.x.
- No multi-file auth-flow tracing. Cross-file privilege-escalation analysis (handler → middleware → service) needs a graph traversal layer; v1.x once cross-file critique pays for itself elsewhere.
- No dependency / CVE scanning.
harness-security-scan is the floor.
- No secret detection (floor concern).
- No test-file critique. Test security has a different shape; v1.x.
- No path-heuristic fallback. If AST scan finds zero signals, the file is skipped. Tight scoping is part of the FP-management strategy.
- No B' bootstrap.
Escalation
- When LLM cost is too high: drop
maxFiles to 50 or maxSignalsPerFile to 5, or scope explicitly with --packages <name>. Per-file cost = (signals × applicable rubrics × per-call); typical handler fires ~3-5 rubrics, not 8.
- When a specific rubric produces false positives: v1 has no per-rubric disable; v1.x adds
craft.security.disabledRubrics: ['SEC-R004']. Until then: filter findings by cite.rubricId downstream.
- When the AST detector misses a framework you use (tRPC, Convex, Cloudflare Workers, Hono RPC): v1 ships baseline coverage for Express / Hono / Fastify / Koa / NestJS-decorator. Adding a framework is a 1-line config in
signals.ts. Track as v1.x.
- When findings are too cautious (confidence floor): the conservative-by-default is deliberate (FP management); v1.x adds
craft.security.confidenceFloor to tighten further. Loosening below medium is intentionally not exposed.
- When you want IaC critique: v1.x. For v1, scope explicitly to source files and accept the gap.
- When a finding is wrong: dismiss it in your consumer; signal as a
suppressedAt entry on the rubric for future catalog evolution.
Status
v1 — in implementation. See:
- Spec:
docs/changes/craft-pipeline/security-craft/proposal.md
- Roadmap entry: part of the
craft-pipeline initiative (the final sub-project; the craft-pipeline initiative completes with this PR)
- Sibling craft skills:
naming-craft, spec-craft, copy-craft, test-craft, knowledge-craft, harness-design-craft (design-pipeline)
- Shared infrastructure:
packages/cli/src/shared/craft/
- Future:
align-security (FIX side; aggressive safeguards), IaC critique, multi-file auth-flow tracing, test-file security, framework expansions (tRPC / Convex / Cloudflare Workers / Hono RPC).
1---2name: security-craft3description: Security Craft4---5# Security Craft67> LLM-judgment critique of security posture for TS/JS source — the ceiling counterpart to `harness-security-scan` (CVE/OWASP rule-based floor) and `harness-security-reviewer` (procedural review). Threat-modeling-as-skill rather than pattern-matching. Critiques whether trust boundaries are respected, where implicit privilege escalation lurks, whether the code defends in depth or just at the gate, whether principle of least authority is honored. Sixth non-design member of the craft-pipeline initiative (the final sub-project). Emits 3-axis findings (tier × impact × confidence per ADR 0019).89## When to Use1011- During PR review on a substantively-changed handler / middleware / privileged op12- After authoring a new endpoint, before exposing it to traffic13- When onboarding a new contributor (audit security-relevant code they introduced)14- Periodically (per-sprint or per-release) to catch security-shape drift15- For threat-modeling shape questions a CVE scanner doesn't address (trust boundaries, fail-closed, authz ordering)16- NOT for CVE / dependency scanning (use `harness-security-scan` — rule-based floor)17- NOT for procedural review checklists (use `harness-security-reviewer`)18- NOT for IaC critique (v1.x — Dockerfile / k8s / Terraform have different rubric vocabulary)19- NOT for secret detection (floor concern; existing regex/entropy scanners cover this)20- NOT for autofix / security rewriting (this is judgment-only; v1.x may add `align-security` with aggressive safeguards)21- NOT for test files (v1 excludes — test security has a different shape; v1.x with dedicated rubrics)2223## Capability Roles2425<!-- Capability seam: this skill participates in a real extension point whose three roles are named and concrete. A seam with only one role filled is accidental single-implementation lock-in. See harness-skill-authoring Phase 1C. -->2627- **Defines (Service Definition):** the shared craft critique contract (`packages/cli/src/shared/craft/`) — `LlmProvider` + finding/axes schema + run store — shared across all `*-craft` skills. This skill implements, and does not own, that contract.28- **Provides (Provider):** **this skill** — a security-posture critique implemented over the shared contract (`packages/cli/src/security-craft/`).29- **Consumes (Consumer):** `craft-fleet` (the craft-pipeline elevation sweep) and the `harness` natural-language router, which invoke every `*-craft` provider uniformly through the shared critique/finding shape3031## Process3233### Phase 1: DISCOVER — Find source files34351. Walk `packages/*/src/` recursively.362. Include `*.{ts,tsx,js,jsx,mjs,cjs}`; exclude test files (`*.test.*`, `*.spec.*`, `tests/`, `__tests__/`).373. Exclude generated / build / coverage dirs (`node_modules`, `dist`, `build`, `coverage`, `.next`, `.turbo`, `__snapshots__`).384. Honor `--packages` for explicit package scoping; `--files` overrides discovery.3940### Phase 2: SIGNAL — AST-driven security construct detection4142For each source file, the TS Compiler API walks the AST once and emits `SecuritySignal`s. Files with zero signals are **skipped entirely** — this is the FP-management strategy from the spec (no path-heuristic fallback).4344Detected signal kinds:4546| Kind | What it matches |47| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- |48| `http-handler` | `(req, res)` / `(req, res, next)` shapes; `app.get/post/...`; `@Get/@Post/...` |49| `middleware` | `(req, res, next) =>` arrow / `(ctx, next)` shapes |50| `auth-api` | `jwt.{sign,verify}`, `bcrypt.{hash,compare}`, `argon2.*`, `passport.*`, `req.session.*`, `res.cookie` |51| `privileged-op` | `child_process.{exec,spawn,...}`, `eval`, `new Function`, `vm.runIn*`, `fs.{writeFile,unlink,chmod,...}` |52| `data-egress` | `fetch`, `axios.*`, `http.request`, `https.request`, `net.connect` |53| `raw-query` | `*.query(\`...${x}...\`)`, `*.raw(...)`, `$queryRaw`, `$executeRaw` with SQL-shaped argument |54| `secret-handling` | Secret-named variable (`token`, `password`, `apiKey`, …) flowing into `console.*`, `logger.*`, `JSON.stringify`, template-literal sink |5556AST awareness (not regex) avoids common false positives: `exec` in a comment, `eval` as a variable name, `token` in a CSS property name.5758### Phase 3: CRITIQUE — Per (file, signal, rubric) loop59608 seed rubrics, each declaring `appliesToSignals` so per-signal pre-filtering minimizes LLM cost:6162| Rubric | Title | Applies to signals |63| ---------- | ----------------------------------------------- | --------------------------------------------------- |64| `SEC-R001` | Trust boundary respected | http-handler, middleware, raw-query, privileged-op |65| `SEC-R002` | Principle of least authority honored | auth-api, privileged-op, http-handler |66| `SEC-R003` | Defense in depth (not gate-only) | auth-api, http-handler |67| `SEC-R004` | Assumed adversary realistic for the deployment | http-handler, middleware, auth-api |68| `SEC-R005` | Data flow across trust boundaries is visible | http-handler, raw-query, data-egress, privileged-op |69| `SEC-R006` | Fail closed, not open | auth-api, middleware, http-handler |70| `SEC-R007` | Secrets carried in a shape that resists leakage | secret-handling |71| `SEC-R008` | Authorization check happens before the action | http-handler, privileged-op |7273For each (signal, rubric) pair where the rubric applies:74751. Build a prompt with rubric description + file path + signal info + **1500-char window AROUND the signal line** (not the whole file — security-critical context is local).762. **Conservative-confidence system prompt** biases the LLM toward `medium` confidence by default; `high` requires a specific, named anti-pattern or visible missing guard.773. LLM returns fenced JSON: `null` (rubric doesn't apply / code is fine) OR `{ tier, impact, confidence, message }`.784. On non-null: emit a `SecurityFinding` with `cite.rubricId` populated for ADR 0020 traceability.7980### Phase 4: REPORT — Aggregate + cost telemetry8182Emit `SecurityCraftOutput`:8384```ts85{86 findings: SecurityFinding[];87 summary: {88 phaseRun: ['critique'];89 mode: 'fast';90 durationMs: number;91 llmCalls: { provider, model, count, costUsd };92 catalog: { rubricsApplied: string[] };93 counts: { filesScanned, filesSkippedNoSignal, signalsDetected };94 runId: string;95 }96}97```9899`filesSkippedNoSignal` is tracked separately so report consumers can see how aggressively the AST pre-filter trimmed the corpus.100101## Harness Integration102103- **`harness security-craft`** — CLI entry. `--files <glob>` / `--packages <names>` / `--max-files <n>` / `--max-signals-per-file <n>` / `--json` / `--verbose`.104- **`mcp__harness__security_craft`** — MCP tool. Same input/output. Consumed by agents.105- **Cross-cutting API:** `critiqueSecurityInFile(file, opts)` exported from `packages/cli/src/security-craft/index.ts`. Returns `[]` for files with no security signals (consistent with the orchestrator's FP-management strategy).106- **Shared craft infrastructure:** `LlmProvider`, `MockLlmProvider`, `derivePriority`, 3-axis types all live in `packages/cli/src/shared/craft/`.107108## Success Criteria109110See `docs/changes/craft-pipeline/security-craft/proposal.md` for the full 29 success criteria. Highlights:111112- 8 seed rubrics ship at `catalog/rubrics/<id>.ts` (file-per-rubric)113- AST detector emits signals for all 7 signal kinds; comment / string contents don't fire (AST-aware, not regex)114- Files with zero signals are skipped (`filesSkippedNoSignal` tracked)115- Per-rubric `appliesToSignals` pre-filter avoids irrelevant LLM calls116- 3-axis output preserved; confidence defaults to medium per the spec's confidence-defaults decision117- `cite.rubricId` populated on every finding (ADR 0020)118- `critiqueSecurityInFile` cross-cutting API works on a single file119120## Rationalizations to Reject121122These are common rationalizations that sound reasonable but lead to incorrect results. When you catch yourself thinking any of these, stop and follow the documented process instead.123124| Rationalization | Why It Is Wrong |125| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |126| "`harness-security-scan` found no CVEs, so this handler is secure." | That scanner is the rule-based floor. Security-craft critiques shape: a CVE-clean handler can still load a document before authorizing (SEC-R008) or fail open on error (SEC-R006). |127| "The file contains the words `exec` and `token`, so I'll critique it even with no signal." | Detection is AST-driven, not regex. `exec` in a comment or `token` as a CSS property name emits no `SecuritySignal`; zero-signal files are skipped by design. Do not critique a string match. |128| "This looks dangerous but I can't name the exploit, so I'll mark it high confidence anyway." | The system prompt biases to `medium` by default; `high` requires a specific named anti-pattern or a visibly missing guard. "Feels risky" without a named sink is `medium` at most. |129| "An auth check exists in the handler, so SEC-R003 (defense in depth) passes." | A single check at the entrance is exactly gate-only defense. SEC-R003 asks whether the code defends in depth — one authz gate with no downstream validation is what the rubric flags. |130| "It's an internal-only endpoint, so the adversary model is moot and SEC-R004 passes." | Assumed-adversary-realistic-for-the-deployment is not "internal, therefore trusted". SSRF, compromised neighbours, and confused-deputy paths all reach internal endpoints. State the realistic adversary; don't assume one away. |131132## Examples133134### Example: User input flowing into child_process135136**Input:** `packages/api/src/handlers/run-script.ts`:137138```ts139import { exec } from 'child_process';140import type { Request, Response } from 'express';141142export function runScript(req: Request, res: Response): void {143 const userScript = req.body.script;144 exec(`bash -c "${userScript}"`, (err, stdout) => {145 res.json({ output: stdout });146 });147}148```149150**Output (mock LLM):**151152```153SEC-R001 [foundational/large/high] child_process.exec:5154 User-controlled `req.body.script` flows directly into `bash -c "${userScript}"`.155 This is a textbook command-injection sink. Either reject the entire pattern156 (no user-supplied shell strings) or move to `execFile` with an allowlist of157 binaries and pre-validated arg arrays. Never templated into a shell.158SEC-R005 [foundational/large/high] child_process.exec:5159 Untrusted input (`req.body.script`) crosses the trust boundary into a160 privileged sink without any visible validation or escaping step. The crossing161 is invisible — the variable is named generically and goes straight to exec.162```163164### Example: Auth check after action165166**Input:** `packages/api/src/handlers/get-doc.ts`:167168```ts169export async function getDoc(req: Request, res: Response) {170 const doc = await db.docs.findOne({ id: req.params.id });171 if (doc.ownerId !== req.user.id) return res.status(403).send();172 return res.json(doc);173}174```175176**Output:**177178```179SEC-R008 [foundational/medium/medium] req,res:1180 The document is loaded BEFORE the authorization check. Even though the181 response is denied, the load has already executed — observable side effects182 (audit logs, rate-limit counters, cache populations) leak existence183 information about documents the caller can't access. Authorize against the184 identifier first (`req.params.id` + `req.user.id`), then load.185```186187### Example: File with no security signals188189**Input:** A pure utility file with no http/auth/exec/fs/network constructs.190191**Output:**192193```194No security findings.195196Summary: 0 findings across 0 files (12 skipped, 0 signals, 8 rubrics, 0 LLM calls, $0.0000, 4ms)197```198199The 12 files were scanned for signals but skipped because none had security-relevant AST constructs — exactly the FP-management strategy at work.200201## Gates202203- **No autofix.** Sibling `align-security` deferred to v2 with aggressive FP safeguards (security rewrites have asymmetric downside).204- **No IaC critique.** Dockerfile / k8s / Terraform need different rubrics; v1.x.205- **No multi-file auth-flow tracing.** Cross-file privilege-escalation analysis (handler → middleware → service) needs a graph traversal layer; v1.x once cross-file critique pays for itself elsewhere.206- **No dependency / CVE scanning.** `harness-security-scan` is the floor.207- **No secret detection** (floor concern).208- **No test-file critique.** Test security has a different shape; v1.x.209- **No path-heuristic fallback.** If AST scan finds zero signals, the file is skipped. Tight scoping is part of the FP-management strategy.210- **No B' bootstrap.**211212## Escalation213214- **When LLM cost is too high:** drop `maxFiles` to 50 or `maxSignalsPerFile` to 5, or scope explicitly with `--packages <name>`. Per-file cost = (signals × applicable rubrics × per-call); typical handler fires ~3-5 rubrics, not 8.215- **When a specific rubric produces false positives:** v1 has no per-rubric disable; v1.x adds `craft.security.disabledRubrics: ['SEC-R004']`. Until then: filter findings by `cite.rubricId` downstream.216- **When the AST detector misses a framework you use** (tRPC, Convex, Cloudflare Workers, Hono RPC): v1 ships baseline coverage for Express / Hono / Fastify / Koa / NestJS-decorator. Adding a framework is a 1-line config in `signals.ts`. Track as v1.x.217- **When findings are too cautious (confidence floor):** the conservative-by-default is deliberate (FP management); v1.x adds `craft.security.confidenceFloor` to tighten further. Loosening below medium is intentionally not exposed.218- **When you want IaC critique:** v1.x. For v1, scope explicitly to source files and accept the gap.219- **When a finding is wrong:** dismiss it in your consumer; signal as a `suppressedAt` entry on the rubric for future catalog evolution.220221## Status222223**v1 — in implementation.** See:224225- Spec: `docs/changes/craft-pipeline/security-craft/proposal.md`226- Roadmap entry: part of the `craft-pipeline` initiative (the final sub-project; the craft-pipeline initiative completes with this PR)227- Sibling craft skills: `naming-craft`, `spec-craft`, `copy-craft`, `test-craft`, `knowledge-craft`, `harness-design-craft` (design-pipeline)228- Shared infrastructure: `packages/cli/src/shared/craft/`229- Future: `align-security` (FIX side; aggressive safeguards), IaC critique, multi-file auth-flow tracing, test-file security, framework expansions (tRPC / Convex / Cloudflare Workers / Hono RPC).