Loading...
Loading...
Scan code changes for security vulnerabilities using STRIDE threat modeling, validate findings for exploitability, and output structured results for downstream patch generation. Supports PR review, scheduled scans, and full repository audits.
npx skill4agent add factory-ai/factory-plugins security-review@droid security.factory/threat-model.mdthreat-model-generation┌──────────────────────┐
│ threat-model- │ ← Generates STRIDE threat model
│ generation │
└─────────┬────────────┘
↓ .factory/threat-model.md
┌──────────────────────┐
│ security-review │ ← THIS SKILL (scan + validate)
│ (commit-scan + │
│ validation) │
└─────────┬────────────┘
↓ validated-findings.json
┌──────────────────────┐
│ security-patch- │ ← Generates fixes
│ generation │
└──────────────────────┘| Input | Description | Required | Default |
|---|---|---|---|
| Mode | | No | |
| Base branch | Branch to diff against | No | Auto-detected from PR |
| CVE lookback | How far back to check dependency CVEs | No | 12 months |
| Severity threshold | Minimum severity to report | No | |
# Check if threat model exists
if [ -f ".factory/threat-model.md" ]; then
echo "Threat model found"
# Check age
LAST_MODIFIED=$(stat -f %m .factory/threat-model.md 2>/dev/null || stat -c %Y .factory/threat-model.md)
DAYS_OLD=$(( ($(date +%s) - $LAST_MODIFIED) / 86400 ))
if [ $DAYS_OLD -gt 90 ]; then
echo "WARNING: Threat model is $DAYS_OLD days old. Consider regenerating."
fi
else
echo "No threat model found. Generate one first using threat-model-generation skill."
fi# PR mode - scan PR diff
git diff --name-only origin/HEAD...
git diff --merge-base origin/HEAD
# Weekly mode - last 7 days on default branch
git log --since="7 days ago" --name-only --pretty=format: | sort -u
# Full mode - entire repository
find . -type f \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.go" -o -name "*.java" \) | head -500
# Staged mode - staged changes only
git diff --staged --name-only# SQL Injection (Tampering)
sql = f"SELECT * FROM users WHERE id = {user_id}" # VULNERABLE
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,)) # SAFE
# Command Injection (Tampering)
os.system(f"ping {user_input}") # VULNERABLE
subprocess.run(["ping", "-c", "1", user_input]) # SAFE
# XSS (Tampering)
element.innerHTML = userInput; // VULNERABLE
element.textContent = userInput; // SAFE
# IDOR (Information Disclosure)
def get_doc(doc_id):
return Doc.query.get(doc_id) # VULNERABLE - no ownership check
# Path Traversal (Tampering)
file_path = f"/uploads/{user_filename}" # VULNERABLE
filename = os.path.basename(user_input) # SAFE# Node.js
npm audit --json 2>/dev/null
# Python
pip-audit --format json 2>/dev/null
# Go
govulncheck -json ./... 2>/dev/null
# Rust
cargo audit --json 2>/dev/nullREACHABLEPOTENTIALLY_REACHABLENOT_REACHABLEsecurity-findings.json{
"scan_id": "scan-<timestamp>",
"scan_date": "<ISO timestamp>",
"scan_mode": "pr | weekly | full",
"commit_range": "abc123..def456",
"threat_model_version": "1.0.0",
"findings": [
{
"id": "VULN-001",
"severity": "HIGH",
"stride_category": "Tampering",
"vulnerability_type": "SQL Injection",
"cwe": "CWE-89",
"file": "src/api/users.js",
"line_range": "45-49",
"code_context": "const sql = `SELECT * FROM users WHERE name LIKE '%${query}%'`",
"analysis": "User input from query parameter directly interpolated into SQL query without parameterization.",
"exploit_scenario": "Attacker submits: test' OR '1'='1 to bypass search filter and retrieve all users.",
"threat_model_reference": "Section 5.2 - SQL Injection",
"recommended_fix": "Use parameterized queries: db.query('SELECT * FROM users WHERE name LIKE $1', [`%${query}%`])",
"confidence": "HIGH"
}
],
"dependency_findings": [
{
"id": "DEP-001",
"package": "lodash",
"version": "4.17.20",
"ecosystem": "npm",
"vulnerability_id": "CVE-2021-23337",
"severity": "HIGH",
"cvss": 7.2,
"fixed_version": "4.17.21",
"reachability": "REACHABLE",
"reachability_evidence": "lodash.template() called in src/utils/email.js:15"
}
],
"summary": {
"total_findings": 5,
"by_severity": {"CRITICAL": 0, "HIGH": 2, "MEDIUM": 2, "LOW": 1},
"by_stride": {
"Spoofing": 0,
"Tampering": 2,
"Repudiation": 0,
"InfoDisclosure": 2,
"DoS": 0,
"ElevationOfPrivilege": 1
}
}
}dangerouslySetInnerHTMLbypassSecurityTrustHtml{
"proof_of_concept": {
"payload": "' OR '1'='1",
"request": "GET /api/users?search=test%27%20OR%20%271%27%3D%271",
"expected_behavior": "Returns users matching 'test'",
"actual_behavior": "Returns ALL users due to SQL injection"
}
}validated-findings.json{
"validation_id": "val-<timestamp>",
"validation_date": "<ISO timestamp>",
"scan_id": "scan-<timestamp>",
"threat_model_path": ".factory/threat-model.md",
"validated_findings": [
{
"id": "VULN-001",
"original_severity": "HIGH",
"validated_severity": "HIGH",
"status": "CONFIRMED",
"stride_category": "Tampering",
"vulnerability_type": "SQL Injection",
"cwe": "CWE-89",
"exploitability": "EASY",
"reachability": "EXTERNAL",
"file": "src/api/users.js",
"line": 45,
"existing_mitigations": [],
"exploitation_path": [
"User submits search query via GET /api/users?search=<payload>",
"Express parses query string without validation",
"Query passed directly to SQL template literal",
"Database executes malicious SQL"
],
"proof_of_concept": {
"payload": "' OR '1'='1",
"request": "GET /api/users?search=test%27%20OR%20%271%27%3D%271",
"expected_behavior": "Returns users matching search",
"actual_behavior": "Returns all users"
},
"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N",
"cvss_score": 9.1,
"recommendation": "Use parameterized queries",
"references": [
"https://cwe.mitre.org/data/definitions/89.html",
"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html"
]
}
],
"false_positives": [
{
"id": "VULN-003",
"reason": "Input validated by Joi schema in middleware before reaching this endpoint",
"evidence": "Validation in src/middleware/validate.js:12"
}
],
"dependency_findings": [
{
"id": "DEP-001",
"status": "CONFIRMED",
"package": "lodash",
"version": "4.17.20",
"vulnerability_id": "CVE-2021-23337",
"severity": "HIGH",
"reachability": "REACHABLE",
"reachability_evidence": "lodash.template() called in src/utils/email.js:15",
"fixed_version": "4.17.21"
}
],
"summary": {
"total_scanned": 8,
"confirmed": 5,
"false_positives": 3,
"by_severity": {
"critical": 1,
"high": 2,
"medium": 1,
"low": 1
},
"by_stride": {
"Spoofing": 0,
"Tampering": 3,
"Repudiation": 0,
"InfoDisclosure": 1,
"DoS": 0,
"ElevationOfPrivilege": 1
}
}
}🔴 **CRITICAL: SQL Injection (CWE-89)**
**STRIDE Category:** Tampering
**Confidence:** High
**File:** `src/api/users.js:45-49`
**Analysis:**
User input from `req.query.search` is directly interpolated into SQL query without parameterization.
**Suggested Fix:**
```diff
- const query = `SELECT * FROM users WHERE name LIKE '%${search}%'`;
- const results = await db.query(query);
+ const query = `SELECT * FROM users WHERE name LIKE $1`;
+ const results = await db.query(query, [`%${search}%`]);
Post summary tracking comment:
```markdown
## 🔒 Security Review Summary
| Severity | Count |
|----------|-------|
| 🔴 Critical | 1 |
| 🟠 High | 2 |
| 🟡 Medium | 3 |
| 🔵 Low | 0 |
### Findings
| ID | Severity | Type | File | Status |
|----|----------|------|------|--------|
| VULN-001 | Critical | SQL Injection | src/api/users.js:45 | Action required |
| VULN-002 | High | XSS | src/components/Comment.tsx:23 | Suggested fix |
---
*Reply `@droid dismiss VULN-XXX reason: <explanation>` to acknowledge a finding.*droid/security-report-{YYYY-MM-DD}fix(security): Security scan report - {date} ({N} findings).factory/security/reports/security-report-{YYYY-MM-DD}.mdvalidated-findings.json.factory/threat-model.md| Severity | PR Mode | Weekly/Full Mode |
|---|---|---|
| CRITICAL | | Create HIGH priority issue, notify security team |
| HIGH | | Create issue, require review |
| MEDIUM | | Create issue |
| LOW | | Include in report |
| Severity | Criteria | Examples |
|---|---|---|
| CRITICAL | Immediately exploitable, high impact, no auth required | RCE, hardcoded production secrets, auth bypass |
| HIGH | Exploitable with some conditions, significant impact | SQL injection, stored XSS, IDOR |
| MEDIUM | Requires specific conditions, moderate impact | Reflected XSS, CSRF, info disclosure |
| LOW | Difficult to exploit, low impact | Verbose errors, missing security headers |
| STRIDE Category | Vulnerability Types |
|---|---|
| Spoofing | Weak auth, session hijacking, token exposure, credential stuffing |
| Tampering | SQL injection, XSS, command injection, mass assignment, path traversal |
| Repudiation | Missing audit logs, insufficient logging |
| Info Disclosure | IDOR, verbose errors, hardcoded secrets, data leaks |
| DoS | Missing rate limits, resource exhaustion, ReDoS |
| Elevation of Privilege | Missing authz, role manipulation, RBAC bypass |
validated-findings.jsonScan PR #123 for security vulnerabilities.@droid security@droid security --fullScan commits from the last 7 days on main for security vulnerabilities.Run full security analysis on PR #123: scan, validate, and generate patches..factory/
├── threat-model.md # STRIDE threat model
├── security-config.json # Configuration
└── security/
├── acknowledged.json # Dismissed findings
└── reports/
└── security-report-{date}.md@droid dismiss reason: Input is validated by Joi schema in middleware@droid dismiss VULN-007 reason: Accepted risk for internal admin tool.factory/security/acknowledged.json