diff --git a/.claude/skills/threat-model-lvms b/.claude/skills/threat-model-lvms new file mode 120000 index 00000000..34a1f628 --- /dev/null +++ b/.claude/skills/threat-model-lvms @@ -0,0 +1 @@ +../../plugins/threat-model/skills/lvms-threat-model \ No newline at end of file diff --git a/.claude/skills/threat-model-sno b/.claude/skills/threat-model-sno new file mode 120000 index 00000000..7b03e4b0 --- /dev/null +++ b/.claude/skills/threat-model-sno @@ -0,0 +1 @@ +../../plugins/threat-model/skills/sno-threat-model \ No newline at end of file diff --git a/.claude/skills/threat-model-tna b/.claude/skills/threat-model-tna new file mode 120000 index 00000000..f1ef2498 --- /dev/null +++ b/.claude/skills/threat-model-tna @@ -0,0 +1 @@ +../../plugins/threat-model/skills/tna-threat-model \ No newline at end of file diff --git a/.claude/skills/threat-model-tnf b/.claude/skills/threat-model-tnf new file mode 120000 index 00000000..7fd69d32 --- /dev/null +++ b/.claude/skills/threat-model-tnf @@ -0,0 +1 @@ +../../plugins/threat-model/skills/tnf-threat-model \ No newline at end of file diff --git a/plugins/threat-model/.claude-plugin/plugin.json b/plugins/threat-model/.claude-plugin/plugin.json new file mode 100644 index 00000000..775bf5c8 --- /dev/null +++ b/plugins/threat-model/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "threat-model", + "version": "1.0.0", + "description": "Analyze PRs for security threats with STRIDE/DFD analysis, MITRE ATT&CK and OWASP mapping for OpenShift edge topologies (TNA, TNF, SNO, LVMS)", + "author": { + "name": "TNF Security" + }, + "license": "Apache-2.0", + "keywords": ["security", "threat-model", "stride", "mitre", "owasp", "openshift", "tnf", "tna", "sno", "lvms"] +} diff --git a/plugins/threat-model/README.md b/plugins/threat-model/README.md new file mode 100644 index 00000000..4e767758 --- /dev/null +++ b/plugins/threat-model/README.md @@ -0,0 +1,104 @@ +# Threat Model Plugin for Claude Code + +Security threat analysis for OpenShift PRs across multiple topologies (TNF, TNA, SNO, LVMS). + +## What It Does + +Analyzes pull requests for security threats against OpenShift clusters: + +- Fetches PR diffs from GitHub +- Runs ShellCheck on shell scripts +- Maps changes to Data Flow Diagram (DFD) elements +- Applies per-element STRIDE analysis +- Cross-references against formal threat models +- Maps findings to MITRE ATT&CK techniques and OWASP Top 10:2025 +- Generates formal threat analysis reports + +## Usage + +### TNF (Two-Node Fencing) + +```bash +/threat-model:tnf 2136 +/threat-model:tnf https://github.com/ClusterLabs/resource-agents/pull/2136 +/threat-model:tnf resource-agents 2136 +``` + +### TNA (Two-Node Arbiter) + +```bash +/threat-model:tna 1437 +/threat-model:tna https://github.com/openshift/cluster-etcd-operator/pull/1437 +/threat-model:tna installer 10403 +``` + +### SNO (Single Node OpenShift) + +```bash +/threat-model:sno 10498 +/threat-model:sno https://github.com/openshift/installer/pull/10498 +/threat-model:sno installer 10498 +``` + +### LVMS (LVM Storage) + +```bash +/threat-model:lvms 2271 +/threat-model:lvms https://github.com/openshift/lvm-operator/pull/2271 +/threat-model:lvms lvm-operator 2271 +``` + +> **Note**: The LVMS DFD model is not yet defined. The LVMS skill performs general security analysis, ShellCheck scanning, and MITRE/OWASP mapping. Full DFD/STRIDE analysis will be available once its DFD model is created. + +## Workspace Requirements + +The skill expects a workspace with a `repos/` directory containing cloned repositories. It auto-discovers the workspace root at runtime. + +### Recommended workspace layout + +```text +your-workspace/ +├── repos/ +│ ├── cluster-etcd-operator/ +│ ├── installer/ +│ ├── machine-config-operator/ +│ ├── resource-agents/ +│ ├── two-node-toolbox/ +│ │ └── docs/ +│ │ ├── TNF-THREAT-MODEL.md +│ │ └── TNA-THREAT-MODEL.md +│ └── ... +└── .claude/ + └── skills/ + ├── threat-model/ + ├── mitre-findings-tnf.md # Created automatically on first use + ├── mitre-findings-tna.md + ├── mitre-findings-sno.md + └── mitre-findings-lvms.md +``` + +### Optional dependencies + +- **ShellCheck** (`dnf install ShellCheck`) - for automated shell script analysis +- **gh** CLI - for fetching PR details from GitHub +- **Formal threat model files** - for DFD/STRIDE cross-referencing + +## What's Included + +| File | Purpose | +|------|---------| +| `skills/tnf-threat-model/SKILL.md` | TNF threat analysis skill | +| `skills/tnf-threat-model/dfd-elements-tnf.md` | TNF DFD element catalog | +| `skills/tna-threat-model/SKILL.md` | TNA threat analysis skill | +| `skills/tna-threat-model/dfd-elements-tna.md` | TNA DFD element catalog | +| `skills/sno-threat-model/SKILL.md` | SNO threat analysis skill | +| `skills/sno-threat-model/dfd-elements-sno.md` | SNO DFD element catalog (SNO-P1–P6, SNO-DS1–DS6, SNO-DF1–DF10, SNO-TB1–TB3) | +| `skills/lvms-threat-model/SKILL.md` | LVMS threat analysis skill | +| `skills/lvms-threat-model/dfd-elements-lvms.md` | LVMS DFD element catalog (placeholder) | +| `references/mitre-reference.md` | MITRE ATT&CK quick reference | +| `references/owasp-reference.md` | OWASP Top 10:2025 reference | +| `references/mitre-findings-template.md` | Cumulative findings tracker template | + +## License + +Apache-2.0 diff --git a/plugins/threat-model/references/mitre-findings-template.md b/plugins/threat-model/references/mitre-findings-template.md new file mode 100644 index 00000000..845a9a78 --- /dev/null +++ b/plugins/threat-model/references/mitre-findings-template.md @@ -0,0 +1,13 @@ +# MITRE ATT&CK Findings Tracker + +Cumulative security findings from PR threat analysis. + +## Legend + +**Severity**: Critical / High / Medium / Low / Info +**Status**: Open / Mitigated / Accepted / FalsePositive + +--- + + + diff --git a/plugins/threat-model/references/mitre-reference.md b/plugins/threat-model/references/mitre-reference.md new file mode 100644 index 00000000..662db209 --- /dev/null +++ b/plugins/threat-model/references/mitre-reference.md @@ -0,0 +1,171 @@ +# MITRE ATT&CK Quick Reference + +Common techniques for infrastructure and Kubernetes security. + +## Initial Access (TA0001) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1078 | Valid Accounts | Default creds, leaked tokens, service account abuse | +| T1190 | Exploit Public-Facing App | Unpatched CVEs, injection flaws | +| T1133 | External Remote Services | Exposed SSH, RDP, VNC, API endpoints | + +## Execution (TA0002) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1059 | Command/Script Interpreter | Shell exec, eval, unsanitized input to commands | +| T1609 | Container Admin Command | kubectl exec, docker exec, crictl | +| T1610 | Deploy Container | Malicious container images, privileged pods | + +## Persistence (TA0003) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1053 | Scheduled Task/Job | CronJobs, systemd timers | +| T1098 | Account Manipulation | Adding users, modifying RBAC | +| T1543 | Create/Modify System Process | Systemd services, init scripts | +| T1136 | Create Account | New ServiceAccounts, local users | + +## Privilege Escalation (TA0004) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1068 | Exploitation for Privilege Escalation | CVE exploits, kernel vulns | +| T1548 | Abuse Elevation Control | sudo, setuid, capabilities | +| T1611 | Escape to Host | Container breakout, hostPID, hostNetwork | + +## Defense Evasion (TA0005) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1070 | Indicator Removal | Log deletion, history clearing | +| T1562 | Impair Defenses | Disabling SELinux, seccomp, audit | +| T1036 | Masquerading | Renamed binaries, fake processes | + +## Credential Access (TA0006) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1552 | Unsecured Credentials | Hardcoded secrets, env vars, config files | +| T1528 | Steal Application Access Token | Token theft, SA token access | +| T1003 | OS Credential Dumping | /etc/shadow, memory scraping | +| T1555 | Credentials from Password Stores | Secret managers, keyrings | + +## Discovery (TA0007) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1083 | File and Directory Discovery | Filesystem enumeration | +| T1046 | Network Service Discovery | Port scanning, service probing | +| T1613 | Container and Resource Discovery | kubectl get, API enumeration | + +## Lateral Movement (TA0008) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1021 | Remote Services | SSH, WinRM, kubectl | +| T1550 | Use Alternate Auth Material | Token reuse, cert theft | + +## Impact (TA0040) + +| ID | Technique | Indicators | +|----|-----------|------------| +| T1485 | Data Destruction | rm -rf, etcd data deletion | +| T1486 | Data Encrypted for Impact | Ransomware patterns | +| T1489 | Service Stop | systemctl stop, kill processes | +| T1529 | System Shutdown/Reboot | STONITH abuse, power off | + +## TNF-Specific Techniques + +| ID | Technique | TNF Context | DFD Elements | +|----|-----------|-------------|--------------| +| T1552 | Unsecured Credentials | BMC credentials in install-config, secrets, CIB | DS1, DS2, DS3, DF1-DF9 | +| T1529 | System Shutdown | Malicious fencing, STONITH abuse | P6, P8, EE2 | +| T1489 | Service Stop | etcd/pacemaker service disruption | P7, DS5 | +| T1557 | Adversary-in-the-Middle | BMC MITM when cert disabled, Corosync interception | P8, DF10, EE2, EE3 | +| T1078 | Valid Accounts | BMC account compromise, predictable PCSD token | P3, P8, DS4, EE2 | +| T1059 | Command Interpreter | Shell injection via credentials, OCF agent scripts | P5, P7, DF9 | +| T1611 | Escape to Host | Privileged TNF setup/fencing containers with nsenter | P3, P4, P5 | +| T1562 | Impair Defenses | CIB manipulation to disable STONITH | DS3, P4 | + +## TNA-Specific Techniques + +| ID | Technique | TNA Context | DFD Elements | +|----|-----------|-------------|--------------| +| T1078 | Valid Accounts | Admin credential theft (kubeconfig) | TNA-EE1 | +| T1552 | Unsecured Credentials | Worker ignition token leak | TNA-DS6 | +| T1611 | Escape to Host | Container escape from pod to node root | TNA-P5 | +| T1562 | Impair Defenses | Arbiter taint removal disabling scheduling protection | TNA-P3 | +| T1489 | Service Stop | etcd quorum disruption (arbiter + 1 master) | TNA-DS5 | +| T1021 | Remote Services | Lateral movement from worker to control plane via pod network | TNA-P5, TNA-DS5 | + +## SNO-Specific Techniques + +| ID | Technique | SNO Context | DFD Elements | +|----|-----------|-------------|--------------| +| T1552 | Unsecured Credentials | Pull secret, offline token, kubeadmin-password on admin workstation | SNO-DS1, SNO-DS4 | +| T1611 | Escape to Host | Bootstrap-in-place agent runs privileged on bare metal | SNO-P5 | +| T1610 | Deploy Container | Workloads scheduled on master (no worker isolation) | SNO-P6 | +| T1562 | Impair Defenses | UnsafeScalingStrategy bypasses quorum safety checks | SNO-P4 | +| T1485 | Data Destruction | Single etcd member — node failure = total data loss | SNO-DS3 | +| T1195 | Supply Chain Compromise | Discovery ISO tampering before boot | SNO-DS2 | +| T1078 | Valid Accounts | Admin credential theft (kubeconfig) | SNO-EE1 | + +## LVMS-Specific Techniques + +> **Not yet defined.** This section will be populated once the LVMS DFD model is created. + +## TNF DFD Element to ATT&CK Mapping + +| DFD Element | Primary ATT&CK Techniques | Per-Element STRIDE IDs | +|-------------|--------------------------|----------------------| +| P1 (Installer) | T1552 | PE-P1-I-1, PE-P1-T-1 | +| P3 (Auth Job) | T1078, T1611 | PE-P3-S-1, PE-P3-E-1 | +| P4 (Setup Job) | T1611, T1562 | PE-P4-E-1, PE-P4-T-1 | +| P5 (Fencing Job) | T1059, T1552, T1611 | PE-P5-I-1, PE-P5-T-1, PE-P5-E-1 | +| P6 (fenced) | T1529 | PE-P6-S-1, PE-P6-D-1 | +| P7 (podman-etcd) | T1489, T1059 | PE-P7-T-1, PE-P7-D-1 | +| P8 (fence_redfish) | T1557, T1529, T1552 | PE-P8-S-1, PE-P8-I-1 | +| DS1 (install-config) | T1552 | PE-DS1-I-1 | +| DS2 (K8s Secrets) | T1552 | PE-DS2-I-1, PE-DS2-T-1 | +| DS3 (CIB) | T1552, T1562 | PE-DS3-I-1, PE-DS3-T-1 | +| DS4 (PCSD Token) | T1078 | PE-DS4-I-1 | +| DF9 (creds as CLI args) | T1552, T1059 | PE-DF9-I-1 | +| DF10 (Redfish HTTPS) | T1557 | PE-DF10-T-1, PE-DF10-I-1 | +| EE2 (BMC) | T1529, T1190 | PE-EE2-S-1, PE-EE2-S-2 | + +## TNA DFD Element to ATT&CK Mapping + +| DFD Element | Primary ATT&CK Techniques | Per-Element STRIDE IDs | +|-------------|--------------------------|----------------------| +| TNA-P1 (Installer) | T1552 | PE-TNA-P1-T-1, PE-TNA-P1-D-1 | +| TNA-P3 (MCO) | T1562 | PE-TNA-P3-T-1, PE-TNA-P3-D-1 | +| TNA-P4 (CEO) | T1489 | PE-TNA-P4-T-1, PE-TNA-P4-D-1 | +| TNA-P5 (Worker Kubelet) | T1021, T1611 | PE-TNA-P5-S-1, PE-TNA-P5-E-1 | +| TNA-DS5 (etcd Data) | T1552, T1489 | PE-TNA-DS5-T-1, PE-TNA-DS5-I-1, PE-TNA-DS5-D-1 | +| TNA-DS6 (Worker Ignition) | T1552 | PE-TNA-DS6-T-1, PE-TNA-DS6-I-1 | +| TNA-EE1 (Admin) | T1078 | PE-TNA-EE1-S-1, PE-TNA-EE1-R-1 | + +## SNO DFD Element to ATT&CK Mapping + +| DFD Element | Primary ATT&CK Techniques | Per-Element STRIDE IDs | +|-------------|--------------------------|----------------------| +| SNO-P1 (Installer) | T1552 | PE-SNO-P1-T-1, PE-SNO-P1-I-1 | +| SNO-P2 (Assisted Service) | T1078, T1552 | PE-SNO-P2-S-1, PE-SNO-P2-T-1 | +| SNO-P3 (MCO) | T1611 | PE-SNO-P3-T-1, PE-SNO-P3-E-1 | +| SNO-P4 (CEO) | T1562 | PE-SNO-P4-T-1, PE-SNO-P4-D-1 | +| SNO-P5 (Bootstrap Agent) | T1611, T1552 | PE-SNO-P5-E-1, PE-SNO-P5-I-1 | +| SNO-P6 (Kubelet) | T1610 | PE-SNO-P6-E-1, PE-SNO-P6-D-1 | +| SNO-DS1 (install-config) | T1552 | PE-SNO-DS1-I-1 | +| SNO-DS2 (Discovery ISO) | T1195 | PE-SNO-DS2-T-1, PE-SNO-DS2-I-1 | +| SNO-DS3 (etcd Data) | T1485, T1552 | PE-SNO-DS3-T-1, PE-SNO-DS3-I-1, PE-SNO-DS3-D-1 | +| SNO-DS4 (Credentials) | T1552 | PE-SNO-DS4-I-1 | +| SNO-EE1 (Admin) | T1078 | PE-SNO-EE1-S-1, PE-SNO-EE1-R-1 | +| SNO-EE2 (Assisted Service Cloud) | T1195 | PE-SNO-EE2-S-1 | + +## References + +- MITRE ATT&CK Enterprise: +- MITRE ATT&CK Containers: +- MITRE ATT&CK Mitigations: diff --git a/plugins/threat-model/references/owasp-reference.md b/plugins/threat-model/references/owasp-reference.md new file mode 100644 index 00000000..155af281 --- /dev/null +++ b/plugins/threat-model/references/owasp-reference.md @@ -0,0 +1,117 @@ +# OWASP Top 10:2025 Reference + +Quick reference for mapping findings to OWASP categories. + +Source: + +## OWASP Top 10:2025 Categories + +| ID | Category | Description | Common CWEs | +|----|----------|-------------|-------------| +| **A01** | Broken Access Control | Missing or improper access restrictions; SSRF now included | CWE-22, CWE-284, CWE-285, CWE-352, CWE-918 | +| **A02** | Security Misconfiguration | Insecure defaults, open cloud storage, verbose errors, missing hardening | CWE-16, CWE-209, CWE-548 | +| **A03** | Software Supply Chain Failures | Vulnerable dependencies, compromised build pipelines, untrusted sources | CWE-426, CWE-494, CWE-829 | +| **A04** | Cryptographic Failures | Weak crypto, exposed keys, missing encryption, improper certificate validation | CWE-259, CWE-327, CWE-328, CWE-330, CWE-331 | +| **A05** | Injection | SQL, NoSQL, OS command, LDAP, XSS injection | CWE-20, CWE-74, CWE-77, CWE-78, CWE-79, CWE-89 | +| **A06** | Insecure Design | Missing threat modeling, insecure architecture patterns | CWE-73, CWE-183, CWE-209, CWE-312 | +| **A07** | Authentication Failures | Broken auth, credential stuffing, weak passwords, session issues | CWE-287, CWE-384, CWE-522, CWE-798 | +| **A08** | Software or Data Integrity Failures | Code/data without integrity verification, insecure deserialization | CWE-345, CWE-353, CWE-426, CWE-502 | +| **A09** | Security Logging and Alerting Failures | Missing audit logs, unmonitored security events | CWE-117, CWE-223, CWE-532, CWE-778 | +| **A10** | Mishandling of Exceptional Conditions | Improper error handling, fail-open logic, unhandled exceptions | CWE-252, CWE-280, CWE-388, CWE-754, CWE-755 | + +--- + +## Pattern to OWASP Mapping + +| Security Pattern | OWASP | MITRE | CWE | +|-----------------|-------|-------|-----| +| **Command Injection** | A05 | T1059 | CWE-78 | +| Shell exec with unsanitized input | A05 | T1059 | CWE-78 | +| fmt.Sprintf() building shell commands | A05 | T1059 | CWE-78 | +| **Hardcoded Credentials** | A07 | T1552 | CWE-798 | +| Passwords in source code | A07 | T1552 | CWE-798 | +| API keys in config files | A07 | T1552 | CWE-798 | +| **Broken Access Control** | A01 | T1078 | CWE-284 | +| Missing authorization checks | A01 | T1078 | CWE-285 | +| Path traversal | A01 | T1083 | CWE-22 | +| SSRF | A01 | T1046 | CWE-918 | +| **Cryptographic Failures** | A04 | T1573 | CWE-327 | +| Weak algorithms (MD5, SHA1) | A04 | T1573 | CWE-328 | +| Disabled TLS verification | A04 | T1557 | CWE-295 | +| InsecureSkipVerify = true | A04 | T1557 | CWE-295 | +| **Security Misconfiguration** | A02 | T1562 | CWE-16 | +| Debug mode in production | A02 | T1562 | CWE-489 | +| Privileged containers | A02 | T1611 | CWE-250 | +| **Insecure Deserialization** | A08 | T1059 | CWE-502 | +| pickle.loads(), yaml.load() | A08 | T1059 | CWE-502 | +| **Logging Sensitive Data** | A09 | T1005 | CWE-532 | +| Credentials in logs | A09 | T1005 | CWE-532 | +| **Missing Error Handling** | A10 | - | CWE-754 | +| Unchecked error returns | A10 | - | CWE-252 | +| Fail-open logic | A10 | T1562 | CWE-636 | + +--- + +## TNF-Specific OWASP Mappings + +| TNF Component | Risk | OWASP | MITRE | CWE | DFD Elements | PE-* IDs | +|---------------|------|-------|-------|-----|--------------|----------| +| BMC credentials in install-config | Hardcoded secrets | A07 | T1552 | CWE-798 | P1, DS1, DF1, DF2 | PE-P1-I-1, PE-DS1-I-1 | +| BMC password in shell command | Command injection | A05 | T1059 | CWE-78 | P5, DF9 | PE-P5-T-1, PE-P5-I-1 | +| Credentials in CIB XML | Plaintext storage | A04 | T1552 | CWE-312 | DS3, DF7 | PE-DS3-I-1, PE-DF7-I-1 | +| InsecureSkipVerify on BMC | Crypto failure | A04 | T1557 | CWE-295 | P8, DF10 | PE-P8-S-1, PE-DF10-T-1 | +| Privileged TNF setup pods | Misconfiguration | A02 | T1611 | CWE-250 | P3, P4, P5 | PE-P4-E-1, PE-P5-E-1 | +| fencing-credentials Secret | Access control | A01 | T1552 | CWE-284 | DS2, DF4 | PE-DS2-I-1, PE-DS2-T-1 | +| Corosync unencrypted | Crypto failure | A04 | T1557 | CWE-319 | EE3, DF12 | PE-EE3-S-1 | +| PCS token generation | Auth weakness | A07 | T1078 | CWE-330 | P3, DS4, DF5 | PE-P3-S-1, PE-DS4-I-1 | +| Credentials in CLI args | Info exposure | A07 | T1552 | CWE-214 | P6, P8, DF9 | PE-DF9-I-1, PE-P8-I-1 | +| No fencing audit trail | Logging failure | A09 | - | CWE-778 | P5, P6 | PE-P5-R-1, PE-P1-R-1 | + +--- + +## TNA-Specific OWASP Mappings + +| TNA Component | Risk | OWASP | MITRE | CWE | DFD Elements | PE-* IDs | +|---------------|------|-------|-------|-----|--------------|----------| +| Arbiter taint as sole scheduling protection | Misconfiguration | A02 | T1562 | CWE-250 | TNA-P3 | PE-TNA-P3-T-1 | +| Worker ignition token | Credential exposure | A07 | T1552 | CWE-798 | TNA-DS6 | PE-TNA-DS6-I-1 | +| Worker lateral movement to control plane | Access control | A01 | T1021 | CWE-284 | TNA-P5, TNA-DS5 | PE-TNA-P5-E-1 | +| etcd data on compromised node | Crypto failure | A04 | T1552 | CWE-312 | TNA-DS5 | PE-TNA-DS5-I-1 | +| Rogue worker CSR approval | Auth failure | A07 | T1078 | CWE-287 | TNA-P5, TNA-DS6 | PE-TNA-P5-S-1 | +| No arbiter taint drift alert | Logging failure | A09 | - | CWE-778 | TNA-P3 | PE-TNA-P3-T-1 | + +--- + +## SNO-Specific OWASP Mappings + +| SNO Component | Risk | OWASP | MITRE | CWE | DFD Elements | PE-* IDs | +|---------------|------|-------|-------|-----|--------------|----------| +| install-config with pull secret + offline token | Credential exposure | A07 | T1552 | CWE-798 | SNO-DS1 | PE-SNO-DS1-I-1 | +| Single-member etcd (no quorum) | Data loss / total compromise | A06 | T1485 | CWE-312 | SNO-DS3 | PE-SNO-DS3-I-1, PE-SNO-DS3-D-1 | +| UnsafeScalingStrategy bypasses quorum checks | Insecure design | A06 | T1562 | CWE-636 | SNO-P4 | PE-SNO-P4-D-1 | +| Bootstrap-in-place runs privileged on bare metal | Misconfiguration | A02 | T1611 | CWE-250 | SNO-P5 | PE-SNO-P5-E-1 | +| Master schedulable (workloads on control plane) | Access control | A01 | T1610 | CWE-284 | SNO-P6 | PE-SNO-P6-E-1 | +| Kubeconfig + kubeadmin-password on admin workstation | Credential exposure | A07 | T1552 | CWE-522 | SNO-DS4 | PE-SNO-DS4-I-1 | +| Discovery ISO integrity | Supply chain | A03 | T1195 | CWE-494 | SNO-DS2 | PE-SNO-DS2-T-1 | + +--- + +## LVMS-Specific OWASP Mappings + +| LVMS Component | Risk | OWASP | MITRE | CWE | DFD Elements | PE-* IDs | +|----------------|------|-------|-------|-----|--------------|----------| +| TBD | TBD | TBD | TBD | TBD | TBD | TBD | + +--- + +## OWASP Cheat Sheets + +| Topic | URL | +|-------|-----| +| OS Command Injection | | +| Secrets Management | | +| Input Validation | | +| Cryptographic Storage | | +| Error Handling | | +| Docker Security | | +| Kubernetes Security | | diff --git a/plugins/threat-model/references/report-templates.md b/plugins/threat-model/references/report-templates.md new file mode 100644 index 00000000..7fd0ebec --- /dev/null +++ b/plugins/threat-model/references/report-templates.md @@ -0,0 +1,220 @@ +# Report Templates + +Shared report naming conventions and output templates used by all threat-model skills. +Each skill substitutes its own topology name (TNA, TNF, SNO, LVMS) where indicated. + +--- + +## Report Naming Convention + +- **Full threat model**: `PR-THREAT-MODEL-.md` +- **Individual vuln**: `VULN-PR-.md` + +## Report Format: Threat Model + +```markdown +# PR # Threat Analysis: + +**Document Version**: 1.0 +**Date**: YYYY-MM-DD +**Classification**: Internal - Security Sensitive +**Repository**: +**Topology**: +**PR Author**: +**PR URL**: + +--- + +## Executive Summary + +[Brief overview of the PR and key security findings] + +### Findings Summary + +| Severity | Count | Summary | +|----------|-------|---------| +| Critical | X | [brief] | +| High | X | [brief] | +| Medium | X | [brief] | +| Low | X | [brief] | + +--- + +## Change Overview + +[What this PR does, its purpose, and security-relevant changes] + +--- + +## Affected Files + +| File | Changes | Security Relevance | +|------|---------|-------------------| +| path/to/file.go | +X/-Y lines | [relevance] | + +--- + +## DFD Impact Analysis + +This PR affects the following elements in the Data Flow Diagram +(see -THREAT-MODEL.md): + +### Affected DFD Elements + +| Element | Name | Impact | Trust Boundary | +|---------|------|--------|----------------| +| P# | [process name] | [what changed] | TB# | +| DS# | [store name] | [what changed] | TB# | +| DF# | [flow description] | [what changed] | TB#->TB# | + +### Trust Boundary Crossings + +[Describe any trust boundaries crossed by the changed code] + +### Per-Element STRIDE + +| Element | S | T | R | I | D | E | Notes | +|---------|---|---|---|---|---|---|-------| +| P# | - | - | - | - | - | - | [Processes: all 6] | +| DS# | N/A | - | N/A | - | - | N/A | [Data Stores: T, I, D] | +| DF# | N/A | - | N/A | - | - | N/A | [Data Flows: T, I, D] | +| EE# | - | N/A | - | N/A | N/A | N/A | [External Entities: S, R] | + +**Legend**: **X** = new threat found, **~** = existing threat modified, **-** = no impact, N/A = not applicable + +### Threat Model Cross-Reference + +| PR Finding | Existing PE-* ID | Status | +|------------|-----------------|--------| +| [finding] | PE-XX-X-X | Matches existing / New gap / Mitigated | + +--- + +## Threat Analysis + +### VULN-1: [Vulnerability Title] + +**Severity**: Critical/High/Medium/Low +**OWASP**: A##:2025 - Category Name +**MITRE ATT&CK**: T#### - Technique Name +**CWE**: CWE-### + +#### Affected Code + +**File**: `path/to/file.go:line` + +#### Description + +[Detailed description of the vulnerability] + +#### Attack Vector + +[How this could be exploited] + +#### Impact + +- **Confidentiality**: [impact] +- **Integrity**: [impact] +- **Availability**: [impact] + +#### Recommended Fix + +[Code showing the fix] + +--- + +## OWASP & MITRE ATT&CK Mapping + +| Finding | OWASP | MITRE | CWE | Status | +|---------|-------|-------|-----|--------| +| VULN-1 | A05:2025 Injection | T1059 | CWE-78 | Open | + +--- + +## Risk Assessment + +| Finding | Likelihood | Impact | Risk | +|---------|------------|--------|------| +| VULN-1 | High | Critical | Critical | + +--- + +## Recommendations + +### For Developers (Code Changes) + +#### Before Merge + +1. [Code fix or change required in this PR] + +#### After Merge + +1. [Follow-up code improvement, test addition, or refactor] + +### For Customers (Deployment & Operations) + +#### Configuration Hardening + +1. [Cluster configuration or hardening recommendation] + +#### Operational Practices + +1. [Monitoring, incident response, or day-2 operational guidance] + +--- + +## References + +- [OWASP Top 10:2025](https://owasp.org/Top10/2025/) +- [MITRE ATT&CK](https://attack.mitre.org/) +- [Relevant CVEs, CWEs, documentation] +``` + +## Report Format: Individual Vulnerability (for Critical/High findings) + +```markdown +# Security Ticket: [Vulnerability Title] + +**Ticket ID**: VULN-PR- +**Severity**: CRITICAL/HIGH +**Component**: +**Status**: Open +**Created**: YYYY-MM-DD +**PR**: # + +## Summary + +[One paragraph summary] + +## Affected Code + +**File**: `path/to/file.go:lines` + +## Exploitation + +### Attack Flow + +[ASCII diagram or description of attack flow] + +### Exploit Examples + +[Code examples showing exploitation] + +## Impact + +[Detailed impact analysis] + +## Recommended Fix + +### For Developers + +[Code showing the fix with explanation] + +### For Customers + +[Deployment hardening, configuration changes, or monitoring guidance] + +## References + +- [CWE, OWASP, other references] +``` diff --git a/plugins/threat-model/skills/lvms/SKILL.md b/plugins/threat-model/skills/lvms/SKILL.md new file mode 100644 index 00000000..8295e495 --- /dev/null +++ b/plugins/threat-model/skills/lvms/SKILL.md @@ -0,0 +1,238 @@ +--- +name: threat-model:lvms +description: Analyze a PR for LVMS (LVM Storage) security threats with STRIDE/DFD analysis, MITRE ATT&CK and OWASP mapping +disable-model-invocation: true +allowed-tools: Read, Grep, Glob, Write, Edit, Bash, WebFetch +argument-hint: "" +--- + +# LVMS PR Threat Analysis + +Analyze a pull request for security threats against the **LVMS (LVM Storage)** operator, map to MITRE ATT&CK, and generate a formal report. + +> **Note**: The LVMS DFD model (`dfd-elements-lvms.md`) is not yet defined. This skill will perform general security analysis, ShellCheck scanning, and MITRE/OWASP mapping. DFD element mapping and STRIDE cross-referencing will be available once the DFD model is created. + +## Reference Files + +Bundled with this skill: + +- `dfd-elements-lvms.md` — LVMS DFD element catalog (placeholder — not yet modeled) + +Shared references (in `$PLUGIN_DIR/references/`): + +- `mitre-reference.md` — MITRE ATT&CK lookup with DFD element mappings +- `owasp-reference.md` — OWASP Top 10:2025 mapping with DFD element cross-references +- `mitre-findings-template.md` — Template for cumulative findings tracker + +Discovered at runtime from the workspace: + +- `$THREAT_MODEL_DIR/LVMS-THREAT-MODEL.md` — LVMS formal threat model (when available) +- `$FINDINGS_FILE` — LVMS findings tracker (created from template on first use) + +## Workspace Discovery + +Before starting analysis, discover the workspace layout. + +### Discovery Steps + +1. **Find workspace root**: Walk upward from `$PWD` until a directory containing `repos/` is found. If no parent qualifies, fall back to checking whether the current git repo sits inside a `repos/` directory: + + ```bash + d="$PWD" + while [ "$d" != "/" ]; do + if [ -d "$d/repos" ]; then + echo "$d" + break + fi + d="$(dirname "$d")" + done + if [ "$d" = "/" ]; then + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" + if [ -n "$repo_root" ] && [ "$(basename "$(dirname "$repo_root")")" = "repos" ]; then + echo "$(dirname "$(dirname "$repo_root")")" + fi + fi + ``` + +2. **Set workspace paths**: Once the workspace root (`WORKSPACE`) is found: + - **Repos directory**: `$WORKSPACE/repos/` + - **Threat model**: Look for `LVMS-THREAT-MODEL.md` in: + - `$WORKSPACE/repos/lvm-operator/docs/` + - `$WORKSPACE/docs/` + - The current directory + - **Report output**: If `$REPORT_DIR` is already set in the environment, use it directly. Otherwise, write reports to the same directory where the threat model is found. If not found, write to `$WORKSPACE/reports/` (create if needed). + - **Findings tracker**: `$WORKSPACE/.claude/skills/threat-model/mitre-findings-lvms.md` — initialized from `$PLUGIN_DIR/references/mitre-findings-template.md` on first use. + +3. **Validate workspace**: Warn the user if: + - No `repos/` directory is found + - Required repos for the target PR are not cloned locally + - DFD model is not yet defined (analysis proceeds without DFD mapping) + +### Path Variables Reference + +| Variable | Description | Example | +|----------|-------------|---------| +| `$WORKSPACE` | Root directory containing `repos/` | `/home/user/Projects/lvm-workspace` | +| `$REPOS` | Repos directory | `$WORKSPACE/repos` | +| `$THREAT_MODEL_DIR` | Directory containing formal threat model | `$REPOS/lvm-operator/docs` | +| `$REPORT_DIR` | Directory for generated reports | Same as `$THREAT_MODEL_DIR` or `$WORKSPACE/reports` | +| `$FINDINGS_FILE` | LVMS findings tracker | `$WORKSPACE/.claude/skills/threat-model/mitre-findings-lvms.md` | + +### Findings File + +Each threat-model skill writes to its own findings file (`mitre-findings-tnf.md`, `mitre-findings-tna.md`, `mitre-findings-sno.md`, `mitre-findings-lvms.md`), so no file locking is required during concurrent execution. + +**Append protocol** (use in step 12): + +```bash +FINDINGS_FILE="$WORKSPACE/.claude/skills/threat-model/mitre-findings-lvms.md" + +mkdir -p "$(dirname "$FINDINGS_FILE")" +cp -n "RESOLVED_TEMPLATE_PATH" "$FINDINGS_FILE" + +cat >> "$FINDINGS_FILE" <<'FINDINGS_BLOCK' + +## LVMS — REPO PR #NUMBER (YYYY-MM-DD) + +| Technique ID | Technique Name | Finding | Severity | Status | Notes | +|--------------|----------------|---------|----------|--------|-------| +| T#### | Name | VULN-# | Severity | Open | Description | + +--- +FINDINGS_BLOCK +``` + +Substitute `RESOLVED_TEMPLATE_PATH` with the absolute path to `$PLUGIN_DIR/references/mitre-findings-template.md` (resolved from this skill's directory). Fill in `REPO`, `NUMBER`, `YYYY-MM-DD`, and the table rows from the current analysis. + +## Input Formats + +### Option 1: PR Number Only + +```text +/threat-model:lvms 2271 +``` + +Detects the repository from the current working directory. + +### Option 2: GitHub PR URL + +```text +/threat-model:lvms https://github.com/openshift/lvm-operator/pull/2271 +``` + +### Option 3: Explicit repo and PR + +```text +/threat-model:lvms lvm-operator 2271 +``` + +## Parsing Logic + +1. **If input is a URL** (contains `github.com`): + - Extract org/repo/PR from: `https://github.com///pull/` + +2. **If input is a single number**: + - Detect repo from current directory path + - Look for pattern `repos//` in the working directory + +3. **If input is ` `**: + - Use provided repo name + - Look up org from the repository mapping table + +## Repository Mapping + +| Repo | GitHub Org | +|------|------------| +| lvm-operator | openshift | +| origin | openshift | + +## Instructions + +1. **Discover workspace** using the Workspace Discovery steps above +2. **Parse input** to determine org, repo, and PR number +3. **Fetch PR details** using `gh pr view --repo /` or WebFetch +4. **Get changed files** with `gh pr diff --repo /` or WebFetch +5. **Run ShellCheck** on any shell scripts in the changed files (see Automated Scanner section) +6. **Analyze all changes** for security-relevant patterns (see Security Patterns) +7. **Map to DFD elements** — if `dfd-elements-lvms.md` has been populated, map changed files to affected DFD elements. If not yet modeled, skip and note in the report. +8. **Apply per-element STRIDE** to affected elements (if DFD is available) and cross-reference against `$THREAT_MODEL_DIR/LVMS-THREAT-MODEL.md` (if found) +9. **Combine findings** from ShellCheck + AI analysis + DFD/STRIDE analysis +10. **Map findings to MITRE ATT&CK** techniques (see `$PLUGIN_DIR/references/mitre-reference.md`) +11. **Generate report** at `$REPORT_DIR/` +12. **Append findings to tracker** — follow the Append Protocol to write a findings block to `$FINDINGS_FILE` + +--- + +## Automated Scanner: ShellCheck + +ShellCheck is available in RHEL/Fedora repos (`dnf install ShellCheck`) - no external downloads required. + +### Installation Check + +```bash +command -v shellcheck >/dev/null && echo "shellcheck: installed" || echo "shellcheck: NOT installed (run: dnf install ShellCheck)" +``` + +### Running ShellCheck + +```bash +shellcheck -f json +shellcheck -S warning +shellcheck -s bash +``` + +### Security-Relevant ShellCheck Codes + +| Code | Severity | Security Relevance | MITRE | +|------|----------|-------------------|-------| +| SC2086 | Warning | Unquoted variable - command injection risk | T1059 | +| SC2091 | Warning | Command in $() used as condition - injection | T1059 | +| SC2046 | Warning | Unquoted command substitution | T1059 | +| SC2012 | Info | Parsing ls output - can be exploited | T1059 | +| SC2029 | Warning | ssh command with unescaped variables | T1059 | +| SC2087 | Warning | Unquoted heredoc - variable expansion | T1059 | +| SC2155 | Warning | Declare/assign separately to avoid masking errors | - | +| SC2164 | Warning | cd without error-exit guard - path traversal risk | T1083 | + +--- + +## Optional External Scanners + +| Tool | Source | Risks | Mitigations | +|------|--------|-------|-------------| +| **Semgrep** | pip/GitHub | Fetches rules from semgrep.dev; may send telemetry | Use `--offline` mode with local rules | +| **Gitleaks** | GitHub releases | Binary from external source | Verify checksums; use container image | +| **gosec** | GitHub/go install | Binary from external source | Verify checksums; audit source | + +--- + +## Security Patterns to Detect + +| Category | Patterns | MITRE | Severity | +|----------|----------|-------|----------| +| Command Injection | shell exec, os.system, subprocess, fmt.Sprintf with shell | T1059 | Critical | +| Credentials | hardcoded secrets, API keys, tokens, passwords in code | T1552 | Critical | +| Privilege Escalation | setuid, capabilities, privileged containers, sudo, nsenter | T1548 | High | +| Authentication | auth bypass, weak validation, token handling flaws | T1078 | High | +| Crypto Weakness | weak algorithms, hardcoded keys, disabled TLS verify | T1573 | High | +| Path Traversal | unsanitized file paths, symlink attacks | T1083 | Medium | +| Container Escape | host mounts, hostPID, hostNetwork, privileged mode | T1611 | Critical | +| Logging Exposure | sensitive data in logs, credential printing | T1005 | Medium | +| SSRF/Network | unvalidated URLs, exposed internal endpoints | T1046 | Medium | +| Deserialization | unsafe unmarshal, pickle, yaml.load | T1059 | High | + +## LVMS DFD Element Mapping + +> **Not yet modeled.** Once `dfd-elements-lvms.md` is populated with DFD elements, add code path mapping and trust boundary crossing tables here. + +When the DFD is available, the analysis should follow the same STRIDE methodology as TNF/TNA: + +- Map changed files to affected DFD elements +- Apply per-element STRIDE questions +- Cross-reference against `LVMS-THREAT-MODEL.md` + +--- + +## Report Output + +Use report templates from `$PLUGIN_DIR/references/report-templates.md`. Set `` to **LVMS** when filling in the templates. diff --git a/plugins/threat-model/skills/lvms/dfd-elements-lvms.md b/plugins/threat-model/skills/lvms/dfd-elements-lvms.md new file mode 100644 index 00000000..26f7e16e --- /dev/null +++ b/plugins/threat-model/skills/lvms/dfd-elements-lvms.md @@ -0,0 +1,17 @@ +# LVMS (LVM Storage) DFD Elements + +This file will contain the Data Flow Diagram element catalog for the LVMS topology. + +## Status + +**Not yet defined.** This is a placeholder for future DFD modeling. + +## Expected Structure + +Once modeled, this file should define: + +- **Processes (P#)**: Components involved in LVMS operation (operator, vg-manager, topolvm-controller, topolvm-node) +- **Data Stores (DS#)**: LVM volume groups, PVs, thin pools, device state +- **Data Flows (DF#)**: Communication paths between components (CSI gRPC, k8s API, LVM commands) +- **Trust Boundaries (TB#)**: Security isolation boundaries (k8s API, node host, LVM subsystem) +- **External Entities (EE#)**: Users, workloads consuming PVCs, block devices diff --git a/plugins/threat-model/skills/sno/SKILL.md b/plugins/threat-model/skills/sno/SKILL.md new file mode 100644 index 00000000..e5e300b0 --- /dev/null +++ b/plugins/threat-model/skills/sno/SKILL.md @@ -0,0 +1,305 @@ +--- +name: threat-model:sno +description: Analyze a PR for SNO (Single Node OpenShift) security threats with STRIDE/DFD analysis, MITRE ATT&CK and OWASP mapping +disable-model-invocation: true +allowed-tools: Read, Grep, Glob, Write, Edit, Bash, WebFetch +argument-hint: "" +--- + +# SNO PR Threat Analysis + +Analyze a pull request for security threats against the **SNO (Single Node OpenShift)** topology, map to MITRE ATT&CK, and generate a formal report. + +This skill focuses on SNO-specific DFD elements, trust boundaries, and code paths. For TNF analysis, use `/threat-model:tnf`. For TNA, use `/threat-model:tna`. + +## Reference Files + +Bundled with this skill: + +- `dfd-elements-sno.md` — SNO DFD element catalog (SNO-P1–P6, SNO-DS1–DS6, SNO-DF1–DF10, SNO-TB1–TB3) + +Shared references (in `$PLUGIN_DIR/references/`): + +- `mitre-reference.md` — MITRE ATT&CK lookup with DFD element mappings +- `owasp-reference.md` — OWASP Top 10:2025 mapping with DFD element cross-references +- `mitre-findings-template.md` — Template for cumulative findings tracker + +Discovered at runtime from the workspace: + +- `$THREAT_MODEL_DIR/SNO-THREAT-MODEL.md` — SNO formal threat model (when available) +- `$FINDINGS_FILE` — SNO findings tracker (created from template on first use) + +## Workspace Discovery + +Before starting analysis, discover the workspace layout. + +### Discovery Steps + +1. **Find workspace root**: Walk upward from `$PWD` until a directory containing `repos/` is found. If no parent qualifies, fall back to checking whether the current git repo sits inside a `repos/` directory: + + ```bash + d="$PWD" + while [ "$d" != "/" ]; do + if [ -d "$d/repos" ]; then + echo "$d" + break + fi + d="$(dirname "$d")" + done + if [ "$d" = "/" ]; then + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" + if [ -n "$repo_root" ] && [ "$(basename "$(dirname "$repo_root")")" = "repos" ]; then + echo "$(dirname "$(dirname "$repo_root")")" + fi + fi + ``` + +2. **Set workspace paths**: Once the workspace root (`WORKSPACE`) is found: + - **Repos directory**: `$WORKSPACE/repos/` + - **Threat model**: Look for `SNO-THREAT-MODEL.md` in: + - `$WORKSPACE/repos/sno-deploy/docs/` + - `$WORKSPACE/docs/` + - The current directory + - **Report output**: If `$REPORT_DIR` is already set in the environment, use it directly. Otherwise, write reports to the same directory where the threat model is found. If not found, write to `$WORKSPACE/reports/` (create if needed). + - **Findings tracker**: `$WORKSPACE/.claude/skills/threat-model/mitre-findings-sno.md` — initialized from `$PLUGIN_DIR/references/mitre-findings-template.md` on first use. + +3. **Validate workspace**: Warn the user if: + - No `repos/` directory is found + - Required repos for the target PR are not cloned locally + - Formal threat model file (`SNO-THREAT-MODEL.md`) is not found (analysis can still proceed, but cross-referencing will be skipped) + +### Path Variables Reference + +| Variable | Description | Example | +|----------|-------------|---------| +| `$WORKSPACE` | Root directory containing `repos/` | `/home/user/Projects/sno-dev-env` | +| `$REPOS` | Repos directory | `$WORKSPACE/repos` | +| `$THREAT_MODEL_DIR` | Directory containing formal threat model | `$REPOS/sno-deploy/docs` | +| `$REPORT_DIR` | Directory for generated reports | Same as `$THREAT_MODEL_DIR` or `$WORKSPACE/reports` | +| `$FINDINGS_FILE` | SNO findings tracker | `$WORKSPACE/.claude/skills/threat-model/mitre-findings-sno.md` | + +### Findings File + +Each threat-model skill writes to its own findings file (`mitre-findings-tnf.md`, `mitre-findings-tna.md`, `mitre-findings-sno.md`, `mitre-findings-lvms.md`), so no file locking is required during concurrent execution. + +**Append protocol** (use in step 12): + +```bash +FINDINGS_FILE="$WORKSPACE/.claude/skills/threat-model/mitre-findings-sno.md" + +mkdir -p "$(dirname "$FINDINGS_FILE")" +cp -n "RESOLVED_TEMPLATE_PATH" "$FINDINGS_FILE" + +cat >> "$FINDINGS_FILE" <<'FINDINGS_BLOCK' + +## SNO — REPO PR #NUMBER (YYYY-MM-DD) + +| Technique ID | Technique Name | Finding | Severity | Status | Notes | +|--------------|----------------|---------|----------|--------|-------| +| T#### | Name | VULN-# | Severity | Open | Description | + +--- +FINDINGS_BLOCK +``` + +Substitute `RESOLVED_TEMPLATE_PATH` with the absolute path to `$PLUGIN_DIR/references/mitre-findings-template.md` (resolved from this skill's directory). Fill in `REPO`, `NUMBER`, `YYYY-MM-DD`, and the table rows from the current analysis. + +## Input Formats + +### Option 1: PR Number Only + +```text +/threat-model:sno 10498 +``` + +Detects the repository from the current working directory. + +### Option 2: GitHub PR URL + +```text +/threat-model:sno https://github.com/openshift/installer/pull/10498 +``` + +### Option 3: Explicit repo and PR + +```text +/threat-model:sno installer 10498 +``` + +## Parsing Logic + +1. **If input is a URL** (contains `github.com`): + - Extract org/repo/PR from: `https://github.com///pull/` + +2. **If input is a single number**: + - Detect repo from current directory path + - Look for pattern `repos//` in the working directory + +3. **If input is ` `**: + - Use provided repo name + - Look up org from the repository mapping table + +## Repository Mapping + +| Repo | GitHub Org | +|------|------------| +| installer | openshift | +| machine-config-operator | openshift | +| cluster-etcd-operator | openshift | +| assisted-service | openshift | +| origin | openshift | +| dev-scripts | openshift-metal3 | +| release | openshift | + +## Instructions + +1. **Discover workspace** using the Workspace Discovery steps above +2. **Parse input** to determine org, repo, and PR number +3. **Fetch PR details** using `gh pr view --repo /` or WebFetch +4. **Get changed files** with `gh pr diff --repo /` or WebFetch +5. **Run ShellCheck** on any shell scripts in the changed files (see Automated Scanner section) +6. **Analyze all changes** for security-relevant patterns (see Security Patterns) +7. **Map to DFD elements** — identify which DFD elements are affected using the SNO mapping table below and `dfd-elements-sno.md` +8. **Apply per-element STRIDE** to affected elements and cross-reference against `$THREAT_MODEL_DIR/SNO-THREAT-MODEL.md` (if found) +9. **Combine findings** from ShellCheck + AI analysis + DFD/STRIDE analysis +10. **Map findings to MITRE ATT&CK** techniques (see `$PLUGIN_DIR/references/mitre-reference.md`) +11. **Generate report** at `$REPORT_DIR/` +12. **Append findings to tracker** — follow the Append Protocol to write a findings block to `$FINDINGS_FILE` + +--- + +## Automated Scanner: ShellCheck + +ShellCheck is available in RHEL/Fedora repos (`dnf install ShellCheck`) - no external downloads required. + +### Installation Check + +```bash +command -v shellcheck >/dev/null && echo "shellcheck: installed" || echo "shellcheck: NOT installed (run: dnf install ShellCheck)" +``` + +### Running ShellCheck + +```bash +shellcheck -f json +shellcheck -S warning +shellcheck -s bash +``` + +### Security-Relevant ShellCheck Codes + +| Code | Severity | Security Relevance | MITRE | +|------|----------|-------------------|-------| +| SC2086 | Warning | Unquoted variable - command injection risk | T1059 | +| SC2091 | Warning | Command in $() used as condition - injection | T1059 | +| SC2046 | Warning | Unquoted command substitution | T1059 | +| SC2012 | Info | Parsing ls output - can be exploited | T1059 | +| SC2029 | Warning | ssh command with unescaped variables | T1059 | +| SC2087 | Warning | Unquoted heredoc - variable expansion | T1059 | +| SC2155 | Warning | Declare/assign separately to avoid masking errors | - | +| SC2164 | Warning | cd without error-exit guard - path traversal risk | T1083 | + +--- + +## Optional External Scanners + +| Tool | Source | Risks | Mitigations | +|------|--------|-------|-------------| +| **Semgrep** | pip/GitHub | Fetches rules from semgrep.dev; may send telemetry | Use `--offline` mode with local rules | +| **Gitleaks** | GitHub releases | Binary from external source | Verify checksums; use container image | +| **gosec** | GitHub/go install | Binary from external source | Verify checksums; audit source | + +--- + +## Security Patterns to Detect + +| Category | Patterns | MITRE | Severity | +|----------|----------|-------|----------| +| Command Injection | shell exec, os.system, subprocess, fmt.Sprintf with shell | T1059 | Critical | +| Credentials | hardcoded secrets, API keys, tokens, passwords in code | T1552 | Critical | +| Privilege Escalation | setuid, capabilities, privileged containers, sudo, nsenter | T1548 | High | +| Authentication | auth bypass, weak validation, token handling flaws | T1078 | High | +| Crypto Weakness | weak algorithms, hardcoded keys, disabled TLS verify | T1573 | High | +| Path Traversal | unsanitized file paths, symlink attacks | T1083 | Medium | +| Container Escape | host mounts, hostPID, hostNetwork, privileged mode | T1611 | Critical | +| Logging Exposure | sensitive data in logs, credential printing | T1005 | Medium | +| SSRF/Network | unvalidated URLs, exposed internal endpoints | T1046 | Medium | +| Deserialization | unsafe unmarshal, pickle, yaml.load | T1059 | High | + +## SNO DFD Element Mapping + +See `dfd-elements-sno.md` for the full element catalog. + +### Code Path to DFD Element + +| Code Path Pattern | DFD Element | STRIDE Focus | +|-------------------|-------------|--------------| +| `installer/pkg/types/installconfig.go` (IsSingleNodeOpenShift, BootstrapInPlace) | SNO-P1 (Installer) | T, D | +| `installer/pkg/asset/machines/master.go` (SingleReplicaTopologyMode) | SNO-P1 | T, D | +| `installer/pkg/types/validation/installconfig.go` (BootstrapInPlace) | SNO-P1 | T | +| `installer/data/data/bootstrap/bootstrap-in-place/` | SNO-P5 (Bootstrap Agent) | T, I, E | +| `assisted-service/internal/common/common.go` (IsSingleNodeCluster) | SNO-P2 (Assisted Service) | T | +| `assisted-service/internal/cluster/validator.go` (SNO validations) | SNO-P2 | S, T | +| `assisted-service/internal/host/validator.go` (SNO host checks) | SNO-P2 | T | +| `cluster-etcd-operator/pkg/operator/ceohelpers/bootstrap.go` (UnsafeScalingStrategy) | SNO-P4 (CEO) | T, D | +| `cluster-etcd-operator/pkg/operator/ceohelpers/control_plane_topology.go` | SNO-P4 | T, D | +| `machine-config-operator/` (MachineConfig, kubelet config) | SNO-P3 (MCO) | T, E | +| `sno-deploy/day_two/templates/` (DU policy generation, workload partitioning) | SNO-P3 (MCO), SNO-DS6 | T | +| `origin/test/extended/` (SNO test code) | Test | - | + +### Trust Boundary Crossings + +When a PR modifies code that crosses a trust boundary, apply additional scrutiny: + +| Boundary Crossing | Code Indicators | Key Threats | +|-------------------|-----------------|-------------| +| SNO-TB1->SNO-TB2 (Admin -> Assisted Service) | install-config, offline-token, pull-secret, API calls to console.redhat.com | I (credential exposure), T (config tampering) | +| SNO-TB2->SNO-TB3 (Assisted Service -> SNO Node) | Discovery ISO generation, ignition delivery, host inventory | T (ISO tampering), I (ignition secrets), E (privileged bootstrap) | +| SNO-TB1->SNO-TB3 (Admin -> SNO Node) | oc/kubectl, kubeconfig, kubeadmin-password | S (admin impersonation), I (credential theft) | + +### Per-Element STRIDE for PR Analysis + +For each affected DFD element, ask these questions: + +**Processes (all 6 STRIDE categories)**: + +- **S**: Can the process be impersonated? Are auth checks adequate? +- **T**: Can inputs/outputs be modified? Is data validated? +- **R**: Are actions auditable? Are logs adequate and redacted? +- **I**: Does it handle secrets? Are they protected in transit/at rest? +- **D**: Can it be crashed or blocked? What happens on failure? (Critical for SNO — no failover) +- **E**: Does it run with minimal privilege? Can it be abused for escalation? + +**Data Stores (T, I, D)**: + +- **T**: Can stored data be modified by unauthorized parties? +- **I**: Is sensitive data encrypted? Who can read it? +- **D**: Can the store be corrupted or deleted? (Single etcd member — total loss) + +**Data Flows (T, I, D)**: + +- **T**: Can data in transit be modified? Is integrity verified? +- **I**: Is the channel encrypted? Are credentials visible? +- **D**: Can the flow be interrupted or flooded? + +**External Entities (S, R)**: + +- **S**: Can the entity be impersonated? Is authentication enforced? +- **R**: Can the entity deny having performed an action? Are interactions logged? + +### Cross-Referencing the Threat Model + +After identifying per-element threats, check against `$THREAT_MODEL_DIR/SNO-THREAT-MODEL.md`: + +1. Search for relevant `PE-SNO--*` IDs in the Per-Element STRIDE Analysis section +2. If a PR introduces a **new** threat not covered by existing PE-* entries, flag it as a gap +3. If a PR **mitigates** an existing PE-* threat, note it as a positive finding +4. If a PR **worsens** an existing PE-* threat, flag with elevated severity + +If the formal threat model file is not found, skip cross-referencing and note this in the report. + +--- + +## Report Output + +Use report templates from `$PLUGIN_DIR/references/report-templates.md`. Set `` to **SNO** when filling in the templates. diff --git a/plugins/threat-model/skills/sno/dfd-elements-sno.md b/plugins/threat-model/skills/sno/dfd-elements-sno.md new file mode 100644 index 00000000..f742d96b --- /dev/null +++ b/plugins/threat-model/skills/sno/dfd-elements-sno.md @@ -0,0 +1,118 @@ +# SNO DFD Element Reference + +> **Topology**: SNO (Single Node OpenShift) only. For TNF elements see dfd-elements-tnf.md; for TNA see dfd-elements-tna.md. + +Quick reference for mapping PR changes to Data Flow Diagram elements defined in +the SNO threat model. + +> **ID Namespace**: SNO elements use `SNO-` prefixed IDs (SNO-P1, SNO-DS1, etc.) to avoid ambiguity with TNF/TNA element IDs. + +## Processes + +| ID | Name | Code Reference | STRIDE | +|----|------|---------------|--------| +| SNO-P1 | Installer (bootstrap-in-place) | `installer/pkg/types/installconfig.go` (IsSingleNodeOpenShift, BootstrapInPlace), `installer/pkg/asset/machines/master.go` (SingleReplicaTopologyMode) | S, T, R, I, D, E | +| SNO-P2 | Assisted Service API | `assisted-service/internal/common/common.go` (IsSingleNodeCluster), `assisted-service/internal/cluster/validator.go` | S, T, R, I, D, E | +| SNO-P3 | MCO / MCD | `machine-config-operator/` (MachineConfig delivery, kubelet config) | S, T, R, I, D, E | +| SNO-P4 | CEO (single-member etcd) | `cluster-etcd-operator/pkg/operator/ceohelpers/bootstrap.go` (SingleReplicaTopologyMode, UnsafeScalingStrategy) | S, T, R, I, D, E | +| SNO-P5 | Bootstrap-in-Place Agent | `installer/data/data/bootstrap/bootstrap-in-place/` (install-to-disk.service) | S, T, R, I, D, E | +| SNO-P6 | Kubelet (master + worker role) | Single node runs both control-plane and worker workloads | S, T, R, I, D, E | + +## Data Stores + +| ID | Name | Location | STRIDE | +|----|------|----------|--------| +| SNO-DS1 | install-config | `~/.sno-deploy//` or installer workdir — contains pull secret, SSH key, offline token | T, I, D | +| SNO-DS2 | Discovery ISO | Generated by Assisted Service, booted on target node | T, I, D | +| SNO-DS3 | etcd Data (single member) | Single etcd pod on the SNO node — all K8s secrets, no quorum redundancy | T, I, D | +| SNO-DS4 | Kubeconfig / Credentials | `~/.sno-deploy//creds/` — kubeconfig, kubeadmin-password | T, I, D | +| SNO-DS5 | Ignition Config | Bootstrap-in-place ignition written to installation disk | T, I, D | +| SNO-DS6 | MachineConfig State | On-disk machine config applied by MCD | T, I, D | + +## Data Flows + +| ID | From | To | Protocol | STRIDE | +|----|------|----|----------|--------| +| SNO-DF1 | SNO-EE1 (Admin) | SNO-P1 (Installer) | CLI / install-config YAML | T, I | +| SNO-DF2 | SNO-P7 (aicli) | SNO-P2 (Assisted Service) | HTTPS REST API (console.redhat.com); offline token passed as `AI_OFFLINETOKEN` env var | T, I, D | +| SNO-DF3 | SNO-P2 (Assisted Service) | SNO-DS2 (Discovery ISO) | ISO generation + download | T, I | +| SNO-DF4 | SNO-DS2 (Discovery ISO) | SNO-P5 (Bootstrap Agent) | Boot from ISO, discover hardware | T, I | +| SNO-DF5 | SNO-P5 (Bootstrap Agent) | SNO-P2 (Assisted Service) | HTTPS (host inventory, progress) | T, I | +| SNO-DF6 | SNO-P5 (Bootstrap Agent) | SNO-DS5 (Ignition) | Write ignition to installation disk | T, I | +| SNO-DF7 | SNO-P4 (CEO) | SNO-DS3 (etcd) | localhost gRPC (single member, no peer traffic) | T, I, D | +| SNO-DF8 | SNO-P3 (MCO) | SNO-P6 (Kubelet) | MachineConfig delivery | T, I | +| SNO-DF9 | SNO-EE1 (Admin) | SNO-P6 (Kubelet) | oc/kubectl via API server | T, I | +| SNO-DF10 | SNO-P3 (MCO) | SNO-DS6 (MachineConfig) | Workload partitioning + RT kernel configs written to disk | T, I | + +## External Entities + +| ID | Name | Protocol | STRIDE | +|----|------|----------|--------| +| SNO-EE1 | User / Cluster Admin | oc/kubectl, sno-deploy CLI | S, R | +| SNO-EE2 | Assisted Installer Service (console.redhat.com) | HTTPS REST API | S, R | + +## Trust Boundaries + +| ID | Boundary | Elements Inside | +|----|----------|----------------| +| SNO-TB1 | Admin Workstation | SNO-EE1, SNO-DS1, SNO-DS4 | +| SNO-TB2 | Assisted Service (cloud) | SNO-P2, SNO-EE2 | +| SNO-TB3 | SNO Node (single node = control plane + worker) | SNO-P3, SNO-P4, SNO-P5, SNO-P6, SNO-DS3, SNO-DS5, SNO-DS6 | + +--- + +## High-Risk Elements + +Elements with the most significant threats: + +| Element | Key Risks | Notes | +|---------|-----------|-------| +| SNO-DS3 (etcd Data) | Single-member etcd — no quorum redundancy; node compromise exposes all K8s secrets | No HA failover; total data loss on node failure | +| SNO-P5 (Bootstrap Agent) | Runs privileged on bare metal; writes ignition to disk; trusts Assisted Service API | install-to-disk.service runs as root | +| SNO-DS1 (install-config) | Contains pull secret, SSH key, offline token in plaintext on admin workstation | `~/.sno-deploy/` directory | +| SNO-P6 (Kubelet) | Master + worker on same node — no workload isolation; control-plane compromise = full cluster compromise | Workloads schedulable on master by design (ShouldMastersBeSchedulable returns true) | +| SNO-P4 (CEO) | UnsafeScalingStrategy — bypasses quorum safety checks entirely | "Not officially tested or supported" per code comments | + +--- + +## SNO-Specific Characteristics + +SNO differs fundamentally from multi-node topologies (TNF, TNA): + +- **Single point of failure**: One node is the entire cluster — no HA, no quorum, no failover +- **Bootstrap-in-place**: No separate bootstrap node; the target node bootstraps itself via `install-to-disk.service` +- **Master is schedulable**: `ShouldMastersBeSchedulable()` always returns true for SNO — workloads run on control plane +- **UnsafeScalingStrategy**: CEO uses scaling strategy that bypasses all quorum/fault-tolerance checks +- **Assisted Installer**: Cluster creation goes through `console.redhat.com` Assisted Service API (external trust boundary) +- **No VIPs required**: SNO skips API/Ingress VIP validation since there's only one node +- **Validation suppressions**: SNO skips MTU, majority-group connectivity, and multi-host validations +- **Workload partitioning**: DU configuration pins management workloads to specific CPU cores via MachineConfig; RT kernel applied as MachineConfig + +## SNO Does NOT Have + +- No multi-node quorum or HA failover +- No Pacemaker / Corosync / STONITH / fencing (TNF-specific) +- No arbiter node (TNA-specific) +- No worker nodes (master runs all workloads) +- No separate bootstrap node (uses bootstrap-in-place) +- No peer etcd traffic (single member, localhost only) +- No VIP management (single node = single IP) + +Any PR analysis mentioning these components is **not applicable** to SNO topology. + +--- + +## Developer Tooling Risks (not customer-facing) + +The `sno-deploy/` scripts in edge-tooling use **aicli** (`quay.io/karmab/aicli:latest`) to automate SNO cluster creation for dev/test. This is **not** part of any product install path — customers use the installer, assisted-service UI, or agent-based installer directly. + +These risks only affect developers running `sno-deploy/`: + +| Risk | Detail | +|------|--------| +| TLS verification disabled | `verify_ssl = False` + `urllib3.disable_warnings` — token exchange to `sso.redhat.com` vulnerable to MITM | +| Tokens cached in plaintext | Bearer token → `~/.aicli/token.txt`, offline token → `~/.aicli/offlinetoken.txt` | +| Offline token in env var | `AI_OFFLINETOKEN` visible via `podman inspect` or `/proc//environ` | +| Container runs as root | No `USER` directive in Dockerfile | +| Unpinned image tag | `:latest` with no digest verification; Alpine base + pip deps also unpinned | +| Unsupported tool | README states "not supported in any way by Red Hat" | diff --git a/plugins/threat-model/skills/tna/SKILL.md b/plugins/threat-model/skills/tna/SKILL.md new file mode 100644 index 00000000..3520619a --- /dev/null +++ b/plugins/threat-model/skills/tna/SKILL.md @@ -0,0 +1,342 @@ +--- +name: threat-model:tna +description: Analyze a PR for TNA (Two-Node Arbiter) security threats with STRIDE/DFD analysis, MITRE ATT&CK and OWASP mapping +disable-model-invocation: true +allowed-tools: Read, Grep, Glob, Write, Edit, Bash, WebFetch +argument-hint: "" +--- + +# TNA PR Threat Analysis + +Analyze a pull request for security threats against the **TNA (Two-Node Arbiter)** topology, map to MITRE ATT&CK, and generate a formal report. + +This skill focuses on TNA-specific DFD elements, trust boundaries, and code paths. For TNF analysis, use `/threat-model:tnf`. + +## Reference Files + +Bundled with this skill: + +- `dfd-elements-tna.md` — TNA DFD element catalog (TNA-P1, TNA-P3–P5, TNA-DS5–DS6, TNA-TB1–TB3) + +Shared references (in `$PLUGIN_DIR/references/`): + +- `mitre-reference.md` — MITRE ATT&CK lookup with DFD element mappings +- `owasp-reference.md` — OWASP Top 10:2025 mapping with DFD element cross-references +- `mitre-findings-template.md` — Template for cumulative findings tracker + +Discovered at runtime from the workspace: + +- `$THREAT_MODEL_DIR/TNA-THREAT-MODEL.md` — TNA formal threat model with DFD and per-element STRIDE analysis +- `$FINDINGS_FILE` — TNA findings tracker (created from template on first use) + +## Workspace Discovery + +Before starting analysis, discover the workspace layout. + +### Discovery Steps + +1. **Find workspace root**: Walk upward from `$PWD` until a directory containing `repos/` is found. If no parent qualifies, fall back to checking whether the current git repo sits inside a `repos/` directory: + + ```bash + d="$PWD" + while [ "$d" != "/" ]; do + if [ -d "$d/repos" ]; then + echo "$d" + break + fi + d="$(dirname "$d")" + done + if [ "$d" = "/" ]; then + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" + if [ -n "$repo_root" ] && [ "$(basename "$(dirname "$repo_root")")" = "repos" ]; then + echo "$(dirname "$(dirname "$repo_root")")" + fi + fi + ``` + +2. **Set workspace paths**: Once the workspace root (`WORKSPACE`) is found: + - **Repos directory**: `$WORKSPACE/repos/` + - **Threat model**: Look for `TNA-THREAT-MODEL.md` in: + - `$WORKSPACE/repos/two-node-toolbox/docs/` + - `$WORKSPACE/docs/` + - The current directory + - **Report output**: If `$REPORT_DIR` is already set in the environment, use it directly. Otherwise, write reports to the same directory where the threat model is found. If not found, write to `$WORKSPACE/reports/` (create if needed). + - **Findings tracker**: `$WORKSPACE/.claude/skills/threat-model/mitre-findings-tna.md` — initialized from `$PLUGIN_DIR/references/mitre-findings-template.md` on first use. + +3. **Validate workspace**: Warn the user if: + - No `repos/` directory is found + - Required repos for the target PR are not cloned locally + - Threat model reference file is not found (analysis can still proceed, but DFD cross-referencing will be skipped) + +### Path Variables Reference + +| Variable | Description | Example | +|----------|-------------|---------| +| `$WORKSPACE` | Root directory containing `repos/` | `/home/user/Projects/tna-dev-env` | +| `$REPOS` | Repos directory | `$WORKSPACE/repos` | +| `$THREAT_MODEL_DIR` | Directory containing formal threat model | `$REPOS/two-node-toolbox/docs` | +| `$REPORT_DIR` | Directory for generated reports | Same as `$THREAT_MODEL_DIR` or `$WORKSPACE/reports` | +| `$FINDINGS_FILE` | TNA findings tracker | `$WORKSPACE/.claude/skills/threat-model/mitre-findings-tna.md` | + +### Findings File + +Each threat-model skill writes to its own findings file (`mitre-findings-tnf.md`, `mitre-findings-tna.md`, `mitre-findings-sno.md`, `mitre-findings-lvms.md`), so no file locking is required during concurrent execution. + +**Append protocol** (use in step 12): + +```bash +FINDINGS_FILE="$WORKSPACE/.claude/skills/threat-model/mitre-findings-tna.md" + +mkdir -p "$(dirname "$FINDINGS_FILE")" +cp -n "RESOLVED_TEMPLATE_PATH" "$FINDINGS_FILE" + +cat >> "$FINDINGS_FILE" <<'FINDINGS_BLOCK' + +## TNA — REPO PR #NUMBER (YYYY-MM-DD) + +| Technique ID | Technique Name | Finding | Severity | Status | Notes | +|--------------|----------------|---------|----------|--------|-------| +| T#### | Name | VULN-# | Severity | Open | Description | + +--- +FINDINGS_BLOCK +``` + +Substitute `RESOLVED_TEMPLATE_PATH` with the absolute path to `$PLUGIN_DIR/references/mitre-findings-template.md` (resolved from this skill's directory). Fill in `REPO`, `NUMBER`, `YYYY-MM-DD`, and the table rows from the current analysis. + +## Input Formats + +### Option 1: PR Number Only + +```text +/threat-model:tna 1437 +``` + +Detects the repository from the current working directory. Must be inside a repo under `$REPOS//`. + +### Option 2: GitHub PR URL + +```text +/threat-model:tna https://github.com/openshift/cluster-etcd-operator/pull/1437 +``` + +Extracts org, repo, and PR number from the URL automatically. + +### Option 3: Explicit repo and PR + +```text +/threat-model:tna cluster-etcd-operator 1437 +``` + +Specify repo name and PR number explicitly. + +## Parsing Logic + +1. **If input is a URL** (contains `github.com`): + - Extract org/repo/PR from: `https://github.com///pull/` + +2. **If input is a single number**: + - Detect repo from current directory path + - Look for pattern `repos//` in the working directory + - Use the repo's configured remote to determine the org + +3. **If input is ` `**: + - Use provided repo name + - Look up org from the repository mapping table + +## Repository Mapping + +| Repo | GitHub Org | +|------|------------| +| assisted-service | openshift | +| cluster-etcd-operator | openshift | +| machine-config-operator | openshift | +| installer | openshift | +| cluster-baremetal-operator | openshift | +| origin | openshift | +| dev-scripts | openshift-metal3 | +| release | openshift | +| enhancements | openshift | +| openshift-docs | openshift | + +## Instructions + +1. **Discover workspace** using the Workspace Discovery steps above +2. **Parse input** to determine org, repo, and PR number +3. **Fetch PR details** using `gh pr view --repo /` or WebFetch +4. **Get changed files** with `gh pr diff --repo /` or WebFetch +5. **Run ShellCheck** on any shell scripts in the changed files (see Automated Scanner section) +6. **Analyze all changes** for security-relevant patterns (see Security Patterns) +7. **Map to DFD elements** — identify which DFD elements are affected using the TNA mapping table below and `dfd-elements-tna.md` +8. **Apply per-element STRIDE** to affected elements and cross-reference against `$THREAT_MODEL_DIR/TNA-THREAT-MODEL.md` (if found) +9. **Combine findings** from ShellCheck + AI analysis + DFD/STRIDE analysis +10. **Map findings to MITRE ATT&CK** techniques (see `$PLUGIN_DIR/references/mitre-reference.md`) +11. **Generate report** at `$REPORT_DIR/` +12. **Append findings to tracker** — follow the Append Protocol to write a findings block to `$FINDINGS_FILE` + +--- + +## Automated Scanner: ShellCheck + +ShellCheck is available in RHEL/Fedora repos (`dnf install ShellCheck`) - no external downloads required. + +### Installation Check + +```bash +command -v shellcheck >/dev/null && echo "shellcheck: installed" || echo "shellcheck: NOT installed (run: dnf install ShellCheck)" +``` + +### Running ShellCheck + +```bash +shellcheck -f json +shellcheck -S warning +shellcheck -s bash +``` + +### Security-Relevant ShellCheck Codes + +| Code | Severity | Security Relevance | MITRE | +|------|----------|-------------------|-------| +| SC2086 | Warning | Unquoted variable - command injection risk | T1059 | +| SC2091 | Warning | Command in $() used as condition - injection | T1059 | +| SC2046 | Warning | Unquoted command substitution | T1059 | +| SC2012 | Info | Parsing ls output - can be exploited | T1059 | +| SC2029 | Warning | ssh command with unescaped variables | T1059 | +| SC2087 | Warning | Unquoted heredoc - variable expansion | T1059 | +| SC2155 | Warning | Declare/assign separately to avoid masking errors | - | +| SC2164 | Warning | cd without without error-exit guard - path traversal risk | T1083 | + +### Include in Report + +Add ShellCheck results under Automated Scanner Results: + +```markdown +## Automated Scanner Results + +### ShellCheck + +**Tool**: ShellCheck (from RHEL repos) +**Version**: X.X.X + +| Code | Severity | File | Line | Message | +|------|----------|------|------|---------| +| SC2086 | warning | script.sh | 42 | Double quote to prevent globbing and word splitting | +``` + +If ShellCheck is not installed, note: *Not installed. Install with: `dnf install ShellCheck`* +If no shell scripts in PR, note: *No shell scripts in this PR - skipped.* + +--- + +## Optional External Scanners + +The following scanners provide additional coverage but require **external downloads**. Use at your own discretion. + +| Tool | Source | Risks | Mitigations | +|------|--------|-------|-------------| +| **Semgrep** | pip/GitHub | Fetches rules from semgrep.dev; may send telemetry | Use `--offline` mode with local rules | +| **Gitleaks** | GitHub releases | Binary from external source | Verify checksums; use container image | +| **gosec** | GitHub/go install | Binary from external source | Verify checksums; audit source | + +```bash +command -v semgrep >/dev/null && echo "semgrep: installed" || echo "semgrep: not installed (external)" +command -v gitleaks >/dev/null && echo "gitleaks: installed" || echo "gitleaks: not installed (external)" +command -v gosec >/dev/null && echo "gosec: installed" || echo "gosec: not installed (external)" +``` + +--- + +## Security Patterns to Detect + +| Category | Patterns | MITRE | Severity | +|----------|----------|-------|----------| +| Command Injection | shell exec, os.system, subprocess, fmt.Sprintf with shell | T1059 | Critical | +| Credentials | hardcoded secrets, API keys, tokens, passwords in code | T1552 | Critical | +| Privilege Escalation | setuid, capabilities, privileged containers, sudo, nsenter | T1548 | High | +| Authentication | auth bypass, weak validation, token handling flaws | T1078 | High | +| Crypto Weakness | weak algorithms, hardcoded keys, disabled TLS verify | T1573 | High | +| Path Traversal | unsanitized file paths, symlink attacks | T1083 | Medium | +| Container Escape | host mounts, hostPID, hostNetwork, privileged mode | T1611 | Critical | +| Logging Exposure | sensitive data in logs, credential printing | T1005 | Medium | +| SSRF/Network | unvalidated URLs, exposed internal endpoints | T1046 | Medium | +| Deserialization | unsafe unmarshal, pickle, yaml.load | T1059 | High | + +## TNA DFD Element Mapping + +See `dfd-elements-tna.md` for the full element catalog. + +### Code Path to DFD Element + +| Code Path Pattern | DFD Element | STRIDE Focus | +|-------------------|-------------|--------------| +| `installer/pkg/asset/machines/arbiter*` | TNA-P1 (Installer) | T, D | +| `installer/pkg/asset/ignition/machine/arbiter*` | TNA-P1, TNA-DS6 | T, I | +| `installer/pkg/types/installconfig.go` (IsArbiterEnabled) | TNA-P1 | T, D | +| `installer/pkg/types/validation/installconfig.go` (arbiter) | TNA-P1 | T | +| `assisted-service/internal/common/common.go` (arbiter) | TNA-P1 | T | +| `assisted-service/internal/cluster/validator.go` (arbiter role) | TNA-P1 | S, T | +| `machine-config-operator/manifests/arbiter*` | TNA-P3 (MCO) | T, D | +| `machine-config-operator/templates/arbiter/` | TNA-P3 | T, E | +| `cluster-etcd-operator/pkg/operator/ceohelpers/control_plane_topology.go` | TNA-P4 (CEO) | T, D | +| `cluster-etcd-operator/pkg/operator/ceohelpers/multiselector_lister.go` | TNA-P4 | T, D | +| `cluster-etcd-operator/pkg/operator/configobservation/*replicas*` | TNA-P4 | T, D | +| `origin/test/extended/two_node/arbiter_topology.go` | Test | - | +| `origin/test/extended/two_node/tna_recovery.go` | Test | - | + +### Trust Boundary Crossings + +When a PR modifies code that crosses a trust boundary, apply additional scrutiny: + +| Boundary Crossing | Code Indicators | Key Threats | +|-------------------|-----------------|-------------| +| TNA-TB1->TNA-TB2 (Admin -> K8s API) | install-config, oc commands | S (admin impersonation), T (config tampering) | +| TNA-TB2 internal (MCO -> kubelet) | arbiter MCP, kubelet config, taint | T (taint removal), D (misconfiguration) | +| TNA-TB2->TNA-TB3 (K8s API -> Worker) | CSR approval, ignition endpoint | S (rogue CSR), E (lateral movement) | + +### Per-Element STRIDE for PR Analysis + +For each affected DFD element, ask these questions: + +**Processes (all 6 STRIDE categories)**: + +- **S**: Can the process be impersonated? Are auth checks adequate? +- **T**: Can inputs/outputs be modified? Is data validated? +- **R**: Are actions auditable? Are logs adequate and redacted? +- **I**: Does it handle secrets? Are they protected in transit/at rest? +- **D**: Can it be crashed or blocked? What happens on failure? +- **E**: Does it run with minimal privilege? Can it be abused for escalation? + +**Data Stores (T, I, D)**: + +- **T**: Can stored data be modified by unauthorized parties? +- **I**: Is sensitive data encrypted? Who can read it? +- **D**: Can the store be corrupted or deleted? + +**Data Flows (T, I, D)**: + +- **T**: Can data in transit be modified? Is integrity verified? +- **I**: Is the channel encrypted? Are credentials visible? +- **D**: Can the flow be interrupted or flooded? + +**External Entities (S, R)**: + +- **S**: Can the entity be impersonated? Is authentication enforced? +- **R**: Can the entity deny having performed an action? Are interactions logged? + +### Cross-Referencing the Threat Model + +After identifying per-element threats, check against `$THREAT_MODEL_DIR/TNA-THREAT-MODEL.md`: + +1. Search for relevant `PE--*` IDs in the Per-Element STRIDE Analysis section +2. If a PR introduces a **new** threat not covered by existing PE-* entries, flag it as a gap +3. If a PR **mitigates** an existing PE-* threat, note it as a positive finding +4. If a PR **worsens** an existing PE-* threat, flag with elevated severity + +If the formal threat model file is not found, skip cross-referencing and note this in the report. + +--- + +## Report Output + +Use report templates from `$PLUGIN_DIR/references/report-templates.md`. Set `` to **TNA** when filling in the templates. diff --git a/plugins/threat-model/skills/tna/dfd-elements-tna.md b/plugins/threat-model/skills/tna/dfd-elements-tna.md new file mode 100644 index 00000000..a0544aa0 --- /dev/null +++ b/plugins/threat-model/skills/tna/dfd-elements-tna.md @@ -0,0 +1,68 @@ +# TNA DFD Element Reference + +> **Topology**: TNA (Two-Node with Arbiter) only. For TNF elements, see dfd-elements-tnf.md. + +Quick reference for mapping PR changes to Data Flow Diagram elements defined in +the TNA formal threat model (see `TNA-THREAT-MODEL.md` in the two-node-toolbox docs directory). + +> **ID Namespace**: TNA elements use `TNA-` prefixed IDs (TNA-P1, TNA-DS5, etc.) to avoid ambiguity with TNF element IDs (e.g., TNF P3 = Auth Job vs TNA-P3 = MCO). + +## Processes + +| ID | Name | Code Reference | STRIDE | +|----|------|---------------|--------| +| TNA-P1 | Installer (arbiter topology) | `installer/pkg/asset/machines/arbiter.go`, `installer/pkg/types/installconfig.go` | S, T, R, I, D, E | +| TNA-P3 | MCO (arbiter config) | `machine-config-operator/manifests/arbiter.machineconfigpool.yaml` | S, T, R, I, D, E | +| TNA-P4 | CEO (standard etcd) | `cluster-etcd-operator/pkg/operator/ceohelpers/control_plane_topology.go` | S, T, R, I, D, E | +| TNA-P5 | Worker Kubelet (optional, OCP 4.22+) | Worker node kubelet | S, T, R, I, D, E | + +## Data Stores + +| ID | Name | Location | STRIDE | +|----|------|----------|--------| +| TNA-DS5 | etcd Data | etcd pods on 2 masters + arbiter | T, I, D | +| TNA-DS6 | Worker Ignition / Credentials | Worker ignition endpoint | T, I, D | + +## External Entities + +| ID | Name | Protocol | STRIDE | +|----|------|----------|--------| +| TNA-EE1 | User / Cluster Admin | oc/kubectl | S, R | + +## Trust Boundaries + +| ID | Boundary | Elements Inside | +|----|----------|----------------| +| TNA-TB1 | Admin Network | TNA-EE1 | +| TNA-TB2 | Kubernetes API | TNA-P1, TNA-P3, TNA-P4, TNA-DS5 | +| TNA-TB3 | Worker Compute (optional) | TNA-P5, TNA-DS6 | + +--- + +## High-Risk Elements + +Elements with the most significant threats (from TNA-THREAT-MODEL.md): + +| Element | Key Risks | Related Threats | +|---------|-----------|-----------------| +| TNA-P3 (MCO) | Arbiter taint removal -> workload scheduling -> quorum loss | T-2, D-1 | +| TNA-DS5 (etcd Data) | Node compromise exposes all K8s secrets | I-1, T-1 | +| TNA-P5 (Worker Kubelet) | Lateral movement from worker to control plane | E-2 | + +--- + +## TNA Does NOT Have + +TNA uses standard Kubernetes etcd (3-member quorum via arbiter) and does **not** include any RHEL-HA / Pacemaker components. The following TNF elements have **no equivalent** in TNA: + +- No Pacemaker / Corosync / STONITH / fencing +- No BMC credentials or fencing-credentials secrets +- No podman-etcd OCF agent +- No PCSD authentication +- No privileged TNF setup jobs (TNF processes P2: CEO Controller, P3: Auth Job, P4: Setup Job, P5: Fencing Job handle Pacemaker setup and fencing and do not exist in TNA). Note: TNA reuses IDs P3–P5 for different components (TNA-P3: MCO, TNA-P4: CEO, TNA-P5: Worker Kubelet) +- No CIB (Cluster Information Base) +- No fence_redfish +- No Corosync network (UDP 5404-5406) +- No BMC network trust boundary + +Any PR analysis mentioning these components is **not applicable** to TNA topology. diff --git a/plugins/threat-model/skills/tnf/SKILL.md b/plugins/threat-model/skills/tnf/SKILL.md new file mode 100644 index 00000000..5ebf3943 --- /dev/null +++ b/plugins/threat-model/skills/tnf/SKILL.md @@ -0,0 +1,351 @@ +--- +name: threat-model:tnf +description: Analyze a PR for TNF (Two-Node Fencing) security threats with STRIDE/DFD analysis, MITRE ATT&CK and OWASP mapping +disable-model-invocation: true +allowed-tools: Read, Grep, Glob, Write, Edit, Bash, WebFetch +argument-hint: "" +--- + +# TNF PR Threat Analysis + +Analyze a pull request for security threats against the **TNF (Two-Node Fencing)** topology, map to MITRE ATT&CK, and generate a formal report. + +This skill focuses on TNF-specific DFD elements, trust boundaries, and code paths. For TNA analysis, use `/threat-model:tna`. + +## Reference Files + +Bundled with this skill: + +- `dfd-elements-tnf.md` — TNF DFD element catalog (P1-P8, DS1-DS5, DF1-DF12, TB1-TB6) + +Shared references (in `$PLUGIN_DIR/references/`): + +- `mitre-reference.md` — MITRE ATT&CK lookup with DFD element mappings +- `owasp-reference.md` — OWASP Top 10:2025 mapping with DFD element cross-references +- `mitre-findings-template.md` — Template for cumulative findings tracker + +Discovered at runtime from the workspace: + +- `$THREAT_MODEL_DIR/TNF-THREAT-MODEL.md` — TNF formal threat model with DFD and per-element STRIDE analysis +- `$FINDINGS_FILE` — TNF findings tracker (created from template on first use) + +## Workspace Discovery + +Before starting analysis, discover the workspace layout. + +### Discovery Steps + +1. **Find workspace root**: Walk upward from `$PWD` until a directory containing `repos/` is found. If no parent qualifies, fall back to checking whether the current git repo sits inside a `repos/` directory: + + ```bash + d="$PWD" + while [ "$d" != "/" ]; do + if [ -d "$d/repos" ]; then + echo "$d" + break + fi + d="$(dirname "$d")" + done + if [ "$d" = "/" ]; then + repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" + if [ -n "$repo_root" ] && [ "$(basename "$(dirname "$repo_root")")" = "repos" ]; then + echo "$(dirname "$(dirname "$repo_root")")" + fi + fi + ``` + +2. **Set workspace paths**: Once the workspace root (`WORKSPACE`) is found: + - **Repos directory**: `$WORKSPACE/repos/` + - **Threat model**: Look for `TNF-THREAT-MODEL.md` in: + - `$WORKSPACE/repos/two-node-toolbox/docs/` + - `$WORKSPACE/docs/` + - The current directory + - **Report output**: If `$REPORT_DIR` is already set in the environment, use it directly. Otherwise, write reports to the same directory where the threat model is found. If not found, write to `$WORKSPACE/reports/` (create if needed). + - **Findings tracker**: `$WORKSPACE/.claude/skills/threat-model/mitre-findings-tnf.md` — initialized from `$PLUGIN_DIR/references/mitre-findings-template.md` on first use. + +3. **Validate workspace**: Warn the user if: + - No `repos/` directory is found + - Required repos for the target PR are not cloned locally + - Threat model reference file is not found (analysis can still proceed, but DFD cross-referencing will be skipped) + +### Path Variables Reference + +| Variable | Description | Example | +|----------|-------------|---------| +| `$WORKSPACE` | Root directory containing `repos/` | `/home/user/Projects/tnf-dev-env` | +| `$REPOS` | Repos directory | `$WORKSPACE/repos` | +| `$THREAT_MODEL_DIR` | Directory containing formal threat model | `$REPOS/two-node-toolbox/docs` | +| `$REPORT_DIR` | Directory for generated reports | Same as `$THREAT_MODEL_DIR` or `$WORKSPACE/reports` | +| `$FINDINGS_FILE` | TNF findings tracker | `$WORKSPACE/.claude/skills/threat-model/mitre-findings-tnf.md` | + +### Findings File + +Each threat-model skill writes to its own findings file (`mitre-findings-tnf.md`, `mitre-findings-tna.md`, `mitre-findings-sno.md`, `mitre-findings-lvms.md`), so no file locking is required during concurrent execution. + +**Append protocol** (use in step 12): + +```bash +FINDINGS_FILE="$WORKSPACE/.claude/skills/threat-model/mitre-findings-tnf.md" + +mkdir -p "$(dirname "$FINDINGS_FILE")" +cp -n "RESOLVED_TEMPLATE_PATH" "$FINDINGS_FILE" + +cat >> "$FINDINGS_FILE" <<'FINDINGS_BLOCK' + +## TNF — REPO PR #NUMBER (YYYY-MM-DD) + +| Technique ID | Technique Name | Finding | Severity | Status | Notes | +|--------------|----------------|---------|----------|--------|-------| +| T#### | Name | VULN-# | Severity | Open | Description | + +--- +FINDINGS_BLOCK +``` + +Substitute `RESOLVED_TEMPLATE_PATH` with the absolute path to `$PLUGIN_DIR/references/mitre-findings-template.md` (resolved from this skill's directory). Fill in `REPO`, `NUMBER`, `YYYY-MM-DD`, and the table rows from the current analysis. + +## Input Formats + +### Option 1: PR Number Only + +```text +/threat-model:tnf 2136 +``` + +Detects the repository from the current working directory. Must be inside a repo under `$REPOS//`. + +### Option 2: GitHub PR URL + +```text +/threat-model:tnf https://github.com/ClusterLabs/resource-agents/pull/2136 +``` + +Extracts org, repo, and PR number from the URL automatically. + +### Option 3: Explicit repo and PR + +```text +/threat-model:tnf resource-agents 2136 +``` + +Specify repo name and PR number explicitly. + +## Parsing Logic + +1. **If input is a URL** (contains `github.com`): + - Extract org/repo/PR from: `https://github.com///pull/` + +2. **If input is a single number**: + - Detect repo from current directory path + - Look for pattern `repos//` in the working directory + - Use the repo's configured remote to determine the org + +3. **If input is ` `**: + - Use provided repo name + - Look up org from the repository mapping table + +## Repository Mapping + +| Repo | GitHub Org | +|------|------------| +| assisted-service | openshift | +| cluster-etcd-operator | openshift | +| machine-config-operator | openshift | +| installer | openshift | +| cluster-baremetal-operator | openshift | +| resource-agents | ClusterLabs | +| origin | openshift | +| dev-scripts | openshift-metal3 | +| release | openshift | +| enhancements | openshift | +| openshift-docs | openshift | +| pacemaker | ClusterLabs | + +## Instructions + +1. **Discover workspace** using the Workspace Discovery steps above +2. **Parse input** to determine org, repo, and PR number +3. **Fetch PR details** using `gh pr view --repo /` or WebFetch +4. **Get changed files** with `gh pr diff --repo /` or WebFetch +5. **Run ShellCheck** on any shell scripts in the changed files (see Automated Scanner section) +6. **Analyze all changes** for security-relevant patterns (see Security Patterns) +7. **Map to DFD elements** — identify which DFD elements are affected using the TNF mapping table below and `dfd-elements-tnf.md` +8. **Apply per-element STRIDE** to affected elements and cross-reference against `$THREAT_MODEL_DIR/TNF-THREAT-MODEL.md` (if found) +9. **Combine findings** from ShellCheck + AI analysis + DFD/STRIDE analysis +10. **Map findings to MITRE ATT&CK** techniques (see `$PLUGIN_DIR/references/mitre-reference.md`) +11. **Generate report** at `$REPORT_DIR/` +12. **Append findings to tracker** — follow the Append Protocol to write a findings block to `$FINDINGS_FILE` + +--- + +## Automated Scanner: ShellCheck + +ShellCheck is available in RHEL/Fedora repos (`dnf install ShellCheck`) - no external downloads required. + +### Installation Check + +```bash +command -v shellcheck >/dev/null && echo "shellcheck: installed" || echo "shellcheck: NOT installed (run: dnf install ShellCheck)" +``` + +### Running ShellCheck + +```bash +shellcheck -f json +shellcheck -S warning +shellcheck -s bash +``` + +### Security-Relevant ShellCheck Codes + +| Code | Severity | Security Relevance | MITRE | +|------|----------|-------------------|-------| +| SC2086 | Warning | Unquoted variable - command injection risk | T1059 | +| SC2091 | Warning | Command in $() used as condition - injection | T1059 | +| SC2046 | Warning | Unquoted command substitution | T1059 | +| SC2012 | Info | Parsing ls output - can be exploited | T1059 | +| SC2029 | Warning | ssh command with unescaped variables | T1059 | +| SC2087 | Warning | Unquoted heredoc - variable expansion | T1059 | +| SC2155 | Warning | Declare/assign separately to avoid masking errors | - | +| SC2164 | Warning | cd without error-exit guard - path traversal risk | T1083 | + +### Include in Report + +Add ShellCheck results under Automated Scanner Results: + +```markdown +## Automated Scanner Results + +### ShellCheck + +**Tool**: ShellCheck (from RHEL repos) +**Version**: X.X.X + +| Code | Severity | File | Line | Message | +|------|----------|------|------|---------| +| SC2086 | warning | podman-etcd | 42 | Double quote to prevent globbing and word splitting | +``` + +If ShellCheck is not installed, note: *Not installed. Install with: `dnf install ShellCheck`* +If no shell scripts in PR, note: *No shell scripts in this PR - skipped.* + +--- + +## Optional External Scanners + +The following scanners provide additional coverage but require **external downloads**. Use at your own discretion. + +| Tool | Source | Risks | Mitigations | +|------|--------|-------|-------------| +| **Semgrep** | pip/GitHub | Fetches rules from semgrep.dev; may send telemetry | Use `--offline` mode with local rules | +| **Gitleaks** | GitHub releases | Binary from external source | Verify checksums; use container image | +| **gosec** | GitHub/go install | Binary from external source | Verify checksums; audit source | + +```bash +command -v semgrep >/dev/null && echo "semgrep: installed" || echo "semgrep: not installed (external)" +command -v gitleaks >/dev/null && echo "gitleaks: installed" || echo "gitleaks: not installed (external)" +command -v gosec >/dev/null && echo "gosec: installed" || echo "gosec: not installed (external)" +``` + +--- + +## Security Patterns to Detect + +| Category | Patterns | MITRE | Severity | +|----------|----------|-------|----------| +| Command Injection | shell exec, os.system, subprocess, fmt.Sprintf with shell | T1059 | Critical | +| Credentials | hardcoded secrets, API keys, tokens, passwords in code | T1552 | Critical | +| Privilege Escalation | setuid, capabilities, privileged containers, sudo, nsenter | T1548 | High | +| Authentication | auth bypass, weak validation, token handling flaws | T1078 | High | +| Crypto Weakness | weak algorithms, hardcoded keys, disabled TLS verify | T1573 | High | +| Path Traversal | unsanitized file paths, symlink attacks | T1083 | Medium | +| Container Escape | host mounts, hostPID, hostNetwork, privileged mode | T1611 | Critical | +| Logging Exposure | sensitive data in logs, credential printing | T1005 | Medium | +| SSRF/Network | unvalidated URLs, exposed internal endpoints | T1046 | Medium | +| Deserialization | unsafe unmarshal, pickle, yaml.load | T1059 | High | + +## TNF DFD Element Mapping + +See `dfd-elements-tnf.md` for the full element catalog. + +### Code Path to DFD Element + +| Code Path Pattern | DFD Element | STRIDE Focus | +|-------------------|-------------|--------------| +| `assisted-service/internal/installcfg/` | P1 (Installer) | I, T, R | +| `assisted-service/internal/bminventory/` | P1 (Installer) | I, S, T | +| `assisted-service/models/fencing*` | P1 (Installer), DF1 | I, T | +| `cluster-etcd-operator/pkg/tnf/operator/` | P2 (CEO Controller) | S, D, E | +| `cluster-etcd-operator/pkg/tnf/auth/` | P3 (Auth Job) | S, E | +| `cluster-etcd-operator/pkg/tnf/setup/` | P4 (Setup Job) | T, I, E, D | +| `cluster-etcd-operator/pkg/tnf/fencing/` | P5 (Fencing Job) | I, T, R, E | +| `cluster-etcd-operator/pkg/tnf/pkg/pcs/fencing*` | P5, DF7, DF9 | I, T | +| `cluster-etcd-operator/pkg/tnf/pkg/pcs/cluster*` | P4, DS3 | T, D | +| `cluster-etcd-operator/pkg/tnf/pkg/tools/secrets*` | DS2, DF4 | I, T | +| `cluster-etcd-operator/pkg/tnf/pkg/tools/redact*` | P5, DF9 | I, R | +| `cluster-etcd-operator/pkg/tnf/pkg/exec/` | P3-P5 (nsenter) | E | +| `cluster-etcd-operator/bindata/tnfdeployment/job*` | P3-P5 (container spec) | E | +| `pacemaker/daemons/fenced/` | P6 (fenced) | S, I, D | +| `resource-agents/heartbeat/podman-etcd` | P7 (OCF Agent) | T, D, I | +| `resource-agents/heartbeat/podman` | P7 (OCF Agent) | T, D | +| `machine-config-operator/templates/*two-node*` | DS4 (PCSD setup) | T, E | +| `installer/pkg/asset/agent/manifests/fencing*` | P1, DS1, DF1, DF2 | I, T | + +### Trust Boundary Crossings + +When a PR modifies code that crosses a trust boundary, apply additional scrutiny: + +| Boundary Crossing | Code Indicators | Key Threats | +|-------------------|-----------------|-------------| +| TB2->TB3 (K8s -> Privileged Container) | Job specs, SA tokens, secret reads | E (escape), I (secret leak) | +| TB3->TB4 (Container -> Host) | nsenter calls, hostPID, privileged | E (host access), T (CIB tamper) | +| TB4->TB5 (Host -> BMC) | fence_redfish calls, Redfish URLs | S (MITM), I (credential exposure) | +| TB2->TB4 (Secrets -> CIB) | Secret->pcs command pipeline | I (plaintext creds in XML) | +| TB6 (Inter-Node) | Corosync config, PCSD auth | S (spoofing), D (quorum loss) | + +### Per-Element STRIDE for PR Analysis + +For each affected DFD element, ask these questions: + +**Processes (all 6 STRIDE categories)**: + +- **S**: Can the process be impersonated? Are auth checks adequate? +- **T**: Can inputs/outputs be modified? Is data validated? +- **R**: Are actions auditable? Are logs adequate and redacted? +- **I**: Does it handle secrets? Are they protected in transit/at rest? +- **D**: Can it be crashed or blocked? What happens on failure? +- **E**: Does it run with minimal privilege? Can it be abused for escalation? + +**Data Stores (T, I, D)**: + +- **T**: Can stored data be modified by unauthorized parties? +- **I**: Is sensitive data encrypted? Who can read it? +- **D**: Can the store be corrupted or deleted? + +**Data Flows (T, I, D)**: + +- **T**: Can data in transit be modified? Is integrity verified? +- **I**: Is the channel encrypted? Are credentials visible? +- **D**: Can the flow be interrupted or flooded? + +**External Entities (S, R)**: + +- **S**: Can the entity be impersonated? Is authentication enforced? +- **R**: Can the entity deny having performed an action? Are interactions logged? + +### Cross-Referencing the Threat Model + +After identifying per-element threats, check against `$THREAT_MODEL_DIR/TNF-THREAT-MODEL.md`: + +1. Search for relevant `PE--*` IDs in the Per-Element STRIDE Analysis section +2. If a PR introduces a **new** threat not covered by existing PE-* entries, flag it as a gap +3. If a PR **mitigates** an existing PE-* threat, note it as a positive finding +4. If a PR **worsens** an existing PE-* threat, flag with elevated severity + +If the formal threat model file is not found, skip cross-referencing and note this in the report. + +--- + +## Report Output + +Use report templates from `$PLUGIN_DIR/references/report-templates.md`. Set `` to **TNF** when filling in the templates. diff --git a/plugins/threat-model/skills/tnf/dfd-elements-tnf.md b/plugins/threat-model/skills/tnf/dfd-elements-tnf.md new file mode 100644 index 00000000..5cddeccf --- /dev/null +++ b/plugins/threat-model/skills/tnf/dfd-elements-tnf.md @@ -0,0 +1,120 @@ +# TNF DFD Element Reference + +> **Topology**: TNF (Two-Node with Fencing) only. For TNA elements, see dfd-elements-tna.md. + +Quick reference for mapping PR changes to Data Flow Diagram elements defined in +the TNF formal threat model (see `TNF-THREAT-MODEL.md` in the two-node-toolbox docs directory). + +## Processes + +| ID | Name | Code Reference | STRIDE | +|----|------|---------------|--------| +| P1 | Installer / Assisted Service | `assisted-service/internal/installcfg/builder/builder.go` | S, T, R, I, D, E | +| P2 | CEO TNF Controller | `cluster-etcd-operator/pkg/tnf/operator/starter.go` | S, T, R, I, D, E | +| P3 | TNF Auth Job | `cluster-etcd-operator/pkg/tnf/auth/runner.go` | S, T, R, I, D, E | +| P4 | TNF Setup Job | `cluster-etcd-operator/pkg/tnf/setup/runner.go` | S, T, R, I, D, E | +| P5 | TNF Fencing Job | `cluster-etcd-operator/pkg/tnf/fencing/runner.go` | S, T, R, I, D, E | +| P6 | Pacemaker fenced | `pacemaker/daemons/fenced/` | S, T, R, I, D, E | +| P7 | podman-etcd OCF Agent | `resource-agents/heartbeat/podman-etcd` | S, T, R, I, D, E | +| P8 | fence_redfish | `/usr/sbin/fence_redfish` (RPM) | S, T, R, I, D, E | + +## Data Stores + +| ID | Name | Location | STRIDE | +|----|------|----------|--------| +| DS1 | install-config.yaml | Installer host filesystem | T, I, D | +| DS2 | K8s Secrets | `openshift-etcd` namespace | T, I, D | +| DS3 | Pacemaker CIB | `/var/lib/pacemaker/cib/cib.xml` | T, I, D | +| DS4 | PCSD Token | `/var/lib/pcsd/token` | T, I, D | +| DS5 | etcd Data | etcd containers / persistent storage | T, I, D | + +## Data Flows + +| ID | From | To | Data | STRIDE | +|----|------|----|------|--------| +| DF1 | EE1 | P1 | BMC credentials | T, I, D | +| DF2 | P1 | DS1 | Credentials in install-config | T, I, D | +| DF3 | P1 | DS2 | Credentials as K8s Secrets | T, I, D | +| DF4 | DS2 | P5 | Secret read by fencing job | T, I, D | +| DF5 | P3 | DS4 | ClusterID as PCSD token | T, I, D | +| DF6 | P4 | DS3 | Cluster + etcd config to CIB | T, I, D | +| DF7 | P4/P5 | DS3 | STONITH credentials to CIB | T, I, D | +| DF8 | DS3 | P6 | Credentials read for fencing | T, I, D | +| DF9 | P6 | P8 | Credentials as CLI args | T, I, D | +| DF10 | P8 | EE2 | HTTPS Basic Auth to BMC | T, I, D | +| DF11 | P7 | DS5 | etcd container lifecycle | T, I, D | +| DF12 | EE3 | P4/P6 | Membership + CIB replication | T, I, D | + +## External Entities + +| ID | Name | Protocol | STRIDE | +|----|------|----------|--------| +| EE1 | User / Cluster Admin | REST API / YAML | S, R | +| EE2 | BMC Controllers | Redfish HTTPS | S, R | +| EE3 | Corosync Network | UDP 5404-5406 | S, R | + +## Trust Boundaries + +| ID | Boundary | Elements Inside | +|----|----------|----------------| +| TB1 | External Network | EE1, EE2 | +| TB2 | Kubernetes API | P1, P2, DS2 | +| TB3 | Privileged Container (nsenter) | P3, P4, P5 | +| TB4 | Host / Pacemaker | P6, P7, P8, DS3, DS4, DS5 | +| TB5 | BMC Network | EE2 | +| TB6 | Inter-Node (Corosync) | EE3 | + +--- + +## High-Risk Elements + +Elements with the most existing per-element threats (from TNF-THREAT-MODEL.md): + +| Element | Threat Count | Key Risks | Related VULNs | +|---------|-------------|-----------|---------------| +| P5 (Fencing Job) | 4 | Credential exposure, shell injection, privilege | VULN-1, VULN-3 | +| P4 (Setup Job) | 4 | CIB tampering, credential storage, privilege | VULN-3, VULN-4 | +| P8 (fence_redfish) | 4 | MITM, credential exposure, BMC spoofing | VULN-1, VULN-2 | +| DS3 (CIB) | 3 | Plaintext credentials, fencing disable, corruption | VULN-4 | +| P6 (fenced) | 4 | Spoofed requests, credential relay, malicious fencing | VULN-1 | +| P3 (Auth Job) | 3 | Predictable token, privilege escalation | VULN-3, VULN-5 | +| DF9 (P6->P8) | 2 | Credentials in /proc/cmdline | VULN-1 | +| DF10 (P8->EE2) | 3 | MITM, credential interception | VULN-2 | + +--- + +## Credential Flow Path + +The full path credentials take through the system (highest-risk data flow): + +```text +EE1 (Admin) --DF1--> P1 (Installer) --DF2--> DS1 (install-config) [plaintext on disk] + --DF3--> DS2 (K8s Secret) [base64 in etcd] + | + DF4 + | + v + P5 (Fencing Job) + | + DF7 + | + v + DS3 (CIB) [plaintext XML] + | + DF8 + | + v + P6 (fenced) + | + DF9 + | + v + P8 (fence_redfish) [CLI args visible] + | + DF10 + | + v + EE2 (BMC) [HTTPS Basic Auth] +``` + +Any PR touching code along this path requires careful credential handling review. diff --git a/plugins/two-node/evals/README.md b/plugins/two-node/evals/README.md new file mode 100644 index 00000000..45bfdeb6 --- /dev/null +++ b/plugins/two-node/evals/README.md @@ -0,0 +1,81 @@ +# Evaluation Configs + +Automated quality scoring for two-node plugin skills using the +[agent-eval-harness](https://github.com/opendatahub-io/agent-eval-harness) +Claude Code plugin. + +Evals measure skill quality on a spectrum (judges score 1-5, not +pass/fail) — they catch regressions and drift, not exact-match +correctness. + +## Available Evals + +| Config | Skill | Modes Tested | Cases | +|--------|-------|--------------|-------| +| `cluster-diagnostic.yaml` | `two-node:cluster-diagnostic` | validate, recovery-guide, game | 6 | +| `threat-model-tnf.yaml` | `threat-model:tnf` | PR analysis | 5 | + +## Running Locally + +```bash +# Install the eval harness plugin +/plugin marketplace add opendatahub-skills/agent-eval-harness + +# Run an existing eval +/eval-run --model claude-opus-4-6 --config evals/cluster-diagnostic.yaml +``` + +To create a new eval, see [Adding a New Eval](#adding-a-new-eval) below. + +## Running in CI + +Comment `/test eval-cluster-diagnostic` on a PR to trigger the eval job. +The CI workflow is defined in +[openshift/release](https://github.com/openshift/release) under +`ci-operator/config/openshift-eng/edge-tooling/`. + +## Directory Structure + +``` +evals/ +├── .yaml # Eval config (judges, thresholds, schema) +├── .md # Cached skill analysis +└── / + └── cases/ + └── case-NNN-/ + ├── input.yaml # Scenario input + └── annotations.yaml # Expected outcomes +``` + +## Adding a New Eval + +1. **Analyze the skill** — reads SKILL.md, designs judges, writes the eval config + ``` + /eval-analyze --skill --config evals/.yaml + ``` + +2. **Generate scenarios** — creates `input.yaml` + `annotations.yaml` per case + ``` + /eval-dataset --config evals/.yaml + ``` + +3. **Run the eval** — executes the skill against each case, scores with judges, generates HTML report + ``` + /eval-run --model claude-opus-4-6 --config evals/.yaml + ``` + +4. **Review results** — walk through cases, collect human feedback + ``` + /eval-review --run-id --config evals/.yaml + ``` + +5. **(Optional) Optimize** — auto-fix SKILL.md based on judge failures, re-run to verify + ``` + /eval-optimize --config evals/.yaml + ``` + +6. **Commit and CI** + - Commit `evals/.yaml`, `evals/.md`, and `evals//cases/` to this repo + - Add a CI entry in [openshift/release](https://github.com/openshift/release) + pointing `EVAL_CONFIG` to the yaml path + - PR reviewers can then trigger the eval with `/test eval-` diff --git a/plugins/two-node/evals/cluster-diagnostic.md b/plugins/two-node/evals/cluster-diagnostic.md new file mode 100644 index 00000000..345a131a --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic.md @@ -0,0 +1,83 @@ +--- +# Auto-generated by /eval-analyze — edit to override +skill: two-node:cluster-diagnostic +analyzed_at: 2026-06-05T23:00:00Z +skill_hash: bb04c2fed029 + +# Discovered skill capabilities +execution_mode: case +headless: true +dry_run: false + +# Suggested judges (summary from analysis) +suggested_judges: + - name: budget_check + type: builtin + description: "Cost stays within $3.00 per case" + - name: severity_classification + type: check + description: "Validate mode assigns correct BLOCKER/WARNING/INFO severity" + - name: procedure_completeness + type: check + description: "Recovery-guide mode returns bash commands, verification steps, parameter templates" + - name: forbidden_recommendations + type: check + description: "Never recommends pcs standby, sequential shutdown, or shutdown -h" + - name: knowledge_base_accuracy + type: llm + description: "Response accurately reflects TNF knowledge base content" +--- + +# Skill Analysis + +The `two-node:cluster-diagnostic` skill diagnoses TNF (Two-Node Fencing) +cluster issues across 4 modes: diagnose (live SSH), validate (check proposed +procedures), recovery-guide (return correct procedures), and game (interactive +training). The skill encodes 7 validated bare metal test scenarios (HPE ProLiant +e920t, OCP 4.22.0-rc.3) into a knowledge base. + +**Eval scope**: Only `validate` and `recovery-guide` modes are testable in eval +because `diagnose` requires live SSH access and `game` requires interactive +AskUserQuestion handling. Game mode can be tested with tool interception but +adds complexity. + +## Inputs + +Each test case has `input.yaml` with: +- `command_input`: Full argument string (e.g., `validate "cordon, drain, shutdown"`, + `recovery-guide full-shutdown`) +- `mode`: Which mode is being tested (`validate`, `recovery-guide`, `game`) + +And `annotations.yaml` with expected outcomes: +- `expected_blockers`: List of BLOCKER findings expected (validate mode) +- `expected_warnings`: List of WARNING findings expected +- `expected_scenario`: Scenario name (recovery-guide mode) +- `should_reject`: Whether the procedure should be rejected (validate mode) + +## Outputs + +All output is conversational — the skill writes nothing to disk. Judges use +`{{ conversation }}` to evaluate the assistant's response text. + +## Pipeline Flow + +1. Parse argument to determine mode +2. Read `cluster-knowledge-base.md` (800+ lines with 7 failure modes, severity + table, correct procedures, edge cases) +3. For validate: parse procedure text → check each step against 7 failure modes + → report BLOCKER/WARNING/INFO with explanations +4. For recovery-guide: look up scenario → return step-by-step bash commands with + parameter templates and verification steps +5. For game: read `game-mode.md` → present questions via AskUserQuestion → score + +## Quality Criteria + +**Deterministic** (code-checkable): +- Severity classification matches knowledge base table +- Never recommends pcs standby, sequential shutdown, or shutdown -h +- Recovery procedures include bash commands and verification steps + +**LLM judgment** (requires reasoning): +- Response accurately reflects TNF architecture facts +- Failure mode explanations reference correct root causes +- Recovery procedures match validated bare metal test results diff --git a/plugins/two-node/evals/cluster-diagnostic.yaml b/plugins/two-node/evals/cluster-diagnostic.yaml new file mode 100644 index 00000000..ec29c31e --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic.yaml @@ -0,0 +1,267 @@ +name: cluster-diagnostic-eval +description: Evaluate the cluster-diagnostic skill across validate, recovery-guide, and game modes +skill: two-node:cluster-diagnostic + +execution: + mode: case + arguments: "{command_input}" + timeout: 300 + max_budget_usd: 3.0 + +runner: + type: claude-code + plugin_dirs: + - plugins/two-node + +models: + judge: claude-opus-4-6 + hook: claude-sonnet-4-6 + +permissions: + allow: [] + deny: [] + +mlflow: + experiment: cluster-diagnostic-eval + +dataset: + path: plugins/two-node/evals/cluster-diagnostic/cases + schema: | + Each case directory contains: + - input.yaml: YAML file with: + - 'command_input' (string): The full argument string passed to the skill. + For validate mode: 'validate ' + For recovery-guide mode: 'recovery-guide ' + For game mode: 'game' (requires AskUserQuestion interception) + - 'mode' (string): One of 'validate', 'recovery-guide', 'game' + Used by annotation-aware judges to apply mode-specific checks. + - annotations.yaml: Expected outcomes for the test case: + - 'mode' (string): validate | recovery-guide | game + - 'expected_blockers' (list): BLOCKER findings expected (validate mode) + - 'expected_warnings' (list): WARNING findings expected (validate mode) + - 'expected_scenario' (string): scenario name (recovery-guide mode) + - 'should_reject' (bool): whether the procedure should be rejected (validate mode) + + Note: diagnose mode is excluded from eval because it requires live SSH + access to cluster nodes. + +inputs: + tools: + - match: Questions asked to the user via AskUserQuestion (game mode) + prompt: | + Answer based on the test case context in input.yaml and answers.yaml. + For game mode selection, pick 'quiz' unless answers.yaml specifies otherwise. + For quiz/scenario/rapid-fire answers, use answers.yaml guidance. + Default: pick the first option. + +outputs: + - path: output + schema: | + This skill produces conversation output only — no files are written to disk. + Judges should use {{ conversation }} to evaluate the assistant's response text. + + For validate mode: expect a findings list with BLOCKER/WARNING/INFO severity + classifications, each referencing a failure mode from the knowledge base. + + For recovery-guide mode: expect step-by-step markdown with bash commands + using parameter templates ($BMC_USER, $BMC_PASS, etc.) and verification steps. + + For game mode: expect interactive questions, scoring, and a final rating + (Novice/Operator/Expert/TNF Master). + +traces: + stdout: true + stderr: true + events: true + metrics: true + +judges: + - name: budget_check + builtin: cost_budget + arguments: + max_cost_usd: 3.0 + + - name: severity_classification + description: | + For validate mode: checks that expected BLOCKER findings are present + and procedures with blockers are rejected. Sequential shutdown and + pcs standby must be BLOCKER. + if: "annotations.get('mode') == 'validate'" + check: | + conversation = outputs.get("conversation", "") + ann = outputs.get("annotations", {}) + expected_blockers = ann.get("expected_blockers", []) + should_reject = ann.get("should_reject", False) + + if not conversation: + return (False, "No conversation output found") + + conv_upper = conversation.upper() + has_blocker = "BLOCKER" in conv_upper + + if should_reject and not has_blocker: + return (False, "Procedure should have been rejected with BLOCKER but no BLOCKER found") + + if not should_reject and has_blocker: + return (False, "Procedure should NOT have BLOCKER findings but BLOCKER was found") + + found_blockers = [] + for b in expected_blockers: + if b.lower() in conversation.lower(): + found_blockers.append(b) + + if expected_blockers and not found_blockers: + return (False, f"Expected blockers {expected_blockers} not found in output") + + return (True, f"Severity classification correct. Blockers found: {found_blockers}") + + - name: warning_classification + description: | + For validate mode: checks that expected WARNING findings are present + in the output. Verifies the skill identifies non-blocking issues. + if: "annotations.get('mode') == 'validate'" + check: | + conversation = outputs.get("conversation", "") + ann = outputs.get("annotations", {}) + expected_warnings = ann.get("expected_warnings", []) + + if not conversation: + return (False, "No conversation output found") + + if not expected_warnings: + return (True, "No warnings expected for this case") + + conv_lower = conversation.lower() + found = [w for w in expected_warnings if w.lower() in conv_lower] + missing = [w for w in expected_warnings if w.lower() not in conv_lower] + + if missing: + return (False, f"Expected warnings not found: {missing}. Found: {found}") + return (True, f"All expected warnings found: {found}") + + - name: procedure_completeness + description: | + For recovery-guide mode: checks that the returned procedure includes + bash commands, verification steps, and parameter templates. + if: "annotations.get('mode') == 'recovery-guide'" + check: | + conversation = outputs.get("conversation", "") + + if not conversation: + return (False, "No conversation output found") + + checks = { + "bash_commands": any(marker in conversation for marker in ["```bash", "```sh", "curl ", "pcs ", "oc "]), + "has_verification": any(w in conversation.lower() for w in ["verify", "confirm", "check", "poll", "wait"]), + "has_parameters": any(p in conversation for p in ["$BMC", "$NODE", "BMC_USER", "BMC_PASS", "BMC_HOST"]), + } + + passed = sum(checks.values()) + total = len(checks) + failed = [k for k, v in checks.items() if not v] + + if passed == total: + return (True, f"All {total} completeness checks passed") + else: + return (False, f"{passed}/{total} checks passed. Missing: {failed}") + + - name: forbidden_recommendations + description: | + Checks that the skill never recommends procedures that violate known + failure modes: pcs node standby, sequential shutdown, shutdown -h. + check: | + conversation = outputs.get("conversation", "") + ann = outputs.get("annotations", {}) + mode = ann.get("mode", "") + + if not conversation: + return (False, "No conversation output found") + + # Only check recommendations sections, not quoted failure descriptions + forbidden = [] + + # Check if skill RECOMMENDS (not just mentions) dangerous procedures + conv_lower = conversation.lower() + + recommend_sections = [] + for marker in ["recommend", "suggested", "recovery", "procedure", "steps to"]: + idx = conv_lower.find(marker) + if idx >= 0: + recommend_sections.append(conversation[idx:idx+500]) + + for section in recommend_sections: + sec_lower = section.lower() + if "pcs node standby" in sec_lower and "never" not in sec_lower and "do not" not in sec_lower: + forbidden.append("pcs node standby recommended") + if "shutdown -h" in sec_lower and "never" not in sec_lower and "do not" not in sec_lower: + forbidden.append("shutdown -h 1 recommended") + + if forbidden: + return (False, f"Forbidden recommendations found: {forbidden}") + return (True, "No forbidden procedures recommended") + + - name: game_mode_scoring + description: | + For game mode: checks that the skill produces a score and a + final rating (Novice/Operator/Expert/TNF Master). + if: "annotations.get('mode') == 'game'" + check: | + conversation = outputs.get("conversation", "") + + if not conversation: + return (False, "No conversation output found") + + conv_lower = conversation.lower() + ratings = ["novice", "operator", "expert", "tnf master"] + found_rating = [r for r in ratings if r in conv_lower] + + has_score = any(w in conv_lower for w in ["score", "points", "/"]) + + if not found_rating: + return (False, "No rating (Novice/Operator/Expert/TNF Master) found") + if not has_score: + return (False, "No score or points found in output") + return (True, f"Game completed with rating: {found_rating[0]}") + + - name: knowledge_base_accuracy + description: | + LLM judge that evaluates whether the skill's response accurately + reflects the TNF knowledge base content — correct failure modes, + proper severity classification reasoning, and accurate recovery procedures. + prompt: | + Evaluate whether this cluster-diagnostic skill response is accurate + and complete for the given mode. + + ## Skill Response + {{ conversation }} + + ## Test Case Annotations + {{ annotations }} + + ## Scoring Criteria + + Score 1-5: + - 5: Response is fully accurate, references correct failure modes, + severity is properly justified, procedures match tested bare metal results + - 4: Minor omissions but no inaccuracies, severity is correct + - 3: Mostly accurate but missing important details or has minor inaccuracy + - 2: Contains inaccurate claims about TNF behavior or recommends untested procedures + - 1: Fundamentally incorrect — wrong failure modes, wrong severity, dangerous recommendations + + Return a JSON object: {"score": <1-5>, "rationale": ""} + +thresholds: + budget_check: + min_pass_rate: 1.0 + severity_classification: + min_pass_rate: 0.8 + warning_classification: + min_pass_rate: 0.8 + procedure_completeness: + min_pass_rate: 0.8 + forbidden_recommendations: + min_pass_rate: 1.0 + game_mode_scoring: + min_pass_rate: 1.0 + knowledge_base_accuracy: + min_mean: 3.5 diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-001-validate-sequential-shutdown/annotations.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-001-validate-sequential-shutdown/annotations.yaml new file mode 100644 index 00000000..217fedab --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-001-validate-sequential-shutdown/annotations.yaml @@ -0,0 +1,9 @@ +mode: validate +expected_blockers: + - sequential shutdown + - shutdown -h +expected_warnings: + - cordon + - drain +expected_scenario: null +should_reject: true diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-001-validate-sequential-shutdown/input.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-001-validate-sequential-shutdown/input.yaml new file mode 100644 index 00000000..4186c5e7 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-001-validate-sequential-shutdown/input.yaml @@ -0,0 +1,4 @@ +command_input: >- + validate "cordon all nodes, drain workloads, then shut down each node + one at a time using shutdown -h 1 via oc debug" +mode: validate diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-002-validate-safe-redfish/annotations.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-002-validate-safe-redfish/annotations.yaml new file mode 100644 index 00000000..c762f6b6 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-002-validate-safe-redfish/annotations.yaml @@ -0,0 +1,5 @@ +mode: validate +expected_blockers: [] +expected_warnings: [] +expected_scenario: null +should_reject: false diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-002-validate-safe-redfish/input.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-002-validate-safe-redfish/input.yaml new file mode 100644 index 00000000..13b06b84 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-002-validate-safe-redfish/input.yaml @@ -0,0 +1,5 @@ +command_input: >- + validate "Send Redfish GracefulShutdown to both nodes simultaneously + using curl, poll PowerState until Off, then send On to both nodes + to restart" +mode: validate diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-003-recovery-full-shutdown/annotations.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-003-recovery-full-shutdown/annotations.yaml new file mode 100644 index 00000000..4b741409 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-003-recovery-full-shutdown/annotations.yaml @@ -0,0 +1,5 @@ +mode: recovery-guide +expected_blockers: [] +expected_warnings: [] +expected_scenario: full-shutdown +should_reject: false diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-003-recovery-full-shutdown/input.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-003-recovery-full-shutdown/input.yaml new file mode 100644 index 00000000..58326c61 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-003-recovery-full-shutdown/input.yaml @@ -0,0 +1,2 @@ +command_input: recovery-guide full-shutdown +mode: recovery-guide diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-004-recovery-standby/annotations.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-004-recovery-standby/annotations.yaml new file mode 100644 index 00000000..66d323bb --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-004-recovery-standby/annotations.yaml @@ -0,0 +1,5 @@ +mode: recovery-guide +expected_blockers: [] +expected_warnings: [] +expected_scenario: standby +should_reject: false diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-004-recovery-standby/input.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-004-recovery-standby/input.yaml new file mode 100644 index 00000000..dda90015 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-004-recovery-standby/input.yaml @@ -0,0 +1,2 @@ +command_input: recovery-guide standby +mode: recovery-guide diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-005-validate-pcs-standby/annotations.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-005-validate-pcs-standby/annotations.yaml new file mode 100644 index 00000000..f0ee61ee --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-005-validate-pcs-standby/annotations.yaml @@ -0,0 +1,6 @@ +mode: validate +expected_blockers: + - pcs node standby +expected_warnings: [] +expected_scenario: null +should_reject: true diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-005-validate-pcs-standby/input.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-005-validate-pcs-standby/input.yaml new file mode 100644 index 00000000..9ef90652 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-005-validate-pcs-standby/input.yaml @@ -0,0 +1,4 @@ +command_input: >- + validate "Put both nodes in standby using pcs node standby --all, + wait for resources to stop, then power off the servers" +mode: validate diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/annotations.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/annotations.yaml new file mode 100644 index 00000000..3b953d22 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/annotations.yaml @@ -0,0 +1,5 @@ +mode: game +expected_blockers: [] +expected_warnings: [] +expected_scenario: null +should_reject: false diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/answers.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/answers.yaml new file mode 100644 index 00000000..44678d18 --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/answers.yaml @@ -0,0 +1,6 @@ +game_mode: quiz +answer_correctly: true +difficulty_guidance: > + Answer TNF knowledge questions accurately based on the + cluster-knowledge-base content. Pick the most correct option + for each question. diff --git a/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/input.yaml b/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/input.yaml new file mode 100644 index 00000000..d7e8057b --- /dev/null +++ b/plugins/two-node/evals/cluster-diagnostic/cases/case-006-game-quiz/input.yaml @@ -0,0 +1,2 @@ +command_input: "game" +mode: game diff --git a/plugins/two-node/evals/threat-model-tnf.md b/plugins/two-node/evals/threat-model-tnf.md new file mode 100644 index 00000000..4dc11d23 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf.md @@ -0,0 +1,101 @@ +--- +# Auto-generated by /eval-analyze — edit to override +skill: threat-model:tnf +analyzed_at: 2026-06-05T00:00:00Z +skill_hash: ca8e410b0d9b + +# Discovered skill capabilities +execution_mode: case +headless: true +dry_run: false + +# Suggested judges (summary from analysis) +suggested_judges: + - name: budget_check + type: builtin + description: "Cost stays under $8 per invocation" + - name: report_exists + type: check + description: "PR-THREAT-MODEL-.md file was generated" + - name: report_sections_complete + type: check + description: "All 9 required sections present in report" + - name: dfd_elements_mapped + type: check + description: "DFD element IDs (P/DS/DF/EE/TB) referenced in report" + - name: stride_matrix_present + type: check + description: "Per-element STRIDE matrix has X/~/-/N/A markers" + - name: mitre_techniques_assigned + type: check + description: "MITRE ATT&CK technique IDs (T####) are present" + - name: threat_analysis_quality + type: llm + description: "Overall quality: severity accuracy, DFD mapping, STRIDE completeness, recommendations" + - name: findings_tracker_updated + type: check + description: "Cumulative findings tracker was appended" +--- + +## Skill Analysis + +The `threat-model:tnf` skill performs security threat analysis on GitHub PRs affecting the TNF (Two-Node Fencing) OpenShift topology. It combines three approaches: + +1. **Automated scanning** — runs ShellCheck on shell scripts in the PR diff +2. **Pattern detection** — searches for command injection, credential exposure, privilege escalation, and 7 other security pattern categories +3. **Formal threat modeling** — maps code changes to TNF DFD elements (8 processes, 5 data stores, 12 data flows, 3 external entities, 6 trust boundaries), applies per-element STRIDE analysis, and cross-references against the formal TNF threat model + +Output is a formal threat model report with MITRE ATT&CK technique mappings, OWASP Top 10:2025 categorization, risk assessment, and actionable recommendations for developers and customers. + +## Inputs + +Each test case provides a single PR identifier via `input.yaml`: + +- **`pr_input`** — the PR to analyze, in one of three formats: + - PR number only: `2156` (repo detected from working directory) + - GitHub URL: `https://github.com/ClusterLabs/resource-agents/pull/2156` + - Repo + number: `resource-agents 2156` + +The PR must be a real, accessible GitHub PR. The skill uses `gh pr view` and `gh pr diff` to fetch PR data. + +Optional fields: `repo` (repository name), `org` (GitHub organization). + +## Outputs + +The skill writes to a `reports/` directory (resolved via workspace discovery): + +- **`PR-THREAT-MODEL-.md`** — main threat model report (~200-500 lines) +- **`VULN-PR-.md`** — individual vulnerability tickets (Critical/High only, optional) + +It also appends to a cumulative findings tracker at `$WORKSPACE/.claude/skills/threat-model/mitre-findings-tnf.md`. + +## Pipeline Flow + +1. **Workspace discovery** — walk up from CWD looking for `repos/` directory; set WORKSPACE, REPOS, THREAT_MODEL_DIR, REPORT_DIR, FINDINGS_FILE +2. **Parse input** — extract org, repo, PR number from the three input formats +3. **Fetch PR** — `gh pr view` for metadata, `gh pr diff` for the full diff +4. **ShellCheck** — run on any .sh files; map security codes (SC2086→T1059) to MITRE +5. **Pattern analysis** — search diff for 10 security pattern categories +6. **DFD mapping** — match code paths to TNF elements using the mapping table in `dfd-elements-tnf.md` +7. **STRIDE analysis** — per-element threat assessment; cross-reference against TNF-THREAT-MODEL.md if available +8. **Combine findings** — deduplicate, assign VULN-N IDs, determine severity +9. **MITRE/OWASP mapping** — assign technique IDs and OWASP categories using reference files +10. **Generate report** — write markdown report using report-templates.md format +11. **Append tracker** — add findings block to cumulative mitre-findings-tnf.md + +## Quality Criteria + +A **good** report: +- Correctly identifies all affected DFD elements from the code paths in the PR +- Applies STRIDE systematically to each element (all 6 categories for processes, T/I/D for stores and flows) +- Assigns accurate severity levels matching MITRE/OWASP standards +- Identifies trust boundary crossings (especially TB3→TB4, TB4→TB5) +- Provides specific, actionable recommendations with code-level guidance +- Maps findings to correct MITRE techniques (T1059 for injection, T1552 for credentials, T1611 for container escape) + +A **bad** report: +- Misses affected DFD elements or assigns wrong elements to code paths +- Has incomplete STRIDE matrix (missing categories or missing rationale) +- Over/under-rates severity (e.g., calling a minor code quality issue "Critical") +- Provides vague recommendations ("improve security") without specific guidance +- Missing sections or incorrect report structure diff --git a/plugins/two-node/evals/threat-model-tnf.yaml b/plugins/two-node/evals/threat-model-tnf.yaml new file mode 100644 index 00000000..f788bc7d --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf.yaml @@ -0,0 +1,249 @@ +name: threat-model-tnf-eval +description: Evaluate the threat-model:tnf skill — PR security analysis with STRIDE/DFD, MITRE ATT&CK, and OWASP mapping for TNF topology +skill: threat-model:tnf + +execution: + mode: case + arguments: "{pr_input}" + timeout: 600 + max_budget_usd: 8.0 + env: + REPORT_DIR: "reports/" + +runner: + type: claude-code + plugin_dirs: + - plugins/threat-model + +models: + skill: claude-opus-4-6 + judge: claude-opus-4-6 + +permissions: + allow: [] + deny: [] + +mlflow: + experiment: threat-model-tnf-eval + +dataset: + path: plugins/two-node/evals/threat-model-tnf/cases + schema: | + Each case directory contains: + - input.yaml: YAML file with fields: + - 'pr_input': the PR identifier to analyze — one of: + - A PR number (e.g., '2156') + - A GitHub URL (e.g., 'https://github.com/ClusterLabs/resource-agents/pull/2156') + - A 'repo number' pair (e.g., 'resource-agents 2156') + [EXTERNAL: GitHub] — must be a real, accessible PR on GitHub + - 'repo' (optional): repository name for context (e.g., 'resource-agents') + - 'org' (optional): GitHub org (e.g., 'ClusterLabs', 'openshift') + - reference.md (optional): gold-standard threat model report for comparison. + Uses the report template format: Executive Summary, DFD Impact Analysis, + Per-Element STRIDE matrix, Threat Analysis (VULN-N sections), MITRE/OWASP + mapping, Risk Assessment, and Recommendations. + - annotations.yaml (optional): expected metadata for outcome-aware scoring: + - 'expected_vuln_count': expected number of findings + - 'expected_severities': list of expected severity levels + - 'affected_dfd_elements': list of expected DFD element IDs (e.g., ['P5', 'P7', 'DS3']) + - 'expected_mitre_techniques': list of expected MITRE technique IDs + - 'has_shell_scripts': whether the PR contains shell scripts for ShellCheck + - 'has_trust_boundary_crossing': whether the PR crosses trust boundaries + +outputs: + - path: reports + schema: | + The skill writes threat model reports as markdown files: + - PR-THREAT-MODEL-.md: main report with sections: + - Document header (version, date, classification, repo, topology, author, URL) + - Executive Summary with findings count table (Critical/High/Medium/Low) + - Change Overview describing the PR and security-relevant changes + - Affected Files table (file path, line changes, security relevance) + - DFD Impact Analysis: + - Affected DFD Elements table (Element ID, Name, Impact, Trust Boundary) + - Trust Boundary Crossings narrative + - Per-Element STRIDE matrix (S/T/R/I/D/E per element, X/~/-/N/A) + - Threat Model Cross-Reference table (PE-* IDs if formal model exists) + - Automated Scanner Results (ShellCheck table or "skipped" note) + - Threat Analysis: per-VULN section with Severity, OWASP, MITRE, CWE, + Affected Code, Description, Attack Vector, Impact (CIA), Recommended Fix + - OWASP & MITRE ATT&CK Mapping table + - Risk Assessment table (Likelihood, Impact, Risk) + - Recommendations (Developers: Before/After Merge; Customers: Config/Ops) + - References + - VULN-PR-.md (optional): individual vulnerability tickets + for Critical/High findings only + +traces: + stdout: true + stderr: true + events: true + metrics: true + +judges: + - name: budget_check + builtin: cost_budget + arguments: + max_cost_usd: 8.0 + + - name: report_exists + description: Verify that the main threat model report markdown file was generated + check: | + files = outputs.get("files", {}) + reports = [k for k in files if "THREAT-MODEL" in k and k.endswith(".md")] + if not reports: + return (False, "No threat model report file found") + return (True, f"Report generated: {reports[0]}") + + - name: report_sections_complete + description: Verify all required report sections are present in the generated report + check: | + files = outputs.get("files", {}) + reports = {k: v for k, v in files.items() if "THREAT-MODEL" in k and k.endswith(".md")} + if not reports: + return (False, "No report file found") + content = list(reports.values())[0] + required = [ + "Executive Summary", + "Change Overview", + "Affected Files", + "DFD Impact Analysis", + "STRIDE", + "Threat Analysis", + "MITRE", + "Risk Assessment", + "Recommendations", + ] + missing = [s for s in required if s not in content] + if missing: + return (False, f"Missing sections: {', '.join(missing)}") + return (True, f"All {len(required)} required sections present") + + - name: dfd_elements_mapped + description: Verify that DFD elements (P1-P8, DS1-DS5, DF1-DF12) are referenced in the report + check: | + import re + files = outputs.get("files", {}) + reports = {k: v for k, v in files.items() if "THREAT-MODEL" in k and k.endswith(".md")} + if not reports: + return (False, "No report file found") + content = list(reports.values())[0] + elements = re.findall(r'\b(P[1-8]|DS[1-5]|DF(?:1[0-2]|[1-9])|EE[1-3]|TB[1-6])\b', content) + unique = set(elements) + if not unique: + return (False, "No DFD elements found in report") + return (True, f"DFD elements referenced: {sorted(unique)}") + + - name: stride_matrix_present + description: Verify the per-element STRIDE matrix is populated with X, ~, or - markers + check: | + import re + files = outputs.get("files", {}) + reports = {k: v for k, v in files.items() if "THREAT-MODEL" in k and k.endswith(".md")} + if not reports: + return (False, "No report file found") + content = list(reports.values())[0] + stride_section = content.split("Per-Element STRIDE") + if len(stride_section) < 2: + return (False, "No Per-Element STRIDE section found") + markers = re.findall(r'\b[XxNn/Aa~-]\b', stride_section[1][:2000]) + if len(markers) < 3: + return (False, f"STRIDE matrix appears empty or minimal ({len(markers)} markers)") + return (True, f"STRIDE matrix populated ({len(markers)} cell markers found)") + + - name: mitre_techniques_assigned + description: Verify MITRE ATT&CK technique IDs (T####) are present and mapped to findings + check: | + import re + files = outputs.get("files", {}) + reports = {k: v for k, v in files.items() if "THREAT-MODEL" in k and k.endswith(".md")} + if not reports: + return (False, "No report file found") + content = list(reports.values())[0] + techniques = set(re.findall(r'T\d{4}', content)) + if not techniques: + return (False, "No MITRE ATT&CK technique IDs found") + return (True, f"MITRE techniques: {sorted(techniques)}") + + - name: threat_analysis_quality + description: | + LLM judge assessing overall threat analysis quality: severity accuracy, + DFD mapping correctness, STRIDE completeness, and recommendation actionability + prompt: | + You are evaluating a TNF (Two-Node Fencing) PR threat analysis report. + + ## Report output: + + {{ outputs }} + + ## Skill conversation: + + {{ conversation }} + + ## Evaluation criteria + + Score on a 1-5 scale across these dimensions, then give an overall score: + + **1. Severity accuracy** — do assigned severities (Critical/High/Medium/Low) match the actual risk? + - Critical: RCE, credential exposure at high-trust boundary (P5/P6/P8), STONITH bypass + - High: command injection with exploitation path, new credential dependency, missing validation on network boundary + - Medium: fail-open behavior, non-critical info disclosure, potential race condition + - Low: minor code quality, non-exploitable pattern + + **2. DFD mapping correctness** — are code changes correctly mapped to TNF DFD elements (P1-P8, DS1-DS5, DF1-DF12)? + - Code paths should match the element mapping table (e.g., cluster-etcd-operator/pkg/tnf/fencing/ → P5) + - Trust boundary crossings should be identified (TB2→TB3, TB3→TB4, TB4→TB5) + + **3. STRIDE completeness** — is each affected element analyzed across all applicable STRIDE categories? + - Processes: all 6 (S,T,R,I,D,E) + - Data Stores: T,I,D + - Data Flows: T,I,D + - External Entities: S,R + + **4. MITRE/OWASP accuracy** — are technique assignments correct? + - T1059 for command injection, T1552 for credential exposure, T1611 for container escape + - OWASP categories should match (A05 for injection, A07 for auth failures) + + **5. Recommendation quality** — are recommendations specific and actionable? + - Developer recommendations should include code-level guidance + - Customer recommendations should include hardening or monitoring steps + - Vague recommendations ("improve security") score low + + ## Scoring + Score 1: Report is missing major sections, contains incorrect mappings, or has no useful findings + Score 2: Report exists but has significant gaps — missing STRIDE analysis, wrong DFD elements, or vague recommendations + Score 3: Adequate report covering basics — correct elements identified, some STRIDE analysis, generic recommendations + Score 4: Good report — accurate DFD mapping, thorough STRIDE, relevant MITRE techniques, specific recommendations + Score 5: Excellent — comprehensive coverage, all trust boundaries analyzed, accurate severity, actionable recommendations with code examples + + Respond with a single integer score (1-5) on the first line, then explain your reasoning. + + - name: findings_tracker_updated + description: Verify the findings tracker was appended with new entries (checks conversation for append confirmation) + check: | + conv = outputs.get("conversation", "") + files = outputs.get("files", {}) + tracker_files = [k for k in files if "mitre-findings" in k.lower()] + if tracker_files: + return (True, f"Findings tracker file found: {tracker_files[0]}") + if "findings" in conv.lower() and ("append" in conv.lower() or "tracker" in conv.lower()): + return (True, "Findings tracker update mentioned in conversation") + return (False, "No evidence of findings tracker update") + +thresholds: + budget_check: + min_pass_rate: 1.0 + report_exists: + min_pass_rate: 1.0 + report_sections_complete: + min_pass_rate: 1.0 + dfd_elements_mapped: + min_pass_rate: 1.0 + stride_matrix_present: + min_pass_rate: 0.8 + mitre_techniques_assigned: + min_pass_rate: 1.0 + threat_analysis_quality: + min_mean: 3.5 + findings_tracker_updated: + min_pass_rate: 0.8 diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-001-shell-script-k8s-api/annotations.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-001-shell-script-k8s-api/annotations.yaml new file mode 100644 index 00000000..751425b7 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-001-shell-script-k8s-api/annotations.yaml @@ -0,0 +1,18 @@ +description: > + Shell script PR that adds kubeconfig-based K8s API access to the podman-etcd + OCF agent. Introduces new trust boundary crossing (TB4→TB2) and credential + dependency. Should trigger ShellCheck analysis and identify credential exposure. +has_shell_scripts: true +has_trust_boundary_crossing: true +expected_severities: + - High + - Medium + - Low +affected_dfd_elements: + - P7 + - DS5 + - DF11 +expected_mitre_techniques: + - T1552 + - T1078 + - T1005 diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-001-shell-script-k8s-api/input.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-001-shell-script-k8s-api/input.yaml new file mode 100644 index 00000000..37361080 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-001-shell-script-k8s-api/input.yaml @@ -0,0 +1,3 @@ +pr_input: "https://github.com/ClusterLabs/resource-agents/pull/2156" +repo: resource-agents +org: ClusterLabs diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-002-credential-rotation-script/annotations.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-002-credential-rotation-script/annotations.yaml new file mode 100644 index 00000000..7d6e2436 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-002-credential-rotation-script/annotations.yaml @@ -0,0 +1,21 @@ +description: > + Adds a TNF fencing credentials rotation script. This touches the full credential + flow path (DS2→P5→DS3) and should identify high-severity findings around + credential handling, STONITH configuration, and BMC access. Complex case with + multiple DFD elements affected. +has_shell_scripts: true +has_trust_boundary_crossing: true +expected_severities: + - Critical + - High + - Medium +affected_dfd_elements: + - P5 + - DS2 + - DS3 + - DF4 + - DF7 +expected_mitre_techniques: + - T1552 + - T1059 + - T1529 diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-002-credential-rotation-script/input.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-002-credential-rotation-script/input.yaml new file mode 100644 index 00000000..cd609686 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-002-credential-rotation-script/input.yaml @@ -0,0 +1,3 @@ +pr_input: "cluster-etcd-operator 1611" +repo: cluster-etcd-operator +org: openshift diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-003-mac-fencing-lookup/annotations.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-003-mac-fencing-lookup/annotations.yaml new file mode 100644 index 00000000..b62f1b9c --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-003-mac-fencing-lookup/annotations.yaml @@ -0,0 +1,16 @@ +description: > + Adds MAC-address based fencing credentials lookup. Introduces a new data flow + for credential resolution and modifies the fencing job's credential discovery + path. Tests DFD mapping for P5 and the credential pipeline. +has_shell_scripts: false +has_trust_boundary_crossing: true +expected_severities: + - High + - Medium +affected_dfd_elements: + - P5 + - DS2 + - DF4 +expected_mitre_techniques: + - T1552 + - T1078 diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-003-mac-fencing-lookup/input.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-003-mac-fencing-lookup/input.yaml new file mode 100644 index 00000000..34abba7b --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-003-mac-fencing-lookup/input.yaml @@ -0,0 +1,3 @@ +pr_input: "https://github.com/openshift/cluster-etcd-operator/pull/1600" +repo: cluster-etcd-operator +org: openshift diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-004-trivial-indentation-fix/annotations.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-004-trivial-indentation-fix/annotations.yaml new file mode 100644 index 00000000..7a2fc3ac --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-004-trivial-indentation-fix/annotations.yaml @@ -0,0 +1,9 @@ +description: > + Trivial indentation fix in nfsserver — not TNF-specific, no shell scripts + relevant to TNF. Should produce a report with minimal or no security findings. + Edge case testing the skill's handling of low-risk, non-TNF PRs. +has_shell_scripts: false +has_trust_boundary_crossing: false +expected_severities: [] +affected_dfd_elements: [] +expected_mitre_techniques: [] diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-004-trivial-indentation-fix/input.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-004-trivial-indentation-fix/input.yaml new file mode 100644 index 00000000..f883d7a3 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-004-trivial-indentation-fix/input.yaml @@ -0,0 +1,3 @@ +pr_input: "https://github.com/ClusterLabs/resource-agents/pull/2168" +repo: resource-agents +org: ClusterLabs diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-005-tnf-retry-bugfix/annotations.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-005-tnf-retry-bugfix/annotations.yaml new file mode 100644 index 00000000..d9d3e7b2 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-005-tnf-retry-bugfix/annotations.yaml @@ -0,0 +1,15 @@ +description: > + Bug fix gating dual-replica setup and adding retry logic in TNF pipeline. + Modifies P4 (Setup Job) behavior. Tests whether the skill correctly identifies + denial-of-service risk from retry logic changes and setup gate modifications. + Uses bare PR number format — tests repo auto-detection from CWD. +has_shell_scripts: false +has_trust_boundary_crossing: false +expected_severities: + - Medium + - Low +affected_dfd_elements: + - P4 + - P2 +expected_mitre_techniques: + - T1499 diff --git a/plugins/two-node/evals/threat-model-tnf/cases/case-005-tnf-retry-bugfix/input.yaml b/plugins/two-node/evals/threat-model-tnf/cases/case-005-tnf-retry-bugfix/input.yaml new file mode 100644 index 00000000..2ccb92a2 --- /dev/null +++ b/plugins/two-node/evals/threat-model-tnf/cases/case-005-tnf-retry-bugfix/input.yaml @@ -0,0 +1,3 @@ +pr_input: "1620" +repo: cluster-etcd-operator +org: openshift