
Threat Patch
- 170 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
threat-patch: A skill for development. This provides functionality for development workflows.
Key points
- threat-patch
Threat Patch by the numbers
- 170 all-time installs (skills.sh)
- +5 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #2,287 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill threat-patchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 170 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I use threat-patch for development tasks?
Use threat-patch for development tasks
Who is it for?
Best when you're working on backend & apis and need structured help with threat-patch.
Skip if: Teams with no backend & apis needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to use threat-patch for development tasks, or when threat-patch: a skill for development. this provides functionality for development workflows.
What you get
Structured output aligned to threat-patch: threat-patch.
Files
Threat Patch
Reads security findings and produces minimal, surgical code patches with structured documentation. Fixes are code-grounded — each patch targets specific files and functions identified in the finding. Output includes a summary, validation steps, and the code changes.
When to Apply
- User provides a
findings.json(from threat-model) and wants fixes - User provides a Codex security findings CSV and wants fixes
- User has a THREAT-MODEL.md and wants to remediate identified risks
- User describes a specific vulnerability and wants a patch
- Reviewing security scanner output and needs actionable fixes
- After a security audit, turning findings into code changes
Input Sources (priority order)
| Source | What It Provides | How to Use |
|---|---|---|
| findings.json (from threat-model) | Structured findings with data flow traces, systemic groupings, exploit chains, and severity ratings | Read directly — richest input, already triaged and grouped |
| Codex CSV | Title, description, severity, relevant_paths per finding | Run scripts/parse-findings.sh <csv-path> to extract structured output |
| THREAT-MODEL.md | Human-readable threat model | Extract findings from Criticality Calibration section |
| Inline description | User describes a specific vulnerability | Parse from conversation context |
When findings.json is available, it's the preferred input — it includes data flow traces (entry → chain → sink) that directly inform where to apply fixes, and systemic groupings that suggest centralized fixes over individual patches.
Workflow Overview
1. Ingest Findings → Read findings.json / CSV / descriptions
2. Triage & Group → Sort by severity, use systemic groupings if available
3. For each finding:
a. Read Code → Open relevant_paths, understand the pattern
b. Confirm → Verify issue is still present in HEAD
c. Design Fix → Determine minimal fix approach
d. Implement → Write the code changes
e. Document → Summary + Validation + Attack-path (if needed)
f. Test → Run relevant tests
4. Output → Per-patch deliverable with summary and diff
5. Update State → Mark patched findings in findings.json (if present)How to Use
1. Read workflow for the detailed patching methodology at each step 2. Read fix patterns when designing fixes — common patterns by vulnerability class 3. Read output format for the documentation template per patch 4. If input is findings.json: read it directly — it's already structured 5. If input is Codex CSV: run scripts/parse-findings.sh <csv-path> to extract structured output
Key Principles
- Minimal diff: Fix the vulnerability, don't refactor surrounding code. The smallest correct patch is the best patch
- Centralize over duplicate: When multiple code paths share the same vulnerability pattern, extract a shared helper rather than patching each site independently
- Explicit error paths: Add specific error types for rejected inputs with clear operator feedback, not silent failures or generic errors
- Confirm before fixing: Always verify the finding is still present in HEAD — code may have moved or been refactored since the finding was detected
- User approval before edits: Present the fix design (files to change, approach) and wait for approval before modifying source code. Hooks gate Edit/Write tool calls for additional safety
- Document even failures: When a fix can't be tested due to environment limitations, document the test command and the limitation
Guardrails
This skill modifies source code. Safety measures:
- PreToolUse hooks on Edit and Write tools prompt for confirmation before each file change
- Confirmation gate in the workflow between fix design and implementation
- Revert path: Without commits (default), use
git checkout -- <files>to undo. With commits, usegit revert
Output Modes
Code patch — when a fix is implemented:
- Summary of what was confirmed and what the fix does
- Testing section with build/test commands
- The actual code changes
Analysis only — when the fix needs user decision or architectural changes:
- Summary of what was confirmed
- Validation checklist
- Attack-path analysis (path, likelihood, impact, assumptions, controls, blindspots)
References
| File | When to Read |
|---|---|
| references/workflow.md | Before starting — detailed approach for each patching phase |
| references/fix-patterns.md | When designing fixes — patterns by vulnerability class |
| references/output-format.md | When documenting — templates for both output modes |
{
"findings_json_path": "findings.json",
"findings_csv_path": "",
"threat_model_path": "THREAT-MODEL.md",
"commit_patches": false,
"branch_prefix": "security/",
"_setup_instructions": {
"findings_json_path": "Path to findings.json from threat-model (preferred input, default: findings.json in project root)",
"findings_csv_path": "Path to a Codex security findings CSV (alternative input)",
"threat_model_path": "Path to THREAT-MODEL.md for cross-referencing (default: THREAT-MODEL.md in project root)",
"commit_patches": "Whether to create git commits for each patch (default: false — just apply edits)",
"branch_prefix": "Prefix for security fix branches when committing (default: security/)"
}
}
Gotchas
parse-findings.sh requires python3
The CSV parser script uses Python's csv module for correct handling of quoted fields with embedded commas. If python3 is not available, read the CSV directly with the Read tool instead. Added: 2026-03-28
Confirm the finding's commit hash matches the current codebase
Findings reference specific commit hashes. If the codebase has been significantly refactored since the finding was detected, the vulnerable code may have moved, been renamed, or been removed. Always grep for the vulnerable pattern rather than assuming the exact file:line from the finding. Added: 2026-03-28
Grouped fixes need careful testing of all affected call sites
When extracting a shared helper to fix multiple duplicate vulnerabilities, test every call site — not just the first one. The helper's interface may not fit all callers identically, especially if they handle errors differently. Added: 2026-03-28
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Security patch: about to edit a source file. Review the proposed change above before approving.'",
"timeout": 5
}]
},
{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Security patch: about to write a file. Review the proposed content before approving.'",
"timeout": 5
}]
}
]
}
}
{
"version": "1.0.3",
"organization": "pproenca",
"technology": "Security Patch Remediation",
"discipline": "composition",
"type": "automation",
"date": "March 2026",
"abstract": "Reads security findings from Codex CSV, threat models, or individual descriptions and produces minimal, surgical code patches. Triages by severity, groups related findings, confirms vulnerabilities in HEAD, and generates fixes with structured documentation including summary, validation steps, and diffs.",
"references": [
"https://owasp.org/www-community/Threat_Modeling",
"https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html"
]
}
Fix Patterns by Vulnerability Class
Standard fix approaches for common vulnerability types. Match the finding to a pattern, then adapt to the specific codebase's conventions.
Input Validation — Untrusted Identifiers
Vulnerability: User-supplied identifiers (PIDs, bundle IDs, UDIDs) used without verifying they belong to the expected domain.
Fix pattern: Validate the identifier against an allowlist of valid values before use.
// Before: trusts raw --pid
let resolvedPID = pid
// After: validates PID is a running simulator app
let running = try await getRunningSimulatorApps()
guard running.values.contains(pid) else {
throw Error.pidNotSimulatorApp(pid)
}Key decisions:
- Validate at the entry point (CLI command handler), not deep in the service layer
- When multiple commands share the same validation, extract a shared helper
- Add a specific error type for rejected identifiers with clear feedback
Path Traversal — Unsanitized Path Components
Vulnerability: User-supplied strings (filenames, bundle IDs, domain names) concatenated into filesystem paths without sanitization.
Fix pattern: Validate the component contains no path separators or traversal sequences, or normalize and contain the resolved path within an allowed root.
// Option A: Reject invalid components
guard !component.contains("/") && !component.contains("..") else {
throw Error.invalidPathComponent(component)
}
// Option B: Normalize and contain within root
let resolved = URL(fileURLWithPath: root)
.appendingPathComponent(component).standardized
guard resolved.path.hasPrefix(root.path) else {
throw Error.pathEscapesRoot(component)
}Key decisions:
- Prefer Option A (reject) for identifiers that should never contain path separators (bundle IDs, UDIDs, preference domains)
- Use Option B (normalize and contain) for user-supplied paths that may legitimately be nested
- Apply at the trust boundary where the untrusted string enters path construction
Predictable Temporary Files — Symlink Races
Vulnerability: Writing to fixed paths under /tmp without unique naming or symlink protection, enabling symlink attacks on multi-user systems.
Fix pattern: Use unique per-run directories with restrictive permissions.
// Before: predictable path
let outputPath = "/tmp/tool-name/output.json"
// After: unique temp directory
let tempDir = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: tempDir,
withIntermediateDirectories: true,
attributes: [.posixPermissions: 0o700])
let outputPath = tempDir.appendingPathComponent("output.json")
// Clean up after use
defer { try? FileManager.default.removeItem(at: tempDir) }When multiple temp files share a pattern: Create a single secure temp directory helper and use it everywhere. This is the centralization principle — one fix, multiple call sites.
func secureTemporaryDirectory(prefix: String) throws -> URL {
let dir = FileManager.default.temporaryDirectory
.appendingPathComponent("\(prefix)-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: dir,
withIntermediateDirectories: true,
attributes: [.posixPermissions: 0o700])
return dir
}XSS in Generated HTML — Unsafe Embedding
Vulnerability: Untrusted data embedded in HTML without context-appropriate escaping.
Fix patterns by context:
Script tag embedding
<!-- Before: JSON in executable script tag -->
<script>const R = UNSAFE_JSON;</script>
<!-- After: JSON in data tag, parsed separately -->
<script type="application/json" id="report-data">ESCAPED_JSON</script>
<script>const R = JSON.parse(document.getElementById('report-data').textContent);</script>Additionally, escape </script> sequences in the JSON payload:
let safe = jsonString.replacingOccurrences(of: "</script>", with: "<\\/script>")HTML attribute injection
// Before: only escapes < > &
func esc(_ s: String) -> String {
s.replacingOccurrences(of: "&", with: "&")
.replacingOccurrences(of: "<", with: "<")
.replacingOccurrences(of: ">", with: ">")
}
// After: also escapes quotes for attribute contexts
func esc(_ s: String) -> String {
s.replacingOccurrences(of: "&", with: "&")
.replacingOccurrences(of: "<", with: "<")
.replacingOccurrences(of: ">", with: ">")
.replacingOccurrences(of: "\"", with: """)
.replacingOccurrences(of: "'", with: "'")
}innerHTML injection
// Before: innerHTML with untrusted data
element.innerHTML = untrustedLabel;
// After: textContent for text, or sanitize for rich content
element.textContent = untrustedLabel;
// Or if HTML rendering is needed:
element.innerHTML = DOMPurify.sanitize(untrustedContent);Unbounded Allocation — Resource Exhaustion
Vulnerability: Allocating memory based on untrusted size values without upper bounds.
Fix pattern: Enforce a maximum size before allocation.
// Before: trusts payload size
size_t bufSize = untrusted_size;
void *buf = malloc(bufSize);
// After: cap at reasonable maximum
#define MAX_PAYLOAD_SIZE (5 * 1024 * 1024) // 5 MB
if (untrusted_size > MAX_PAYLOAD_SIZE) {
*error_text = strdup("Payload exceeds maximum allowed size");
return NULL;
}
void *buf = malloc(untrusted_size);For decompression: Cap the output buffer size independently of the input's declared size.
// Before: trusts gzip ISIZE
let outputSize = Int(isize)
var buffer = [UInt8](repeating: 0, count: outputSize)
// After: cap decompressed size
let maxDecompressed = 50 * 1024 * 1024 // 50 MB
let outputSize = min(Int(isize), maxDecompressed)
guard outputSize <= maxDecompressed else {
throw DecompressionError.payloadTooLarge(declared: Int(isize), max: maxDecompressed)
}Key decisions:
- Choose the cap based on realistic maximum sizes for the data type
- Document the cap constant with a comment explaining the rationale
- Fail with a clear error message, not a silent truncation
Use-After-Free — Resource Lifetime
Vulnerability: Resources used after their owning scope has ended, especially across async boundaries.
Fix pattern: Add reference counting or ensure async work holds the resource alive.
// Before: session freed while async block may still use it
dispatch_async(queue, ^{
use(session); // session may be freed by caller
});
destroy(session);
// After: atomic refcount ensures session lives until all users are done
struct Session {
_Atomic uint32_t refCount;
_Atomic bool destroyRequested;
};
static void retain(Session *s) {
atomic_fetch_add(&s->refCount, 1);
}
static void release(Session *s) {
if (atomic_fetch_sub(&s->refCount, 1) == 1 && s->destroyRequested) {
// Last reference + destroy requested: actually free
free(s);
}
}
// Async work retains before dispatch, releases on all exit paths
retain(session);
dispatch_async(queue, ^{
use(session);
release(session); // releases on every exit path
});
// Destroy becomes deferred
void destroy(Session *s) {
s->destroyRequested = true;
release(s);
}Key decisions:
- Use
_Atomictypes and appropriate memory ordering (memory_order_acq_relfor the decrement) - Release on EVERY exit path from the async block (including early returns and error paths)
- Destroy marks the intention; actual cleanup happens when the last reference is released
Command/Expression Injection — Unescaped Interpolation
Vulnerability: Untrusted input interpolated into shell commands, LLDB expressions, or SQL queries.
Fix pattern: Escape the input for the target context, or use parameterized invocation.
// Before: raw interpolation into LLDB expression
let expr = "po [(NSString *)\"\\(outputPath)\" writeToFile:...]"
// After: escape for ObjC string literal context
let escaped = outputPath
.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "\"", with: "\\\"")
let expr = "po [(NSString *)\"\\(escaped)\" writeToFile:...]"For shell commands, prefer passing arguments as array elements rather than string interpolation:
// Before: string interpolation
Process.run("/bin/sh", arguments: ["-c", "tool --input \(userInput)"])
// After: argument array (no shell interpretation)
Process.run("/usr/bin/tool", arguments: ["--input", userInput])Missing Authentication — Exposed Endpoints
Vulnerability: CRUD or mutation endpoints accessible without authentication.
Fix pattern: Add authentication middleware. When adding auth is a larger change, the immediate fix is to restrict access by network binding.
# Before: open endpoint
class FlagsController < ApplicationController
def update
# mutates flags
end
end
# After: require authentication
class FlagsController < ApplicationController
before_action :authenticate_admin!
def update
# mutates flags
end
endWhen full auth is not yet implemented, document this as an analysis-only finding with the recommendation and the network-restriction workaround.
Recursive Processing — Stack Overflow
Vulnerability: Recursive traversal of untrusted tree structures without depth limits.
Fix pattern: Add a depth parameter with a maximum, or convert to iterative traversal with an explicit stack.
// Before: unbounded recursion
func flatten(_ node: Node) -> [Node] {
[node] + node.children.flatMap { flatten($0) }
}
// After: depth-limited recursion
func flatten(_ node: Node, depth: Int = 0, maxDepth: Int = 200) -> [Node] {
guard depth < maxDepth else { return [node] }
return [node] + node.children.flatMap { flatten($0, depth: depth + 1, maxDepth: maxDepth) }
}Non-Finite Numeric Values — Trap on Conversion
Vulnerability: Float/Double values from untrusted sources cause fatal traps when converted to Int.
Fix pattern: Guard against non-finite values before conversion.
// Before: traps on NaN/Inf
let zIndex = Int(zPosition)
// After: safe conversion with default
let zIndex = zPosition.isFinite ? Int(clamping: zPosition) : 0For JSON serialization, filter non-finite values before encoding:
guard value.isFinite else {
return .number(0) // or skip the field entirely
}Output Format
Each patched finding produces one of two output formats: a code patch or an analysis-only report.
---
Code Patch Format
Use when you produced a working code fix.
Template
## Summary
{What vulnerability was confirmed and what the fix does. 2-3 sentences maximum.
State the core change: "Confirmed X was still present in HEAD; implemented Y."}
{Optional: 1-2 sentences on the approach — what helper was added, what validation
was introduced, what error type was created.}
## Testing
{Warning marker if environment prevents running tests:}
⚠️ {test command} (environment limitation: {what's missing and why}).
{Or if tests can run:}
✅ {test command} — {result summary}
## Diff
{File change summary: N files edited, +X, -Y}
{The actual unified diff or description of edits made}Guidelines
Summary:
- Lead with confirmation: "Confirmed the vulnerability was still present in HEAD by reviewing..."
- State the fix, not just the finding: "The resolution now routes all PID resolution through a single helper that validates..."
- If the fix introduces a new error type, mention it: "Added an explicit error case for rejected host/non-simulator PIDs"
Testing:
- Always include the specific test command, even if it can't run
- Use
⚠️for environment limitations,✅for successful runs - State the limitation clearly: "script depends on lockf, which is unavailable here"
- If multiple test commands are relevant, list each on its own line
Diff:
- Show the full diff for the affected files
- Keep the diff minimal — if you changed 3 lines, don't show the entire 500-line file
---
Analysis-Only Format
Use when the fix needs user decision, requires architectural changes, or when you've confirmed the vulnerability but the remediation is complex.
Template
## Summary
{What vulnerability was confirmed. 1-2 sentences.}
{Description of the vulnerable code path and why a code fix isn't straightforward.}
## Validation
{Checklist of steps to verify the issue exists:}
- [ ] {Step 1: Build with specific flags or config}
- [ ] {Step 2: Trigger the vulnerable code path}
- [ ] {Step 3: Observe the symptom}
- [ ] {Step 4: Use tool X to capture evidence}
- [ ] Code review confirms {specific pattern} in {file}:{lines}
{Optional: Validation artifact reference}
Validation artifact: {path to PoC, test case, or captured evidence}
## Attack-path analysis
{Assessment of real-world exploitability}
**Path**
{Step-by-step exploitation flow using arrow notation:}
{Input source} → {processing step} → {vulnerable operation} → {impact}
**Likelihood**
{Low/Medium/High} — {1-2 sentences justifying the rating with specific preconditions}
**Impact**
{Low/Medium/High} — {1-2 sentences describing the concrete damage}
**Assumptions**
- {What must be true for the attack to work}
- {What access the attacker needs}
**Controls**
- {Existing controls that limit the attack}
- {Factors that reduce likelihood or impact}
**Blindspots**
- {What you couldn't verify due to environment limitations}
- {Unknowns that affect the risk assessment}Guidelines
Validation:
- Steps should be reproducible by another engineer
- Include specific file references and line numbers
- If you have a PoC artifact, reference its path
Attack-path analysis:
- The Path should read like a chain: each step feeds the next
- Likelihood and Impact ratings should be consistent with the finding's severity
- Assumptions must state attacker preconditions explicitly
- Controls include EXISTING mitigations in the codebase
- Blindspots are honest about what you couldn't test
---
Grouping Multiple Findings
When multiple findings share a root cause and are fixed together, use:
## Summary
{What shared vulnerability pattern was confirmed and what the centralized fix does.}
Addresses findings:
- {Finding 1 title} ({severity})
- {Finding 2 title} ({severity})
- {Finding 3 title} ({severity})
## Testing
{Test commands}
## Diff
{The combined diff}---
Commit Message Format
When committing patches:
security: {brief description of fix}
Fixes: {finding title}
Severity: {level}For grouped findings:
security: {brief description of centralized fix}
Fixes:
- {Finding 1 title} ({severity})
- {Finding 2 title} ({severity})
Severity: {highest level in group}Patching Methodology
Detailed approach for turning security findings into minimal, correct code patches.
Phase 1: Ingest Findings
Goal: Parse the input into a structured set of findings with actionable details.
Input formats (in priority order):
1. findings.json (from threat-model): Read directly — already structured with data flow traces, systemic groupings, exploit chains, and severity ratings. This is the richest input. 2. Codex CSV: Use scripts/parse-findings.sh to extract title, description, severity, relevant_paths, commit_hash per finding. 3. THREAT-MODEL.md: Extract risks from the Criticality Calibration section, cross-referenced with Attack Surface subsections for affected files. 4. Individual descriptions: User provides finding text directly.
When input is findings.json
Read the file and use its structured data directly:
findings[].traceprovides the data flow from entry to sink — this tells you exactly which code path to fixfindings[].relevant_pathslists affected filesfindings[].recommended_fixsuggests the fix approachsystemic[]groups findings by root cause — fix the systemic root cause first, which resolves multiple findings at oncechains[]identifies multi-step attack paths — prioritize chain-breaking fixesfindings[].statustells you which findings are alreadypatchedorclosed— skip them
When input is Codex CSV or other formats
For each finding, extract:
| Field | Source |
|---|---|
| Title | CSV title or threat model risk bullet |
| Severity | CSV severity or calibration level |
| Affected files | CSV relevant_paths or threat model file references |
| Description | CSV description or attack surface detail |
| Commit hash | CSV commit_hash (for tracing when the issue was introduced) |
Phase 2: Triage & Group
Goal: Prioritize work and identify findings that share a root cause.
Priority order
Process findings by severity: critical → high → medium → low. Within each level, prioritize by: 1. Findings that affect shared/centralized code (fixing one location fixes multiple findings) 2. Findings with clear, mechanical fixes (input validation, bounds checking) 3. Findings that require design decisions (defer to analysis-only output)
Grouping related findings
Look for findings that share a root cause or fix pattern:
| Grouping signal | Example | Fix approach |
|---|---|---|
| Same file, same pattern | Multiple predictable /tmp paths in ExtractionCapture.swift | One helper for secure temp dirs |
| Same vulnerability, multiple call sites | PID resolution duplicated in attach/watch/await | Extract shared validation helper |
| Same missing sanitization | Path traversal via bundleID in multiple commands | One validation function, applied everywhere |
Grouped findings get a single patch that addresses the root cause rather than individual patches per symptom.
Phase 3: Read Affected Code
Goal: Understand the vulnerable code in its full context before attempting a fix.
Actions: 1. Open each file listed in relevant_paths 2. Read the surrounding context — not just the vulnerable line, but the function, the callers, and the data flow 3. Identify the trust boundary: where does attacker-controlled data enter this code path? 4. Check if there are existing validation/sanitization functions nearby that could be reused 5. Understand the component's error handling conventions (does it throw, return nil, log and continue?)
Why context matters: A patch that doesn't match the codebase's conventions will be rejected or cause regressions. The finding's description identifies the vulnerability; the code context tells you how to fix it idiomatically.
Phase 4: Confirm Vulnerability
Goal: Verify the issue is still present in HEAD before writing a fix.
The finding may reference a specific commit hash. Between that commit and HEAD, the code may have:
- Been refactored (vulnerability moved or renamed)
- Been fixed independently (finding is stale)
- Changed enough that the finding's description no longer applies
Actions: 1. Check if the affected code is still at the paths listed in the finding 2. If the file has moved, search for the relevant function/pattern 3. Verify the vulnerable pattern described in the finding is still present 4. If the issue is fixed or the code no longer exists, skip this finding and note it as "resolved" or "not applicable"
Output: One of:
- Confirmed: The vulnerability is present at the described location
- Moved: The vulnerability exists but the code has been relocated to
{new path} - Resolved: The issue has been fixed since the finding was filed
- Not applicable: The code no longer exists or the preconditions no longer hold
Phase 5: Design Fix
Goal: Determine the minimal correct fix before writing code.
Fix design principles
1. Fix the vulnerability, not the architecture. A security patch is not a refactoring opportunity. The goal is the smallest change that eliminates the risk.
2. Validate at the entry point. When untrusted input crosses a trust boundary, validate it there — not deep inside the call chain. This prevents the same vulnerability from appearing in new callers.
3. Centralize shared fixes. If three call sites have the same vulnerability, extract a shared helper. This is the one case where adding a function is part of the minimal fix, because duplicating the validation three times is worse.
4. Match existing patterns. If the codebase already has input validation helpers, escaping functions, or secure temp dir creation, use them. Don't introduce a new pattern when an existing one serves.
5. Add explicit error types. When rejecting input, add a specific error case with a clear message. Not "invalid input" but "PID \(pid) is not a running simulator app."
6. Fail closed. The default behavior when validation fails should be to reject/error, not to proceed with a warning.
Consult fix patterns
Read fix-patterns.md for the standard fix approach per vulnerability class. Match the finding's vulnerability type to a pattern, then adapt to the specific codebase.
Confirm with user before implementing
Before writing any code, present the fix design to the user:
- Which files will be changed
- What the fix approach is (e.g., "extract a shared validation helper," "add bounds check")
- Whether multiple findings are addressed by this fix
Wait for user approval before proceeding to Phase 6. This is a guardrail — security patches modify source code and must be reviewed before application.
Phase 6: Implement
Goal: Write the code changes.
Implementation rules
- One logical fix per patch. Even if multiple lines change, the fix should address exactly one vulnerability or group of related findings.
- Minimal diff. Don't reformat, rename, or clean up code outside the fix. The diff should be reviewable in under 2 minutes.
- Preserve function signatures when possible. If the fix can be done inside the existing function, don't change its interface. If a new parameter is needed (e.g., an allowed-PIDs set), prefer a new helper over modifying all callers.
- New helpers go near the code they protect. A
resolveSimulatorAppPIDfunction goes in the same file as the commands that use it, markedprivate. Don't create a new file for one function. - Test the fix compiles. If possible, run a build after the change. If build tools are unavailable, note this limitation.
Phase 7: Document
Goal: Write the structured documentation for the patch.
Use the templates in output-format.md. Two modes:
Code patch documentation
Write when you produced a code fix: 1. Summary: What vulnerability was confirmed, what the fix does (2-3 sentences) 2. Testing: Specific build/test commands. If the environment can't run them, document the commands and the limitation with a warning marker 3. Diff: The actual code changes
Analysis-only documentation
Write when the fix needs user decision or architectural changes: 1. Summary: What was confirmed 2. Validation: Checklist of how to verify the issue exists 3. Attack-path analysis: Path diagram, Likelihood, Impact, Assumptions, Controls, Blindspots
Phase 8: Test
Goal: Verify the fix works and doesn't break existing functionality.
Actions: 1. Identify the relevant test suite or test filter for the affected code 2. Run the tests if possible 3. If the environment prevents running tests (missing tools, dependencies), document:
- The exact test command to run
- What the limitation is
- Use the
⚠️marker to signal the limitation
Common testing patterns:
| Language | Test command pattern |
|---|---|
| Swift/SPM | swift test --filter {TestSuiteName} |
| Rust | cargo test {test_name} |
| Node.js | npm test -- --grep "{pattern}" |
| Python | pytest {path}::{test_name} |
| Go | go test ./... -run {TestName} |
| Ruby/Rails | bundle exec rspec {path} |
Phase 9: Output
Goal: Deliver the patches in a consistent format.
For each finding or group: 1. Output the documentation (Summary + Testing + Diff, or Analysis-only) 2. If the user wants commits, create one commit per logical fix with a descriptive message 3. If multiple findings were grouped, note which findings are addressed by the patch
Commit message format
security: {brief description of fix}
Fixes: {finding title}
Severity: {level}Reverting patches
If a patch needs to be undone:
- With commits (
commit_patches: true):git revert <commit>orgit reset --soft HEAD~1 - Without commits (default):
git checkout -- <files>to discard working tree changes, orgit stashto save them for later review - Review before discarding: Always run
git diffbefore reverting to confirm which changes will be lost
When to skip a finding
- Already fixed: Note "Resolved — fixed in {commit}" and move on
- Not applicable: Note "Not applicable — {reason}" and move on
- Needs user decision: Produce analysis-only documentation and ask the user
- Out of scope: If the fix requires changes to a different repository or system, note the dependency
Phase 10: Update Finding State
Goal: Close the loop by updating findings.json with patch results.
If the input was a findings.json file:
1. For each finding that was patched, update its status:
{
"status": "patched",
"resolved_at": "2026-03-28T14:00:00Z",
"resolved_by": "commit-hash-of-fix"
}2. For systemic findings where ALL child findings are patched, update the systemic finding status too.
3. For findings that were skipped (already fixed, not applicable), update status to closed or wont_fix with a reason.
4. Write the updated findings.json back to its original location.
This enables the threat-model → threat-patch feedback loop:
threat-model produces findings.json (status: open)
↓
threat-patch applies fixes, updates findings.json (status: patched)
↓
threat-model --diff re-analyzes, verifies fixes (status: verified)If the input was NOT findings.json (Codex CSV, inline description), this phase is skipped.
#!/usr/bin/env bash
# parse-findings.sh — Parse Codex security findings CSV into structured output
# Extracts key fields per finding, sorted by severity, for use by the patching workflow.
#
# Usage: parse-findings.sh <csv-path> [--repo <repo-filter>] [--severity <level>]
# Output: Structured findings grouped by repository and severity
set -euo pipefail
command -v python3 >/dev/null 2>&1 || {
echo "Error: python3 is required for CSV parsing but not found." >&2
exit 1
}
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <csv-path> [--repo <owner/repo>] [--severity <critical|high|medium|low>]" >&2
echo "" >&2
echo "Examples:" >&2
echo " $0 findings.csv # All findings" >&2
echo " $0 findings.csv --repo pproenca/agent-sim # Filter by repository" >&2
echo " $0 findings.csv --severity high # Filter by severity" >&2
exit 1
fi
CSV_PATH="$1"
shift
if [[ ! -f "$CSV_PATH" ]]; then
echo "Error: '$CSV_PATH' is not a file" >&2
exit 1
fi
REPO_FILTER=""
SEVERITY_FILTER=""
while [[ $# -gt 0 ]]; do
case "$1" in
--repo)
REPO_FILTER="$2"
shift 2
;;
--severity)
SEVERITY_FILTER="$2"
shift 2
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done
python3 - "$CSV_PATH" "$REPO_FILTER" "$SEVERITY_FILTER" << 'PYEOF'
import csv
import sys
from collections import Counter
from datetime import datetime, timezone
csv_path = sys.argv[1]
repo_filter = sys.argv[2] if len(sys.argv) > 2 else ""
severity_filter = sys.argv[3] if len(sys.argv) > 3 else ""
severity_order = {"critical": 1, "high": 2, "medium": 3, "low": 4}
print(f"# Security Findings: {csv_path.split('/')[-1]}")
print(f"# Parsed: {datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}")
if repo_filter:
print(f"# Repository filter: {repo_filter}")
if severity_filter:
print(f"# Severity filter: {severity_filter}")
print()
findings = []
with open(csv_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
repo = row.get("repository", "")
severity = row.get("severity", "")
if repo_filter and repo != repo_filter:
continue
if severity_filter and severity != severity_filter:
continue
findings.append(row)
# Sort by severity
findings.sort(key=lambda r: severity_order.get(r.get("severity", ""), 5))
repo_counts = Counter()
severity_counts = Counter()
for row in findings:
repo = row.get("repository", "")
severity = row.get("severity", "")
title = row.get("title", "")
status = row.get("status", "")
commit = row.get("commit_hash", "")[:12]
paths = row.get("relevant_paths", "")
has_patch = row.get("has_patch", "false")
repo_counts[repo] += 1
severity_counts[severity] += 1
patch_marker = " [has patch]" if has_patch == "true" else ""
print(f"## [{severity}] {title}{patch_marker}")
print(f"- **Repository**: {repo}")
print(f"- **Status**: {status}")
print(f"- **Commit**: `{commit}`")
print(f"- **Files**: {paths}")
print()
print("---")
print("## Summary")
print(f"- **Total findings**: {len(findings)}")
for sev in ["critical", "high", "medium", "low"]:
count = severity_counts.get(sev, 0)
if count > 0:
print(f"- **{sev}**: {count}")
print()
print("## By Repository")
for repo, count in repo_counts.most_common():
print(f"- **{repo}**: {count}")
PYEOF
Related skills
FAQ
What does threat-patch do?
threat-patch: A skill for development. This provides functionality for development workflows.
When should I use threat-patch?
When you need to use threat-patch for development tasks, or when threat-patch: a skill for development. this provides functionality for development workflows.
What are the main capabilities?
threat-patch.