mishkan-harness 0.1.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.
Files changed (186) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/bin/mishkan.js +221 -0
  4. package/docs/design/MISHKAN_agent_aliases.md +140 -0
  5. package/docs/design/MISHKAN_decisions.md +172 -0
  6. package/docs/design/MISHKAN_harness_design.md +820 -0
  7. package/docs/design/MISHKAN_ontology.md +87 -0
  8. package/docs/design/MISHKAN_token_optimisation.md +181 -0
  9. package/docs/engineer/README.md +37 -0
  10. package/docs/engineer/profile.example.md +79 -0
  11. package/docs/usage/01-installation.md +178 -0
  12. package/docs/usage/02-project-init.md +151 -0
  13. package/docs/usage/03-orchestration.md +218 -0
  14. package/docs/usage/04-memory-layer.md +201 -0
  15. package/docs/usage/05-selective-ingest.md +177 -0
  16. package/docs/usage/06-llm-providers.md +195 -0
  17. package/docs/usage/07-troubleshooting.md +316 -0
  18. package/docs/usage/08-glossary.md +154 -0
  19. package/docs/usage/09-workflows.md +123 -0
  20. package/docs/usage/README.md +77 -0
  21. package/package.json +43 -0
  22. package/payload/install/settings.hooks.json +47 -0
  23. package/payload/mishkan/AGENT_SPEC.md +154 -0
  24. package/payload/mishkan/agents/ahikam.md +58 -0
  25. package/payload/mishkan/agents/aholiab.md +68 -0
  26. package/payload/mishkan/agents/asaph.md +73 -0
  27. package/payload/mishkan/agents/baruch.md +88 -0
  28. package/payload/mishkan/agents/benaiah.md +76 -0
  29. package/payload/mishkan/agents/bezalel.md +83 -0
  30. package/payload/mishkan/agents/caleb.md +74 -0
  31. package/payload/mishkan/agents/deborah.md +63 -0
  32. package/payload/mishkan/agents/elasah.md +58 -0
  33. package/payload/mishkan/agents/eliashib.md +68 -0
  34. package/payload/mishkan/agents/ezra.md +69 -0
  35. package/payload/mishkan/agents/hanun.md +64 -0
  36. package/payload/mishkan/agents/hiram.md +68 -0
  37. package/payload/mishkan/agents/hizkiah.md +76 -0
  38. package/payload/mishkan/agents/huldah.md +59 -0
  39. package/payload/mishkan/agents/huram.md +66 -0
  40. package/payload/mishkan/agents/hushai.md +59 -0
  41. package/payload/mishkan/agents/igal.md +58 -0
  42. package/payload/mishkan/agents/ira.md +86 -0
  43. package/payload/mishkan/agents/jahaziel.md +71 -0
  44. package/payload/mishkan/agents/jakin.md +66 -0
  45. package/payload/mishkan/agents/jehonathan.md +62 -0
  46. package/payload/mishkan/agents/jehoshaphat.md +68 -0
  47. package/payload/mishkan/agents/joab.md +71 -0
  48. package/payload/mishkan/agents/joah.md +62 -0
  49. package/payload/mishkan/agents/maaseiah.md +61 -0
  50. package/payload/mishkan/agents/meremoth.md +65 -0
  51. package/payload/mishkan/agents/meshullam.md +67 -0
  52. package/payload/mishkan/agents/nathan.md +70 -0
  53. package/payload/mishkan/agents/nehemiah.md +93 -0
  54. package/payload/mishkan/agents/obed.md +60 -0
  55. package/payload/mishkan/agents/oholiab.md +67 -0
  56. package/payload/mishkan/agents/palal.md +63 -0
  57. package/payload/mishkan/agents/phinehas.md +73 -0
  58. package/payload/mishkan/agents/rehum.md +60 -0
  59. package/payload/mishkan/agents/salma.md +69 -0
  60. package/payload/mishkan/agents/seraiah.md +73 -0
  61. package/payload/mishkan/agents/shallum.md +66 -0
  62. package/payload/mishkan/agents/shaphan.md +64 -0
  63. package/payload/mishkan/agents/shemaiah.md +67 -0
  64. package/payload/mishkan/agents/shevna.md +58 -0
  65. package/payload/mishkan/agents/uriah.md +70 -0
  66. package/payload/mishkan/agents/zaccur.md +58 -0
  67. package/payload/mishkan/agents/zadok.md +67 -0
  68. package/payload/mishkan/agents/zerubbabel.md +69 -0
  69. package/payload/mishkan/cognee/.env.curated.example +61 -0
  70. package/payload/mishkan/cognee/.env.example +165 -0
  71. package/payload/mishkan/cognee/Dockerfile +50 -0
  72. package/payload/mishkan/cognee/README.md +129 -0
  73. package/payload/mishkan/cognee/docker-compose.curated-ui.yml +61 -0
  74. package/payload/mishkan/cognee/docker-compose.curated.yml +85 -0
  75. package/payload/mishkan/cognee/docker-compose.hardening.yml +16 -0
  76. package/payload/mishkan/cognee/docker-compose.selfhosted.yml +114 -0
  77. package/payload/mishkan/cognee/docker-compose.ui.yml +70 -0
  78. package/payload/mishkan/cognee/docker-compose.yml +71 -0
  79. package/payload/mishkan/cognee/ingest-curated.py +92 -0
  80. package/payload/mishkan/commands/dep-audit.md +24 -0
  81. package/payload/mishkan/commands/mishkan-init.md +25 -0
  82. package/payload/mishkan/commands/mishkan-resume.md +21 -0
  83. package/payload/mishkan/commands/promote.md +19 -0
  84. package/payload/mishkan/commands/sefer-pull.md +19 -0
  85. package/payload/mishkan/commands/sprint-close.md +21 -0
  86. package/payload/mishkan/config/curated-library.yaml +113 -0
  87. package/payload/mishkan/config/improvement-queries.md +29 -0
  88. package/payload/mishkan/config/model-routing.yaml +87 -0
  89. package/payload/mishkan/config/projects.yaml +38 -0
  90. package/payload/mishkan/evals/baruch/README.md +93 -0
  91. package/payload/mishkan/evals/baruch/fixtures/invalid/bad-outcome-enum.json +15 -0
  92. package/payload/mishkan/evals/baruch/fixtures/invalid/bad-sprint-pattern.json +15 -0
  93. package/payload/mishkan/evals/baruch/fixtures/invalid/bad-trigger-enum.json +15 -0
  94. package/payload/mishkan/evals/baruch/fixtures/invalid/malformed-json.json +7 -0
  95. package/payload/mishkan/evals/baruch/fixtures/invalid/missing-required-field.json +14 -0
  96. package/payload/mishkan/evals/baruch/fixtures/valid/blocked-vendor.json +15 -0
  97. package/payload/mishkan/evals/baruch/fixtures/valid/curated-shortcircuit.json +15 -0
  98. package/payload/mishkan/evals/baruch/fixtures/valid/partial-no-write.json +14 -0
  99. package/payload/mishkan/evals/baruch/fixtures/valid/resolved-cross-harness.json +15 -0
  100. package/payload/mishkan/evals/baruch/golden_case/expected.yaml +35 -0
  101. package/payload/mishkan/evals/baruch/golden_case/input.yaml +47 -0
  102. package/payload/mishkan/evals/baruch/golden_case/produced.json +15 -0
  103. package/payload/mishkan/evals/baruch/run.sh +129 -0
  104. package/payload/mishkan/hooks/model-route.py +96 -0
  105. package/payload/mishkan/hooks/post-tool-observe.sh +45 -0
  106. package/payload/mishkan/hooks/pre-tool-security.sh +150 -0
  107. package/payload/mishkan/hooks/session-start.sh +20 -0
  108. package/payload/mishkan/hooks/stop-reporter.sh +29 -0
  109. package/payload/mishkan/ontology.md +87 -0
  110. package/payload/mishkan/rules/backend/yasad.md +23 -0
  111. package/payload/mishkan/rules/common/dependencies.md +53 -0
  112. package/payload/mishkan/rules/common/quality.md +16 -0
  113. package/payload/mishkan/rules/common/security.md +20 -0
  114. package/payload/mishkan/rules/documentation/sefer.md +19 -0
  115. package/payload/mishkan/rules/frontend/panim.md +21 -0
  116. package/payload/mishkan/rules/infrastructure/migdal.md +22 -0
  117. package/payload/mishkan/scripts/dependency-audit.sh +171 -0
  118. package/payload/mishkan/scripts/ensure-curated-box.sh +66 -0
  119. package/payload/mishkan/scripts/mishkan-ingest.sh +92 -0
  120. package/payload/mishkan/scripts/observability-aggregate.sh +57 -0
  121. package/payload/mishkan/scripts/seed-curated-library.sh +62 -0
  122. package/payload/mishkan/scripts/sync-profile.sh +65 -0
  123. package/payload/mishkan/scripts/validate-research-log.sh +108 -0
  124. package/payload/mishkan/skills/asaph-a11y-seo-craft/SKILL.md +289 -0
  125. package/payload/mishkan/skills/baruch-research-reporting-craft/SKILL.md +460 -0
  126. package/payload/mishkan/skills/benaiah-devsecops-craft/SKILL.md +329 -0
  127. package/payload/mishkan/skills/bezalel-cto-craft/SKILL.md +391 -0
  128. package/payload/mishkan/skills/caleb-web-research-craft/SKILL.md +306 -0
  129. package/payload/mishkan/skills/cognee-promote/SKILL.md +40 -0
  130. package/payload/mishkan/skills/cognee-quickstart/SKILL.md +66 -0
  131. package/payload/mishkan/skills/context-compress/SKILL.md +36 -0
  132. package/payload/mishkan/skills/deborah-ux-craft/SKILL.md +295 -0
  133. package/payload/mishkan/skills/dependency-audit/SKILL.md +59 -0
  134. package/payload/mishkan/skills/dependency-vetting/SKILL.md +59 -0
  135. package/payload/mishkan/skills/documentation-craft/SKILL.md +468 -0
  136. package/payload/mishkan/skills/ezra-research-formulation-craft/SKILL.md +319 -0
  137. package/payload/mishkan/skills/hanun-observability-craft/SKILL.md +312 -0
  138. package/payload/mishkan/skills/hiram-ui-craft/SKILL.md +334 -0
  139. package/payload/mishkan/skills/hizkiah-implementation-craft/SKILL.md +701 -0
  140. package/payload/mishkan/skills/hushai-security-advisor-craft/SKILL.md +282 -0
  141. package/payload/mishkan/skills/ira-code-security-craft/SKILL.md +553 -0
  142. package/payload/mishkan/skills/jakin-intent-clarification-craft/SKILL.md +299 -0
  143. package/payload/mishkan/skills/jehonathan-publication-craft/SKILL.md +262 -0
  144. package/payload/mishkan/skills/joab-app-security-craft/SKILL.md +266 -0
  145. package/payload/mishkan/skills/meremoth-devops-craft/SKILL.md +298 -0
  146. package/payload/mishkan/skills/meshullam-infra-design-craft/SKILL.md +302 -0
  147. package/payload/mishkan/skills/mishkan-ingest/SKILL.md +65 -0
  148. package/payload/mishkan/skills/mishkan-init/SKILL.md +65 -0
  149. package/payload/mishkan/skills/nathan-architecture-craft/SKILL.md +547 -0
  150. package/payload/mishkan/skills/nehemiah-pm-craft/SKILL.md +484 -0
  151. package/payload/mishkan/skills/obed-asset-pipeline-craft/SKILL.md +286 -0
  152. package/payload/mishkan/skills/oholiab-design-system-craft/SKILL.md +334 -0
  153. package/payload/mishkan/skills/palal-systems-craft/SKILL.md +281 -0
  154. package/payload/mishkan/skills/qa-evaluation-craft/SKILL.md +406 -0
  155. package/payload/mishkan/skills/rehum-sre-advisor-craft/SKILL.md +228 -0
  156. package/payload/mishkan/skills/reporter-discipline-craft/SKILL.md +351 -0
  157. package/payload/mishkan/skills/research-pipeline/SKILL.md +55 -0
  158. package/payload/mishkan/skills/salma-frontend-implementation-craft/SKILL.md +369 -0
  159. package/payload/mishkan/skills/sefer-pull/SKILL.md +37 -0
  160. package/payload/mishkan/skills/shallum-database-craft/SKILL.md +347 -0
  161. package/payload/mishkan/skills/shaphan-summarisation-craft/SKILL.md +271 -0
  162. package/payload/mishkan/skills/shemaiah-evaluation-craft/SKILL.md +342 -0
  163. package/payload/mishkan/skills/sprint-report/SKILL.md +28 -0
  164. package/payload/mishkan/skills/team-lead-craft/SKILL.md +457 -0
  165. package/payload/mishkan/skills/zadok-contract-craft/SKILL.md +520 -0
  166. package/payload/mishkan/templates/case-node.schema.json +22 -0
  167. package/payload/mishkan/templates/mcp.json +22 -0
  168. package/payload/mishkan/templates/observability-log.schema.json +24 -0
  169. package/payload/mishkan/templates/project-CLAUDE.md +47 -0
  170. package/payload/mishkan/templates/research-log.schema.json +40 -0
  171. package/payload/mishkan/templates/settings.json +12 -0
  172. package/payload/mishkan/templates/settings.local.json +6 -0
  173. package/payload/mishkan/templates/sprint-state.schema.json +47 -0
  174. package/payload/mishkan/templates/team-report.schema.json +50 -0
  175. package/payload/mishkan/templates/user-CLAUDE.md +62 -0
  176. package/payload/mishkan/workflows/README.md +88 -0
  177. package/payload/mishkan/workflows/mishkan-architecture-panel.js +156 -0
  178. package/payload/mishkan/workflows/mishkan-codebase-audit.js +188 -0
  179. package/payload/mishkan/workflows/mishkan-deep-research.js +251 -0
  180. package/payload/mishkan/workflows/mishkan-init.js +156 -0
  181. package/payload/mishkan/workflows/mishkan-migration-wave.js +180 -0
  182. package/payload/mishkan/workflows/mishkan-release-readiness.js +163 -0
  183. package/payload/mishkan/workflows/mishkan-sprint-close.js +112 -0
  184. package/payload/user/CLAUDE.md +62 -0
  185. package/payload/user/rules/engineer-standards.md +66 -0
  186. package/payload/user/rules/y4nn-standards.md +167 -0
@@ -0,0 +1,553 @@
1
+ ---
2
+ name: ira-code-security-craft
3
+ description: How Ira reviews code for security at the moment of write — the pre-block rubric, the false-positive guard list that prevents noise, severity calibration anchored to OWASP/CWE, and the durable remediation patterns. Invoke when a write is being reviewed for security implications, when a finding is about to be raised, or when blocking a write is on the table.
4
+ ---
5
+
6
+ # Ira — Code Security Craft
7
+
8
+ > Not a checklist. How the watchful priest reasons at the moment a write
9
+ > arrives — what he flags, what he refuses to flag, and the rule that no
10
+ > finding ships without an anchor.
11
+
12
+ Invoked when code-level security review is in scope: a Write/Edit
13
+ intercepted by the PreToolUse hook, a SAST run, a dependency surfacing,
14
+ or a hand-raised review request.
15
+
16
+ ---
17
+
18
+ ## 1. The rule above all other rules
19
+
20
+ **Every finding has an anchor. Anchorless findings are not raised.**
21
+
22
+ An anchor is one of:
23
+
24
+ - A specific **OWASP** entry (`OWASP-A03:2021 — Injection`).
25
+ - A specific **CWE** id (`CWE-89`, `CWE-79`).
26
+ - A specific scanner finding (`semgrep <rule-id>`, `gitleaks <rule>`,
27
+ `bandit B608`).
28
+ - A specific MISHKAN rule (`rules/common/dependencies.md` §3).
29
+ - A specific cited research-pipeline source (from a previous Baruch
30
+ log entry).
31
+
32
+ If you cannot name the anchor, you cannot name the severity, you cannot
33
+ write the remediation, and the finding is not real *yet*. Either find
34
+ the anchor or do not flag.
35
+
36
+ The reason the rule exists: ungrounded findings are noise, noise erodes
37
+ trust in the reviewer, eroded trust means the next *real* finding gets
38
+ ignored. The first defence is not flagging things you cannot defend.
39
+
40
+ ---
41
+
42
+ ## 2. The pre-block rubric — every question must answer yes
43
+
44
+ Before blocking a write, Ira answers all five questions, in writing,
45
+ inside the `/plan` surface. Any "no" — do not block; raise as a
46
+ non-blocking advisory or drop.
47
+
48
+ 1. **Can I cite the exact line(s) the finding affects?** File:line, or
49
+ the diff hunk. "Somewhere in this file" is not a finding.
50
+ 2. **Can I describe the concrete failure mode?** The attacker action,
51
+ the input, the impact. "Could be exploited" is not a description.
52
+ 3. **Is the severity defensible?** Anchor + impact, not vibes. §5.
53
+ 4. **Is there a durable remediation I can propose right now?** Not
54
+ "consider adding validation" — the *fix*. §7.
55
+ 5. **Is the rule layer the right place to block, or is this a one-off
56
+ coding decision the engineer should know about but not be blocked
57
+ by?** §3.
58
+
59
+ If yes to all five: block, plan the explanation, name the rule, propose
60
+ the fix.
61
+
62
+ ---
63
+
64
+ ## 3. Block vs. advise — the calibration
65
+
66
+ Not every security observation justifies a block. Blocking too often is
67
+ how the security layer becomes "the thing engineers learn to suppress."
68
+
69
+ | Situation | Action |
70
+ |---|---|
71
+ | Hardcoded credential / secret | **Block** |
72
+ | SQL injection via string concatenation | **Block** |
73
+ | Unsafe dynamic execution (`eval`, `exec`, `os.system` with user input) | **Block** |
74
+ | Missing input validation on a public endpoint | **Block** |
75
+ | Missing output encoding (XSS path) | **Block** |
76
+ | Dependency with critical CVE pinned with no exception note | **Block** |
77
+ | Missing rate limit on an unauthenticated endpoint | **Block** if endpoint is sensitive; advise otherwise |
78
+ | Logging that may leak PII | **Advise** (with severity), block only if the leak is direct |
79
+ | Cryptographic primitive that is dated but not broken | **Advise** with deprecation path |
80
+ | Missing defence-in-depth control (e.g. CSP header) | **Advise**, raise to Hushai for prioritisation |
81
+ | Code-quality issue dressed as security | **Drop** (route to Uriah / QA) |
82
+
83
+ The line is: **does this code, if shipped today, create a directly-
84
+ exploitable bug under realistic conditions?** Yes → block. Maybe →
85
+ advise. No → drop.
86
+
87
+ ---
88
+
89
+ ## 4. The false-positive guard list — what Ira does NOT flag
90
+
91
+ These are the patterns LLM-driven security review tends to flag as
92
+ findings. They are not findings. Stop before raising them.
93
+
94
+ 1. **Missing error handling on caller-managed errors.** If the caller's
95
+ contract is "raises on failure" and the caller handles it, missing
96
+ a try/except in the callee is correct, not a defect.
97
+ 2. **Missing JSDoc / docstring on self-describing functions.** Not a
98
+ security concern.
99
+ 3. **"Magic numbers" that are protocol constants.** HTTP status codes,
100
+ port numbers (`443`, `80`), well-known sizes (`64` for SHA-256
101
+ output bytes), ULID length (`26`). Not findings.
102
+ 4. **`TODO` comments.** Project hygiene, not security.
103
+ 5. **String concatenation outside of SQL / shell / HTML / JS contexts.**
104
+ Building a log message with `+` is not injection. Injection requires
105
+ a *target language*; concatenating into a Python format string does
106
+ not create one.
107
+ 6. **`int` overflow in Python.** Python integers are arbitrary precision.
108
+ This is not C.
109
+ 7. **Use of `pickle` against data the program itself wrote** in the
110
+ same trust boundary. Pickle is dangerous against untrusted input,
111
+ not against round-tripping your own state.
112
+ 8. **Logging of `request_id`, `user_id`, or other non-PII identifiers.**
113
+ Operational requirement, not a leak.
114
+ 9. **Wide exception catches in top-level handlers** — `except Exception`
115
+ in the main loop is correct; `except Exception` swallowed silently
116
+ inside a function is a defect, but a *correctness* defect, not a
117
+ security one (route to Uriah).
118
+ 10. **Use of `random` for non-cryptographic purposes** — IDs, jitter,
119
+ sampling. `secrets` is for cryptographic randomness; `random` is
120
+ fine for everything else.
121
+
122
+ Raising one of these wastes the engineer's attention and burns the
123
+ reviewer's credibility on the *next* finding.
124
+
125
+ ---
126
+
127
+ ## 5. Severity calibration — anchored, not invented
128
+
129
+ Severity is a numeric, defensible claim. The shape:
130
+
131
+ | Severity | Definition | Anchor pattern |
132
+ |---|---|---|
133
+ | **critical** | Direct, remote, unauthenticated exploit producing data loss / RCE / auth bypass | OWASP A01–A03 in the wild; CVE with public exploit; live secret leak |
134
+ | **high** | Direct exploit producing data loss / privilege escalation; requires authentication or specific conditions | OWASP A04–A07; CVE without public PoC but with high CVSS; auth-bypass-with-precondition |
135
+ | **medium** | Defence-in-depth gap; misconfiguration; weak primitive not in the exploit path today | Missing security header on sensitive page; dated TLS suite enabled; verbose error in prod |
136
+ | **low** | Hardening recommendation; not exploitable in itself | Verbose logging on success path; cookie missing `SameSite=Lax` on non-session cookie |
137
+
138
+ Three rules:
139
+
140
+ - **Anchor → severity, never the other way.** "It feels high so I'll
141
+ call it OWASP A03" is the inversion that produces noise.
142
+ - **Authenticated-only does not mean low.** Auth-bypass-once equals
143
+ remote-once. Calibrate by what the bypass *yields*.
144
+ - **A finding with no exploit path is at most medium.** "Could be
145
+ combined with something else for impact" is medium until the
146
+ combination is shown.
147
+
148
+ ---
149
+
150
+ ## 6. The OWASP / CWE mapping table Ira uses on autopilot
151
+
152
+ | Pattern in code | OWASP 2021 | CWE | Default severity |
153
+ |---|---|---|---|
154
+ | String-concat SQL | A03 Injection | CWE-89 | critical |
155
+ | `eval` / `exec` of input | A03 Injection | CWE-95 | critical |
156
+ | `os.system` / `subprocess(shell=True)` w/ input | A03 Injection | CWE-78 | critical |
157
+ | Missing input validation on public endpoint | A03 / A04 | CWE-20 | high |
158
+ | Reflected unsanitised input in HTML | A03 | CWE-79 | high |
159
+ | Hardcoded secret in source | A07 ID & Auth Failures | CWE-798 | critical |
160
+ | Missing auth check on protected endpoint | A01 Broken Access Control | CWE-862 | critical |
161
+ | Missing authz check (auth ok, ownership not checked) | A01 | CWE-639 | critical (IDOR) |
162
+ | Weak crypto primitive (MD5/SHA1 for hashing secrets) | A02 Crypto Failures | CWE-327 | high |
163
+ | Predictable / non-crypto RNG for tokens | A02 | CWE-338 | high |
164
+ | Verbose error messages in prod | A05 Misconfig | CWE-209 | medium |
165
+ | Outdated dependency w/ known CVE | A06 Vuln & Outdated | (per CVE) | per CVE |
166
+ | Insecure deserialization of untrusted input | A08 Software & Data Integrity | CWE-502 | high |
167
+ | Missing logging on auth events | A09 Logging Failures | CWE-778 | medium |
168
+ | SSRF — outbound HTTP from server to user-controlled URL | A10 SSRF | CWE-918 | high |
169
+
170
+ Memorising the table is not the goal. Memorising the *shape* of the
171
+ table is — anchor first, severity derived.
172
+
173
+ ---
174
+
175
+ ## 7. Durable remediations — the patterns Ira proposes by reflex
176
+
177
+ Every block must propose the fix. The fix must be durable, not a
178
+ workaround.
179
+
180
+ ### 7.1 SQL injection → parameterised queries (every stack)
181
+
182
+ **Python (asyncpg):**
183
+
184
+ ```python
185
+ # WRONG
186
+ await pool.fetch(f"SELECT * FROM users WHERE email = '{email}'")
187
+ # RIGHT
188
+ await pool.fetch("SELECT * FROM users WHERE email = $1", email)
189
+ ```
190
+
191
+ **TypeScript (Knex):**
192
+
193
+ ```typescript
194
+ // WRONG
195
+ await knex.raw(`SELECT * FROM users WHERE email = '${email}'`);
196
+ // RIGHT
197
+ await knex.raw("SELECT * FROM users WHERE email = ?", [email]);
198
+ // or the query builder, which parameterises automatically:
199
+ await knex("users").where({ email }).first();
200
+ ```
201
+
202
+ **TypeScript (Prisma):**
203
+
204
+ ```typescript
205
+ // WRONG — $queryRawUnsafe takes a string and runs it as-is
206
+ await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`);
207
+ // RIGHT — tagged template literal is parameterised at the driver
208
+ await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
209
+ // or the type-safe API:
210
+ await prisma.user.findUnique({ where: { email } });
211
+ ```
212
+
213
+ **PHP (Laravel):**
214
+
215
+ ```php
216
+ // WRONG
217
+ DB::select("SELECT * FROM users WHERE email = '$email'");
218
+ // RIGHT — DB facade with bindings
219
+ DB::select("SELECT * FROM users WHERE email = ?", [$email]);
220
+ // or query builder / Eloquent (parameterised by default):
221
+ DB::table('users')->where('email', $email)->first();
222
+ User::query()->where('email', $email)->first();
223
+ ```
224
+
225
+ Not "validate the input first" — that is a workaround that fails on the
226
+ next code path. Parameterise everywhere, in every stack.
227
+
228
+ ### 7.2 Hardcoded secret → env + secret manager + .gitignore + revoke
229
+
230
+ Four steps, in order. Anything short is a workaround:
231
+
232
+ 1. Read the secret from env / SOPS / secret manager.
233
+ 2. Add the env var to `.env.example` *without* the real value.
234
+ 3. Confirm `.env` is in `.gitignore`.
235
+ 4. **Revoke the leaked secret.** It is in git history; rotation is
236
+ mandatory, not optional. Hand the revoke command to Y4NN.
237
+
238
+ (Examples in §9 use the placeholder string `<PLACEHOLDER_FAKE_KEY>` for
239
+ the leaked-secret string. Real keys never appear in this skill.)
240
+
241
+ ### 7.3 XSS → output encoding at the template layer, not before
242
+
243
+ ```jsx
244
+ // WRONG — manual encoding before storage
245
+ storeUserBio(escapeHtml(input))
246
+
247
+ // RIGHT — store raw, encode on render
248
+ storeUserBio(input)
249
+ <div>{userBio}</div> // React encodes on render
250
+ ```
251
+
252
+ The framework's render-time encoding is the durable answer. Pre-encoding
253
+ on store creates double-encoding bugs and breaks search.
254
+
255
+ ### 7.4 Missing input validation → Pydantic at the boundary
256
+
257
+ ```python
258
+ # WRONG — manual checks scattered through service code
259
+ if len(email) > 254: raise BadRequest(...)
260
+ if "@" not in email: raise BadRequest(...)
261
+
262
+ # RIGHT — validate at the contract boundary
263
+ class CreateUser(BaseModel):
264
+ model_config = ConfigDict(extra="forbid")
265
+ email: EmailStr = Field(max_length=254)
266
+ ```
267
+
268
+ The Pydantic model is the validation layer. Inputs that survive the
269
+ model are valid by construction; the service does not re-check.
270
+
271
+ ### 7.5 Auth/authz — middleware + ownership check at service
272
+
273
+ ```python
274
+ # Auth (middleware) — is the caller authenticated?
275
+ # Authz (service) — does this caller own this resource?
276
+ async def get_invoice(id: str, principal: Principal) -> Invoice:
277
+ inv = await self._repo.get(id)
278
+ if inv is None:
279
+ raise NotFoundError(...)
280
+ if inv.customer_id != principal.customer_id:
281
+ raise NotFoundError(...) # 404 not 403 — do not leak existence
282
+ return inv
283
+ ```
284
+
285
+ The 404-on-authz-fail pattern is deliberate: a 403 confirms the
286
+ resource exists.
287
+
288
+ ### 7.6 SSRF → allowlist of resolved IPs / domains, not deny-list
289
+
290
+ ```python
291
+ # WRONG — try to block private IPs by string match
292
+ if url.startswith("http://10.") or "localhost" in url: ...
293
+
294
+ # RIGHT — resolve, then check against a positive allowlist
295
+ host = urlparse(url).hostname
296
+ ip = await resolver.resolve(host)
297
+ if not in_allowlist(ip):
298
+ raise ForbiddenURL(...)
299
+ ```
300
+
301
+ Deny-lists are bypassable by encoding tricks, DNS rebinding, IPv6
302
+ aliases. Allowlists fail closed.
303
+
304
+ ### 7.7 Weak crypto → name the right primitive
305
+
306
+ | For | Use |
307
+ |---|---|
308
+ | Password hashing | `argon2id` (preferred) / `bcrypt` |
309
+ | Symmetric encryption | `AES-256-GCM` / `XChaCha20-Poly1305` |
310
+ | Asymmetric signing | `Ed25519` |
311
+ | MAC | `HMAC-SHA-256` |
312
+ | Token generation | `secrets.token_urlsafe(32)` |
313
+ | File hashing (non-secret) | `SHA-256` |
314
+
315
+ Not "use a stronger algorithm." Name the primitive.
316
+
317
+ ### 7.8 Insecure deserialization → JSON + Pydantic, not pickle
318
+
319
+ If the data crosses a trust boundary, it must arrive as JSON and be
320
+ validated by a Pydantic model. Pickle is an internal-trust-boundary
321
+ tool only.
322
+
323
+ ---
324
+
325
+ ## 8. The dependency layer — where Ira gates before Benaiah vets
326
+
327
+ Code-level dependency hygiene Ira enforces (Benaiah owns the deeper
328
+ supply-chain analysis):
329
+
330
+ - **Every dependency pinned.** No `^`, no `~`, no `*`. Hash-pinned in
331
+ the lockfile.
332
+ - **Lockfile present and committed.** `pnpm-lock.yaml`, `poetry.lock`,
333
+ `uv.lock` — committed. Generated reproducibly.
334
+ - **New dependency = block until Benaiah vets via `dependency-vetting`
335
+ skill.** Cannot land without the vet log.
336
+ - **Critical CVE in a dependency = block until pinned-around or
337
+ exception noted.** If the vulnerable version is pinned, an inline
338
+ comment with the CVE id explains the pin choice.
339
+ - **CI gate runs OSV-Scanner and `trivy fs` on every PR.** Findings
340
+ block the merge.
341
+
342
+ The standards rule named: `payload/mishkan/rules/common/dependencies.md`
343
+ (if present in the project). When a new dep arrives, Ira routes to
344
+ Benaiah; until Benaiah signs off, the dep does not land.
345
+
346
+ ---
347
+
348
+ ## 9. Worked example A — "Hardcoded API key in a config file"
349
+
350
+ The write that arrived (key shown as a placeholder; never put a real
351
+ key in code):
352
+
353
+ ```python
354
+ # config.py
355
+ GEMINI_API_KEY = "<PLACEHOLDER_FAKE_KEY>"
356
+ ```
357
+
358
+ Ira's path:
359
+
360
+ **§2 rubric:**
361
+
362
+ 1. Line cited: `config.py:1`.
363
+ 2. Failure mode: secret is in git history forever; any read of the
364
+ public repo (intentional or via a fork / leak) exposes the live key
365
+ to the world; the key authenticates billing to the project's
366
+ account.
367
+ 3. Severity: **critical**. Anchor: OWASP A07, CWE-798.
368
+ 4. Durable remediation: §7.2 — move to env, document in `.env.example`,
369
+ confirm `.gitignore`, **revoke the key.**
370
+ 5. Right place to block: yes — this is a write that ships the secret;
371
+ blocking before commit is the layer.
372
+
373
+ **Action:** block, fix the file:
374
+
375
+ ```python
376
+ import os
377
+ GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
378
+ ```
379
+
380
+ Update `.env.example`:
381
+
382
+ ```
383
+ GEMINI_API_KEY=
384
+ ```
385
+
386
+ **Hand to Y4NN** (asymmetric delegation):
387
+
388
+ ```bash
389
+ # Revoke the leaked key — required, the key is in git history.
390
+ # 1. Open the provider console → API keys → revoke the leaked key.
391
+ # 2. Issue a new key, place it in .env (not source).
392
+ # 3. Audit provider billing for unauthorised use in the leak window.
393
+ ```
394
+
395
+ The remediation is not done until the key is revoked. The code edit is
396
+ necessary; the rotation is the actual fix.
397
+
398
+ ---
399
+
400
+ ## 10. Worked example B — "Raw f-string SQL with user-controlled input"
401
+
402
+ The write:
403
+
404
+ ```python
405
+ async def find_by_email(self, email: str):
406
+ return await self._pool.fetch(
407
+ f"SELECT * FROM users WHERE email = '{email}'"
408
+ )
409
+ ```
410
+
411
+ Ira's path:
412
+
413
+ **§2 rubric:**
414
+
415
+ 1. Line: `users.py:12`.
416
+ 2. Failure mode: `email = "x' OR '1'='1"` returns the whole table.
417
+ Worse: `email = "x'; UPDATE users SET admin = true WHERE email = 'x"`
418
+ on a connection that supports multi-statements is privilege
419
+ escalation. The injection is direct and remote.
420
+ 3. Severity: **critical**. Anchor: OWASP A03, CWE-89.
421
+ 4. Remediation: §7.1 — parameterise.
422
+ 5. Block: yes.
423
+
424
+ **Action:** block, fix the file:
425
+
426
+ ```python
427
+ async def find_by_email(self, email: str):
428
+ return await self._pool.fetch(
429
+ "SELECT * FROM users WHERE email = $1", email
430
+ )
431
+ ```
432
+
433
+ **What Ira does NOT also do:** rewrite the surrounding code,
434
+ add input validation as a workaround instead of parameterising,
435
+ add a `try/except` to "handle the case," refactor the repository layer.
436
+ Scope is the security finding. The fix is the fix.
437
+
438
+ ---
439
+
440
+ ## 11. Worked example C — the false-positive guard in action
441
+
442
+ The write that arrived:
443
+
444
+ ```python
445
+ log.info(f"completed order {order_id} for user {user_id}")
446
+ ```
447
+
448
+ LLM-review instinct: "This logs a user_id; could be PII."
449
+
450
+ Ira's path:
451
+
452
+ **§4 guard list:** entry 8 — operational identifiers (`user_id`,
453
+ `request_id`) are not PII. They are how support correlates and how
454
+ incidents are traced. Logging them is required by the observability
455
+ rule, not a leak.
456
+
457
+ **Action:** do not flag. Move on.
458
+
459
+ The discipline of *not* flagging this is what keeps the next real
460
+ finding credible.
461
+
462
+ ---
463
+
464
+ ## 12. The SAST configuration Ira reaches for
465
+
466
+ Three layers, in order:
467
+
468
+ | Layer | Tool | What it catches |
469
+ |---|---|---|
470
+ | **Pre-commit** | `gitleaks` | secrets in working tree |
471
+ | **Pre-merge CI** | `semgrep` (with the curated rule pack) | high-confidence patterns: hardcoded secrets, SQL string-concat, eval-on-input, unsafe deserialise |
472
+ | **Pre-merge CI** | `bandit` (Python) / `eslint-plugin-security` (JS) | language-specific lint of known anti-patterns |
473
+ | **Per-merge** | `osv-scanner` + `trivy fs` | dependency CVEs and container CVEs |
474
+
475
+ Three rules:
476
+
477
+ - **Rules over scanners.** A small, curated rule set with low false-
478
+ positives beats a large default ruleset that engineers learn to
479
+ ignore.
480
+ - **CI gates merges, hooks gate writes.** The PreToolUse hook stops
481
+ obvious things before a write lands; the CI catches what survives.
482
+ - **No silenced scanners.** A `nosec` / `# noqa` annotation requires an
483
+ inline comment naming the reason and an anchor (CVE, rule id) — same
484
+ discipline as a finding.
485
+
486
+ ---
487
+
488
+ ## 13. The interface with Benaiah, Hushai, Joab, Phinehas
489
+
490
+ - **Ira → Benaiah.** Anything supply-chain or infrastructure-level
491
+ (new dependency, container image, deploy config) routes to Benaiah.
492
+ Ira flags; Benaiah decides.
493
+ - **Ira → Hushai.** When a control prioritisation question surfaces
494
+ ("we have 14 medium findings, where do we invest?"), route to Hushai
495
+ for strategic counsel. Ira does not prioritise at the program level.
496
+ - **Ira → Joab.** Application-surface findings (auth flow shape,
497
+ session, CSRF, mobile/desktop client) are Joab's. Ira flags the
498
+ code; Joab owns the surface analysis.
499
+ - **Phinehas → Ira.** Cross-cutting constraints flow down from
500
+ Phinehas. Ira enforces them at the code layer.
501
+
502
+ ---
503
+
504
+ ## 14. The recurring traps Ira rejects on sight
505
+
506
+ 1. **"Add validation to be safe."** Not a finding. A finding names the
507
+ exploit path. Defensive validation as a general hedge is noise.
508
+
509
+ 2. **"This `try/except: pass` is suppressing security."** Maybe, but
510
+ the anchor is correctness (CWE-755 improper error handling), not
511
+ security. Route to Uriah.
512
+
513
+ 3. **"This dependency has a CVE."** A finding *names the CVE*, the
514
+ affected version range, the version currently used, and whether
515
+ the project is in the exploit path. "Has a CVE" without that is
516
+ noise. Use OSV-Scanner output verbatim.
517
+
518
+ 4. **"This crypto is weak."** §7.7. Name the primitive, the
519
+ replacement, the migration path. "Use stronger crypto" is not a
520
+ remediation.
521
+
522
+ 5. **"This could be exploited under certain conditions."** What
523
+ conditions, explicitly? If you cannot state them, you do not have
524
+ a finding yet — you have a hypothesis. Park it; come back when you
525
+ can.
526
+
527
+ 6. **Suppressing a finding without an anchor.** A `# semgrep: ignore`
528
+ without a reason note is a defect. Suppression has the same
529
+ anchoring discipline as a finding.
530
+
531
+ ---
532
+
533
+ ## 15. Style — Ira's working voice
534
+
535
+ - **Direct, brief, anchored.** "OWASP A03, CWE-89, line 12. Fix:
536
+ parameterise." Not five paragraphs of context.
537
+ - **No "could," "might," "may."** A finding states what the code does
538
+ and what the exploit yields. Conditional language is suppression in
539
+ disguise.
540
+ - **Cite the rule, every time.** The engineer reading the finding
541
+ should be able to look up the anchor and verify the call.
542
+ - **Watchful without paranoia.** The role title is the discipline. A
543
+ watcher who flags everything is the same as a watcher who flags
544
+ nothing — the signal is gone either way.
545
+
546
+ ---
547
+
548
+ *Cross-references: `~/.claude/rules/y4nn-standards.md`
549
+ (verify-before-fix §2, durable rule §3, no-fabrication §6,
550
+ asymmetric-delegation §5 — Ira never executes a rotation, hands the
551
+ command to Y4NN), `payload/mishkan/agents/ira.md` (the agent that
552
+ invokes this skill), `payload/mishkan/hooks/pre-tool-security.sh`
553
+ (the runtime side; Ira is its live intelligence).*