Architect8999 commited on
Commit
9452af2
·
verified ·
1 Parent(s): d529357

feat: ethical AVR pipeline — semantic extractor, harness factory, chain analyzer, disclosure vault, Security Research tab

Browse files
SECURITY_RESEARCH_PLAYBOOK.md ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Rhodawk AI — Ethical Autonomous Vulnerability Research (AVR)
2
+ ## Operator & Investor Playbook v1.0
3
+
4
+ > **"The next generation of security tooling does not find known CVEs.
5
+ > It finds the assumptions that developers got wrong — before attackers do."**
6
+
7
+ ---
8
+
9
+ ## The Problem: Why Existing Security Tools Are Obsolete Against Tier-1 Targets
10
+
11
+ The global security tooling market ($20B+ and growing) is flooded with scanners that search for patterns they already know about. They find Log4Shell *after* it has been CVE-assigned. They find SQL injection via regex. They find secret keys with keyword matching.
12
+
13
+ Against Tier-1 targets — Linux kernel, Kubernetes, V8, OpenSSL, gRPC — none of this works. The vulnerabilities that command $500K+ bug bounty payouts and change the course of software history are **logic flaws**. They are invisible to pattern-matching tools because they have never occurred before.
14
+
15
+ The root cause of every novel critical vulnerability is a single cognitive failure: **a developer made an assumption that was true in their mental model but false at the boundary of another component's execution context.**
16
+
17
+ ---
18
+
19
+ ## The Solution: Rhodawk AVR — Semantic Logic Reversal
20
+
21
+ Rhodawk's Ethical Autonomous Vulnerability Research (AVR) module reconstructs the developer's mental model from their code, then systematically proves where that model breaks.
22
+
23
+ This is not fuzzing. This is not SAST. This is a reasoning engine.
24
+
25
+ ---
26
+
27
+ ## Architecture: The Five-Stage Ethical Pipeline
28
+
29
+ Every stage is designed around a single principle: **the human operator is the final authority at every decision point**. The AI accelerates discovery. The human ensures responsibility.
30
+
31
+ ---
32
+
33
+ ### Stage 1 — Semantic Reversal Engine
34
+
35
+ **What it does:**
36
+ Ingests the target repository alongside any available RFCs, API documentation, or architecture markdown. Instructs Nous Hermes 3 (405B via OpenRouter) to construct a JSON graph of the application's **trust state machine** — the precise path that data travels from "completely untrusted external input" to "fully trusted internal state".
37
+
38
+ **What it finds:**
39
+ The "Assumption Gap" — the exact line of code where a developer assumed a variable was safe, but the state machine graph proves an edge case can deliver an untrusted value to that point.
40
+
41
+ **Ethical constraint:**
42
+ This stage is **pure static analysis**. No code is executed. The repository is cloned locally and read. No network calls are made from within the analysis. Every finding is tagged `requires_human_verification: true`.
43
+
44
+ **Output:**
45
+ A structured JSON state machine graph with scored assumption gaps, ready for operator review.
46
+
47
+ ---
48
+
49
+ ### Stage 2 — Dynamic Harness Compiler
50
+
51
+ **What it does:**
52
+ For each operator-reviewed assumption gap, Hermes generates a **minimal Python proof-of-concept harness** that exercises the specific code path identified in Stage 1.
53
+
54
+ The harness speaks the application's own protocol (correct JSON structure, valid OAuth flows, proper gRPC framing) to reach the deep logic — then introduces precisely the edge-case input that the assumption gap predicts will bypass the validation.
55
+
56
+ **What it is not:**
57
+ This is not a weaponised exploit. The harness is PoC-grade: it demonstrates whether the gap is triggerable in a controlled local environment. It cannot be repurposed for remote attacks without substantial additional development by a human actor.
58
+
59
+ **Ethical constraint:**
60
+ The harness is displayed to the operator in full before any execution occurs. The operator must read the code and click **"I have reviewed this code"** to proceed. The harness runs in an isolated local sandbox with:
61
+ - All secrets and API keys stripped from the environment
62
+ - No outbound network connections permitted
63
+ - 30-second hard timeout
64
+ - Execution against a locally cloned copy of the codebase only
65
+
66
+ **Output:**
67
+ `TRIGGERED: True / False` — a boolean result that either validates or falsifies the assumption gap hypothesis.
68
+
69
+ ---
70
+
71
+ ### Stage 3 — Vulnerability Chain Synthesiser
72
+
73
+ **What it does:**
74
+ Advanced vulnerabilities are rarely a single bug. A P5 memory leak combined with a P4 timing discrepancy might chain into a P1 privilege escalation that neither primitive reveals alone.
75
+
76
+ Hermes maintains a local SQLite database (`chain_memory.sqlite`) of all primitive findings. When sufficient primitives are stored for a target, it reasons about logical chains — the sequence of steps an attacker would need to take to elevate a collection of low-severity primitives into a critical exploit.
77
+
78
+ **Ethical constraint:**
79
+ All chain proposals are **theoretical documents** tagged `PENDING_HUMAN_REVIEW`. No chain is executed automatically. The operator reads each proposed chain, assesses its plausibility, and approves or rejects it before any further action is taken.
80
+
81
+ **Output:**
82
+ A structured chain proposal document with severity rating, required conditions, and human verification checklist.
83
+
84
+ ---
85
+
86
+ ### Stage 4 — Isolated Execution Chamber
87
+
88
+ **What it does:**
89
+ Executes operator-approved harnesses against locally cloned repository code. Returns a concrete boolean result: did the harness trigger the hypothesised behaviour?
90
+
91
+ The chamber enforces:
92
+ - **Offline execution** — the target codebase runs without network access
93
+ - **Secret isolation** — all credentials removed from the subprocess environment
94
+ - **Time boxing** — hard 30-second limit per harness
95
+ - **No persistence** — harness temp files deleted after execution
96
+
97
+ **What it does not do:**
98
+ It does not execute against live production systems. It does not attempt to achieve real privilege escalation in a production environment. It does not exfiltrate data. The "detonation" is entirely simulated against local code.
99
+
100
+ **Ethical constraint:**
101
+ This stage is only reachable after the operator has passed through Stage 2's explicit human approval gate.
102
+
103
+ **Output:**
104
+ Execution result stored as a primitive finding and surfaced in the Rhodawk dashboard.
105
+
106
+ ---
107
+
108
+ ### Stage 5 — Air-Gapped Disclosure Vault
109
+
110
+ **What it does:**
111
+ Compiles all findings — semantic graph, assumption gap description, harness PoC, chain analysis, execution result — into a structured responsible disclosure dossier.
112
+
113
+ The dossier is stored locally in encrypted-at-rest format. Nothing leaves the system until the operator takes explicit action.
114
+
115
+ **The Disclosure Lifecycle:**
116
+
117
+ | Stage | Actor | Action |
118
+ |---|---|---|
119
+ | DRAFT | System | Dossier compiled, stored locally |
120
+ | PENDING | **Human Operator** | Reads full dossier, independently verifies |
121
+ | APPROVED | **Human Operator** | Clicks Approve, enters their name (audit record) |
122
+ | DISCLOSED | **Human Operator** | Sends prepared message via maintainer's security channel |
123
+ | COORDINATED | Maintainer | Acknowledges, begins remediation |
124
+ | PUBLIC | Both | Coordinated public disclosure after 90-day window |
125
+
126
+ **Ethical constraint:**
127
+ The system never sends anything to an external party. After approval, it generates a disclosure message that the operator sends manually via the maintainer's existing security policy (SECURITY.md, HackerOne, Bugcrowd, direct email). The 90-day responsible disclosure clock is tracked and surfaced in the dashboard.
128
+
129
+ **GitHub API lockout:**
130
+ When AVR mode is active, all outbound GitHub API write access is severed. The system is a research instrument, not an autonomous actor.
131
+
132
+ ---
133
+
134
+ ## The Adversarial Reviewer (Multi-Model Validation)
135
+
136
+ Before any finding advances past Stage 2, the Rhodawk Multi-Model Adversarial Reviewer evaluates the logical soundness of the assumption gap. Three models (Qwen, Gemma, Mistral) independently assess whether the proposed gap is:
137
+
138
+ - Logically coherent given the state machine graph
139
+ - Supported by the actual source code evidence
140
+ - Not a hallucination or pattern-match false positive
141
+
142
+ A 2-of-3 consensus is required to advance. This eliminates low-quality findings before they consume expensive harness generation and sandbox compute — and prevents operators from being overwhelmed with noise.
143
+
144
+ ---
145
+
146
+ ## Market Opportunity
147
+
148
+ | Segment | TAM | Rhodawk Position |
149
+ |---|---|---|
150
+ | Application security testing | $8.3B (2025) | AI-native SAST replacement |
151
+ | Bug bounty & VDP platforms | $1.2B | Autonomous discovery layer |
152
+ | Penetration testing services | $4.5B | Augmented researcher tooling |
153
+ | Threat intelligence platforms | $6.7B | Novel zero-day feed |
154
+
155
+ **The inflection point:** Every major tech company (Google, Meta, Apple, Microsoft) now pays six-figure sums for Tier-1 zero-days through internal and external bug bounty programmes. The bottleneck is not payouts — it is the scarcity of researchers capable of finding these vulnerabilities.
156
+
157
+ Rhodawk does not replace security researchers. It gives them an AI-powered Tier-1 co-researcher that works 24/7, never forgets a primitive finding, and can hold the entire state machine of a 500,000-line codebase in context simultaneously.
158
+
159
+ ---
160
+
161
+ ## Why Ethical Design Is the Moat
162
+
163
+ Automated exploitation tooling is a commodity. Nation-state actors have had it for decades. The defensible market position is not in building another offensive tool — it is in building the first platform that makes elite vulnerability research **auditable, reproducible, and responsible at scale**.
164
+
165
+ Every action in Rhodawk AVR is logged with a SHA-256 JSONL audit trail. Every human approval is recorded with the operator's name and timestamp. Every disclosure follows the industry-standard 90-day coordinated timeline.
166
+
167
+ This is not a constraint on capability. It is the capability. Enterprises, governments, and bug bounty programmes will pay premium rates for a platform whose output is defensible in front of a board of directors, a regulator, or a court.
168
+
169
+ ---
170
+
171
+ ## Technical Stack
172
+
173
+ | Component | Technology | Role |
174
+ |---|---|---|
175
+ | Orchestrator LLM | Nous Hermes 3 405B (OpenRouter) | Semantic reasoning, chain synthesis |
176
+ | Adversarial Reviewer | Qwen + Gemma + Mistral (2/3 consensus) | Hallucination elimination |
177
+ | Static Analysis | Custom `semantic_extractor.py` | Trust state machine extraction |
178
+ | Harness Generation | `harness_factory.py` + Hermes | PoC code synthesis |
179
+ | Chain Memory | SQLite (`chain_memory.sqlite`) | Longitudinal primitive storage |
180
+ | Disclosure Vault | SQLite + local filesystem | Dossier lifecycle management |
181
+ | Sandbox Runtime | subprocess + env isolation | Time-limited local execution |
182
+ | Audit Trail | SHA-256 JSONL chain | SOC 2 / ISO 27001 evidence |
183
+ | UI | Gradio (HuggingFace Spaces) | Human operator dashboard |
184
+
185
+ ---
186
+
187
+ ## Operational Principles (Non-Negotiable)
188
+
189
+ 1. **No automated disclosure.** The human operator is the final authority.
190
+ 2. **No live system testing.** All PoC execution is against locally cloned code.
191
+ 3. **No credential use.** Secrets are stripped from all sandbox environments.
192
+ 4. **90-day timeline.** All disclosures follow coordinated responsible disclosure.
193
+ 5. **Open-source targets only.** Only repositories with established security policies.
194
+ 6. **Full audit log.** Every action, approval, and disclosure is recorded.
195
+
196
+ ---
197
+
198
+ *Rhodawk AI — Building the infrastructure for responsible AI-native security research*
199
+ *Contact: security-research@rhodawk.ai*
app.py CHANGED
@@ -1109,6 +1109,190 @@ def reset_queue():
1109
  return "✅ Job queue cleared."
1110
 
1111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1112
  # ──────────────────────────────────────────────────────────────
1113
  # GRADIO ENTERPRISE DASHBOARD
1114
  # ──────────────────────────────────────────────────────────────
@@ -1394,6 +1578,147 @@ GET /webhook/queue — Current job status (JSON)
1394
  - Public leaderboard (`public_leaderboard.py`) — real numbers, real PRs, no fake metrics
1395
  """)
1396
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1397
  # ── AUTO-REFRESH ────────────────────────────────────────────
1398
  timer = gr.Timer(3)
1399
  timer.tick(get_live_logs, outputs=live_logs)
 
1109
  return "✅ Job queue cleared."
1110
 
1111
 
1112
+ # ──────────────────────────────────────────────────────────────
1113
+ # ETHICAL SECURITY RESEARCH PIPELINE
1114
+ # Human approval gate at every stage — nothing disclosed automatically
1115
+ # ──────────────────────────────────────────────────────────────
1116
+
1117
+ def _research_clone(repo: str) -> str:
1118
+ """Clone repo to a local research directory (read-only analysis)."""
1119
+ repo_dir = f"/tmp/research_{repo.replace('/', '_')}"
1120
+ if not os.path.exists(repo_dir):
1121
+ ui_log(f"Cloning {repo} for static analysis...", "INFO")
1122
+ Repo.clone_from(f"https://github.com/{repo}.git", repo_dir)
1123
+ return repo_dir
1124
+
1125
+
1126
+ def run_semantic_analysis(repo_input: str) -> tuple[str, str]:
1127
+ """Pure static analysis — no code executed."""
1128
+ repo = (repo_input or "").strip()
1129
+ if not repo or "/" not in repo or len(repo.split("/")) != 2:
1130
+ return "❌ Use format: owner/repo", ""
1131
+ try:
1132
+ from semantic_extractor import run_semantic_extraction
1133
+ from language_runtime import RuntimeFactory
1134
+ repo_dir = _research_clone(repo)
1135
+ runtime = RuntimeFactory.for_repo(repo_dir)
1136
+ result = run_semantic_extraction(repo_dir, runtime.language)
1137
+ gaps = result.get("assumption_gaps", [])
1138
+ summary = (
1139
+ f"Static analysis complete.\n"
1140
+ f"Language: {result.get('language', 'unknown')}\n"
1141
+ f"Files analysed: {len(result.get('analyzed_files', []))}\n"
1142
+ f"Assumption gaps found: {len(gaps)}\n\n"
1143
+ f"All findings tagged requires_human_verification=true."
1144
+ )
1145
+ ui_log(f"Semantic analysis: {repo} → {len(gaps)} gap(s)", "INFO")
1146
+ return summary, json.dumps(result, indent=2)[:10000]
1147
+ except Exception as e:
1148
+ return f"Analysis failed: {e}", ""
1149
+
1150
+
1151
+ def generate_harness_for_review(gap_json: str, repo_input: str) -> str:
1152
+ """Generate PoC harness for operator review — NOT executed here."""
1153
+ try:
1154
+ from harness_factory import generate_poc_harness
1155
+ gap = json.loads(gap_json)
1156
+ repo_dir = f"/tmp/research_{repo_input.strip().replace('/', '_')}"
1157
+ result = generate_poc_harness(gap, repo_dir)
1158
+ if "error" in result:
1159
+ return f"Generation failed: {result['error']}"
1160
+ return (
1161
+ f"Status: {result['status']}\n"
1162
+ f"Gap ID: {result['gap_id']}\n\n"
1163
+ f"--- REVIEW THIS CODE BEFORE APPROVING EXECUTION ---\n\n"
1164
+ f"{result['harness_code']}"
1165
+ )
1166
+ except Exception as e:
1167
+ return f"Error: {e}"
1168
+
1169
+
1170
+ def execute_approved_harness(harness_code: str, repo_input: str, venv_path: str) -> str:
1171
+ """
1172
+ Sandbox execution — only after operator reads and approves harness.
1173
+ No network access, secrets stripped, 30 s timeout.
1174
+ """
1175
+ if not harness_code.strip():
1176
+ return "❌ No harness code provided."
1177
+ try:
1178
+ from harness_factory import run_harness_in_sandbox
1179
+ repo_dir = f"/tmp/research_{repo_input.strip().replace('/', '_')}"
1180
+ r = run_harness_in_sandbox(harness_code, repo_dir, venv_path.strip() or "/data/target_venv")
1181
+ status = "⚠️ GAP TRIGGERED in sandbox" if r.get("triggered") else "✅ Not triggered"
1182
+ return (
1183
+ f"{status}\n\n"
1184
+ f"Exit code : {r.get('exit_code', 'N/A')}\n"
1185
+ f"Timed out : {r.get('timed_out', False)}\n\n"
1186
+ f"Stdout:\n{r.get('stdout', '')}\n\n"
1187
+ f"Stderr:\n{r.get('stderr', '')}"
1188
+ )
1189
+ except Exception as e:
1190
+ return f"Sandbox error: {e}"
1191
+
1192
+
1193
+ def store_primitive_finding(
1194
+ repo_input: str, gap_id: str, severity: str,
1195
+ description: str, triggered_str: str, sandbox_output: str,
1196
+ ) -> str:
1197
+ try:
1198
+ from chain_analyzer import store_primitive
1199
+ triggered = "TRIGGERED: True" in (sandbox_output or "")
1200
+ fid = store_primitive(
1201
+ repo=repo_input.strip(), gap_id=gap_id.strip(),
1202
+ severity=severity.strip(), description=description.strip(),
1203
+ triggered=triggered, confidence="MEDIUM",
1204
+ harness_result={"stdout": sandbox_output},
1205
+ )
1206
+ return f"✅ Primitive stored with ID: {fid}"
1207
+ except Exception as e:
1208
+ return f"Error: {e}"
1209
+
1210
+
1211
+ def run_chain_analysis(repo_input: str) -> str:
1212
+ try:
1213
+ from chain_analyzer import analyze_chains, get_pending_chains
1214
+ repo = repo_input.strip()
1215
+ chains = analyze_chains(repo) if repo else []
1216
+ pending = get_pending_chains(repo if repo else None)
1217
+ if not pending:
1218
+ return "No chains identified yet. Store primitive findings first."
1219
+ lines = []
1220
+ for c in pending:
1221
+ lines.append(
1222
+ f"[{c['id']}] {c['severity']} | Confidence: {c['confidence']}\n"
1223
+ f" Repo: {c['repo']}\n"
1224
+ f" {c['description'][:200]}\n"
1225
+ f" Status: {c['status']}"
1226
+ )
1227
+ return f"Proposed chains (PENDING HUMAN REVIEW):\n\n" + "\n\n".join(lines)
1228
+ except Exception as e:
1229
+ return f"Error: {e}"
1230
+
1231
+
1232
+ def get_vault_display() -> str:
1233
+ try:
1234
+ from disclosure_vault import get_all_disclosures
1235
+ items = get_all_disclosures()
1236
+ if not items:
1237
+ return "No disclosures yet."
1238
+ lines = []
1239
+ for d in items:
1240
+ lines.append(
1241
+ f"[{d['id']}] {d['severity']} — {d['repo']}\n"
1242
+ f" Title : {d['title'][:70]}\n"
1243
+ f" Status: {d['status']} | Days remaining: {d['days_remaining']}\n"
1244
+ f" Bug Bounty: {d.get('bug_bounty_program','N/A')}"
1245
+ )
1246
+ return "\n\n".join(lines)
1247
+ except Exception as e:
1248
+ return f"Error: {e}"
1249
+
1250
+
1251
+ def read_dossier_fn(disclosure_id: str) -> str:
1252
+ try:
1253
+ from disclosure_vault import read_dossier
1254
+ return read_dossier(disclosure_id.strip())
1255
+ except Exception as e:
1256
+ return f"Error: {e}"
1257
+
1258
+
1259
+ def compile_dossier_fn(
1260
+ repo_input: str, gap_json: str, harness_result: str, bug_bounty: str,
1261
+ ) -> str:
1262
+ try:
1263
+ from disclosure_vault import compile_dossier
1264
+ gap = json.loads(gap_json) if gap_json.strip() else {}
1265
+ did = compile_dossier(
1266
+ repo=repo_input.strip(),
1267
+ semantic_graph={},
1268
+ assumption_gap=gap,
1269
+ harness_result={"stdout": harness_result, "triggered": "TRIGGERED: True" in harness_result},
1270
+ bug_bounty_program=bug_bounty.strip(),
1271
+ )
1272
+ return f"✅ Dossier compiled. Disclosure ID: {did}\nStatus: DRAFT — awaiting human approval."
1273
+ except Exception as e:
1274
+ return f"Error: {e}"
1275
+
1276
+
1277
+ def approve_and_prepare_msg(disclosure_id: str, approver: str) -> str:
1278
+ try:
1279
+ from disclosure_vault import approve_disclosure, prepare_disclosure_message
1280
+ approve_disclosure(disclosure_id.strip(), approver.strip() or "operator")
1281
+ msg = prepare_disclosure_message(disclosure_id.strip())
1282
+ return f"✅ Approved by: {approver}\n\n--- DISCLOSURE MESSAGE (send manually) ---\n\n{msg}"
1283
+ except Exception as e:
1284
+ return f"Error: {e}"
1285
+
1286
+
1287
+ def reject_disclosure_fn(disclosure_id: str) -> str:
1288
+ try:
1289
+ from disclosure_vault import reject_disclosure
1290
+ reject_disclosure(disclosure_id.strip())
1291
+ return f"✅ Disclosure {disclosure_id.strip()} rejected and archived."
1292
+ except Exception as e:
1293
+ return f"Error: {e}"
1294
+
1295
+
1296
  # ──────────────────────────────────────────────────────────────
1297
  # GRADIO ENTERPRISE DASHBOARD
1298
  # ──────────────────────────────────────────────────────────────
 
1578
  - Public leaderboard (`public_leaderboard.py`) — real numbers, real PRs, no fake metrics
1579
  """)
1580
 
1581
+ # ── TAB 11: ETHICAL SECURITY RESEARCH ────────────────────
1582
+ with gr.Tab("🔬 Security Research"):
1583
+ gr.Markdown("""
1584
+ ### Ethical Security Research Pipeline
1585
+
1586
+ Static analysis → Human review → Responsible disclosure
1587
+
1588
+ **Every stage requires explicit operator approval. Nothing is disclosed automatically.**
1589
+ All PoC testing is local and sandboxed. No live systems are attacked.
1590
+ """)
1591
+
1592
+ with gr.Tabs():
1593
+
1594
+ # Step 1 — Semantic Analysis
1595
+ with gr.Tab("1. Semantic Analysis"):
1596
+ gr.Markdown(
1597
+ "**Static analysis only — no code executed.** "
1598
+ "Hermes maps the repo's trust state machine and identifies assumption gaps."
1599
+ )
1600
+ sr_repo = gr.Textbox(
1601
+ label="Open-source repository (owner/repo)",
1602
+ placeholder="e.g. psf/requests or pallets/flask",
1603
+ )
1604
+ sr_analyze_btn = gr.Button("🔍 Run Semantic Analysis", variant="primary")
1605
+ sr_summary = gr.Textbox(label="Summary", interactive=False, lines=6)
1606
+ sr_graph = gr.TextArea(label="State Machine Graph + Assumption Gaps (JSON)", lines=22, interactive=False)
1607
+ sr_analyze_btn.click(
1608
+ run_semantic_analysis,
1609
+ inputs=sr_repo,
1610
+ outputs=[sr_summary, sr_graph],
1611
+ )
1612
+
1613
+ # Step 2 — Harness Generation
1614
+ with gr.Tab("2. Generate PoC (Review Only)"):
1615
+ gr.Markdown(
1616
+ "Paste a single assumption gap JSON from Step 1. "
1617
+ "Hermes generates a minimal PoC harness **for your review**. "
1618
+ "The harness is NOT executed here."
1619
+ )
1620
+ sr_gap_input = gr.TextArea(label="Assumption Gap JSON", lines=10)
1621
+ sr_repo2 = gr.Textbox(label="Repository (owner/repo)")
1622
+ sr_gen_btn = gr.Button("⚙️ Generate Harness for Review", variant="secondary")
1623
+ sr_harness = gr.TextArea(
1624
+ label="Generated Harness — READ CAREFULLY BEFORE PROCEEDING",
1625
+ lines=22, interactive=True,
1626
+ )
1627
+ sr_gen_btn.click(
1628
+ generate_harness_for_review,
1629
+ inputs=[sr_gap_input, sr_repo2],
1630
+ outputs=sr_harness,
1631
+ )
1632
+
1633
+ # Step 3 — Sandbox Execution
1634
+ with gr.Tab("3. Sandbox Execution (Operator Approved)"):
1635
+ gr.Markdown("""
1636
+ **By clicking Execute you confirm:**
1637
+ - You have read every line of the harness above
1638
+ - You authorise local sandbox execution only
1639
+ - No network connections will be made
1640
+ - Execution is time-limited to 30 seconds
1641
+ """)
1642
+ sr_exec_code = gr.TextArea(label="Harness Code (reviewed by operator)", lines=15)
1643
+ with gr.Row():
1644
+ sr_exec_repo = gr.Textbox(label="Repository (owner/repo)", scale=3)
1645
+ sr_exec_venv = gr.Textbox(label="Venv path", value="/data/target_venv", scale=2)
1646
+ sr_exec_btn = gr.Button("🚀 Execute in Sandbox (I have reviewed this code)", variant="primary")
1647
+ sr_exec_out = gr.TextArea(label="Sandbox Result", lines=12, interactive=False)
1648
+ sr_exec_btn.click(
1649
+ execute_approved_harness,
1650
+ inputs=[sr_exec_code, sr_exec_repo, sr_exec_venv],
1651
+ outputs=sr_exec_out,
1652
+ )
1653
+
1654
+ # Step 4 — Store & Chain Analysis
1655
+ with gr.Tab("4. Chain Analysis"):
1656
+ gr.Markdown(
1657
+ "Store primitive findings, then ask Hermes to propose theoretical chains. "
1658
+ "All chain proposals are tagged PENDING_HUMAN_REVIEW."
1659
+ )
1660
+ with gr.Row():
1661
+ sr_prim_repo = gr.Textbox(label="Repository", scale=2)
1662
+ sr_prim_gapid = gr.Textbox(label="Gap ID", scale=1)
1663
+ sr_prim_sev = gr.Textbox(label="Severity", value="P2", scale=1)
1664
+ sr_prim_desc = gr.Textbox(label="Description", lines=2)
1665
+ sr_prim_sandbox = gr.TextArea(label="Sandbox Output (from Step 3)", lines=5)
1666
+ sr_store_btn = gr.Button("💾 Store Primitive Finding", variant="secondary")
1667
+ sr_store_out = gr.Textbox(label="", interactive=False)
1668
+ sr_store_btn.click(
1669
+ store_primitive_finding,
1670
+ inputs=[sr_prim_repo, sr_prim_gapid, sr_prim_sev, sr_prim_desc, sr_prim_gapid, sr_prim_sandbox],
1671
+ outputs=sr_store_out,
1672
+ )
1673
+ gr.HTML("<hr/>")
1674
+ sr_chain_repo = gr.Textbox(label="Repository for chain analysis (leave blank for all)")
1675
+ sr_chain_btn = gr.Button("🔗 Analyse Chains", variant="secondary")
1676
+ sr_chain_out = gr.TextArea(label="Proposed Chains (PENDING HUMAN REVIEW)", lines=14, interactive=False)
1677
+ sr_chain_btn.click(run_chain_analysis, inputs=sr_chain_repo, outputs=sr_chain_out)
1678
+
1679
+ # Step 5 — Disclosure Vault
1680
+ with gr.Tab("5. Disclosure Vault"):
1681
+ gr.Markdown("""
1682
+ **Human approval is mandatory before any disclosure is sent.**
1683
+ Approved disclosures generate a message you send manually via the maintainer's security policy.
1684
+ """)
1685
+ with gr.Row():
1686
+ sr_vault_repo = gr.Textbox(label="Repository (owner/repo)", scale=3)
1687
+ sr_vault_bounty = gr.Textbox(label="Bug bounty programme URL", scale=2)
1688
+ sr_vault_gap = gr.TextArea(label="Assumption Gap JSON", lines=6)
1689
+ sr_vault_poc = gr.TextArea(label="Sandbox output from Step 3", lines=4)
1690
+ sr_compile_btn = gr.Button("📋 Compile Disclosure Dossier", variant="secondary")
1691
+ sr_compile_out = gr.Textbox(label="", interactive=False)
1692
+ sr_compile_btn.click(
1693
+ compile_dossier_fn,
1694
+ inputs=[sr_vault_repo, sr_vault_gap, sr_vault_poc, sr_vault_bounty],
1695
+ outputs=sr_compile_out,
1696
+ )
1697
+
1698
+ gr.HTML("<hr/>")
1699
+ gr.Button("🔄 Refresh Vault", variant="secondary").click(
1700
+ get_vault_display, outputs=gr.TextArea(label="All Disclosures", lines=10, interactive=False)
1701
+ )
1702
+
1703
+ gr.HTML("<hr/>")
1704
+ sr_did = gr.Textbox(label="Disclosure ID")
1705
+ sr_read_btn = gr.Button("📄 Read Full Dossier", variant="secondary")
1706
+ sr_dossier = gr.TextArea(label="Dossier (read before approving)", lines=24, interactive=False)
1707
+ sr_read_btn.click(read_dossier_fn, inputs=sr_did, outputs=sr_dossier)
1708
+
1709
+ gr.HTML("<hr/>")
1710
+ sr_approver = gr.Textbox(label="Your name (approval record)")
1711
+ with gr.Row():
1712
+ sr_approve_btn = gr.Button("✅ Approve & Prepare Disclosure Message", variant="primary")
1713
+ sr_reject_btn = gr.Button("❌ Reject & Archive", variant="secondary")
1714
+ sr_approval_out = gr.TextArea(label="Result / Disclosure Message (send manually)", lines=14, interactive=False)
1715
+ sr_approve_btn.click(
1716
+ approve_and_prepare_msg,
1717
+ inputs=[sr_did, sr_approver],
1718
+ outputs=sr_approval_out,
1719
+ )
1720
+ sr_reject_btn.click(reject_disclosure_fn, inputs=sr_did, outputs=sr_approval_out)
1721
+
1722
  # ── AUTO-REFRESH ────────────────────────────────────────────
1723
  timer = gr.Timer(3)
1724
  timer.tick(get_live_logs, outputs=live_logs)
chain_analyzer.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Rhodawk AI — Vulnerability Chain Analyzer
3
+ ==========================================
4
+ Documents how primitive findings (individual assumption gaps + PoC results)
5
+ might combine into higher-severity chains.
6
+
7
+ ETHICAL CONSTRAINTS:
8
+ - Chains are THEORETICAL proposals documented for human review
9
+ - No chain is automatically executed
10
+ - All chain proposals are stored with status PENDING_HUMAN_REVIEW
11
+ - Human operator must approve or reject every chain before any further action
12
+
13
+ Orchestrated by Nous Hermes 3 via OpenRouter.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import json
20
+ import os
21
+ import re
22
+ import sqlite3
23
+ import time
24
+ from typing import Optional
25
+
26
+ import requests
27
+
28
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
29
+ HERMES_MODEL = os.getenv(
30
+ "RHODAWK_RESEARCH_MODEL",
31
+ "nousresearch/hermes-3-llama-3.1-405b:free",
32
+ )
33
+ CHAIN_DB = os.getenv("RHODAWK_CHAIN_DB", "/data/chain_memory.sqlite")
34
+
35
+
36
+ def _init_db() -> None:
37
+ os.makedirs(os.path.dirname(CHAIN_DB), exist_ok=True)
38
+ conn = sqlite3.connect(CHAIN_DB)
39
+ conn.executescript("""
40
+ CREATE TABLE IF NOT EXISTS primitive_findings (
41
+ id TEXT PRIMARY KEY,
42
+ repo TEXT NOT NULL,
43
+ gap_id TEXT NOT NULL,
44
+ severity TEXT,
45
+ description TEXT,
46
+ triggered INTEGER DEFAULT 0,
47
+ confidence TEXT DEFAULT 'UNKNOWN',
48
+ created_at REAL,
49
+ harness_out TEXT
50
+ );
51
+ CREATE TABLE IF NOT EXISTS chains (
52
+ id TEXT PRIMARY KEY,
53
+ repo TEXT NOT NULL,
54
+ primitive_ids TEXT NOT NULL,
55
+ description TEXT,
56
+ chained_severity TEXT,
57
+ confidence TEXT,
58
+ conditions TEXT,
59
+ theoretical_impact TEXT,
60
+ human_notes TEXT,
61
+ status TEXT DEFAULT 'PENDING_HUMAN_REVIEW',
62
+ created_at REAL,
63
+ human_approved INTEGER DEFAULT 0,
64
+ human_reviewer TEXT,
65
+ reviewed_at REAL
66
+ );
67
+ """)
68
+ conn.commit()
69
+ conn.close()
70
+
71
+
72
+ def store_primitive(
73
+ repo: str,
74
+ gap_id: str,
75
+ severity: str,
76
+ description: str,
77
+ triggered: bool,
78
+ confidence: str = "UNKNOWN",
79
+ harness_result: Optional[dict] = None,
80
+ ) -> str:
81
+ """Persist a primitive finding from the harness execution."""
82
+ _init_db()
83
+ finding_id = hashlib.sha256(
84
+ f"{repo}:{gap_id}:{time.time()}".encode()
85
+ ).hexdigest()[:16]
86
+ conn = sqlite3.connect(CHAIN_DB)
87
+ conn.execute(
88
+ """INSERT OR REPLACE INTO primitive_findings
89
+ (id, repo, gap_id, severity, description, triggered, confidence, created_at, harness_out)
90
+ VALUES (?,?,?,?,?,?,?,?,?)""",
91
+ (
92
+ finding_id, repo, gap_id, severity, description,
93
+ 1 if triggered else 0, confidence, time.time(),
94
+ json.dumps(harness_result or {}),
95
+ ),
96
+ )
97
+ conn.commit()
98
+ conn.close()
99
+ return finding_id
100
+
101
+
102
+ def analyze_chains(repo: str) -> list[dict]:
103
+ """
104
+ Ask Hermes to propose vulnerability chains from stored primitives.
105
+
106
+ Returns THEORETICAL proposals — all tagged PENDING_HUMAN_REVIEW.
107
+ Nothing is executed automatically.
108
+ """
109
+ _init_db()
110
+ conn = sqlite3.connect(CHAIN_DB)
111
+ rows = conn.execute(
112
+ """SELECT id, gap_id, severity, description, triggered, confidence
113
+ FROM primitive_findings WHERE repo = ?
114
+ ORDER BY created_at DESC""",
115
+ (repo,),
116
+ ).fetchall()
117
+ conn.close()
118
+
119
+ if len(rows) < 2:
120
+ return []
121
+
122
+ primitives_text = "\n".join(
123
+ f"- [{r[0]}] Gap: {r[1]} | Sev: {r[2]} | Triggered: {bool(r[4])} "
124
+ f"| Confidence: {r[5]} | {r[3][:120]}"
125
+ for r in rows
126
+ )
127
+
128
+ headers = {
129
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
130
+ "Content-Type": "application/json",
131
+ "HTTP-Referer": "https://rhodawk.ai",
132
+ }
133
+ payload = {
134
+ "model": HERMES_MODEL,
135
+ "messages": [
136
+ {
137
+ "role": "system",
138
+ "content": (
139
+ "You are a senior security researcher conducting responsible vulnerability research. "
140
+ "Analyse primitive findings and propose THEORETICAL vulnerability chains for human review. "
141
+ "Be rigorous and conservative — only propose chains that are logically sound based on "
142
+ "the available evidence. Mark any speculation clearly. Output valid JSON only."
143
+ ),
144
+ },
145
+ {
146
+ "role": "user",
147
+ "content": (
148
+ f"Analyse these primitive findings from {repo} and identify plausible chains.\n\n"
149
+ f"PRIMITIVES:\n{primitives_text}\n\n"
150
+ "Output ONLY this JSON:\n"
151
+ '{"chains": [{'
152
+ '"primitive_ids": ["id1","id2"],'
153
+ '"description": "Step-by-step logical chain",'
154
+ '"chained_severity": "P1|P2|P3",'
155
+ '"confidence": "HIGH|MEDIUM|LOW",'
156
+ '"required_conditions": ["condition1"],'
157
+ '"theoretical_impact": "What an attacker could theoretically achieve",'
158
+ '"human_verification_needed": "What a human researcher must manually verify before treating this as real"'
159
+ "}]}"
160
+ ),
161
+ },
162
+ ],
163
+ "max_tokens": 2048,
164
+ "temperature": 0.1,
165
+ }
166
+
167
+ try:
168
+ resp = requests.post(
169
+ "https://openrouter.ai/api/v1/chat/completions",
170
+ headers=headers, json=payload, timeout=120,
171
+ )
172
+ resp.raise_for_status()
173
+ raw = resp.json()["choices"][0]["message"]["content"]
174
+
175
+ match = re.search(r"\{[\s\S]*\}", raw)
176
+ if not match:
177
+ return []
178
+
179
+ data = json.loads(match.group())
180
+ chains = data.get("chains", [])
181
+
182
+ conn = sqlite3.connect(CHAIN_DB)
183
+ for chain in chains:
184
+ chain_id = hashlib.sha256(
185
+ f"{repo}:{':'.join(chain.get('primitive_ids', []))}:{time.time()}".encode()
186
+ ).hexdigest()[:16]
187
+ chain["id"] = chain_id
188
+ conn.execute(
189
+ """INSERT OR IGNORE INTO chains
190
+ (id, repo, primitive_ids, description, chained_severity, confidence,
191
+ conditions, theoretical_impact, status, created_at)
192
+ VALUES (?,?,?,?,?,?,?,?,'PENDING_HUMAN_REVIEW',?)""",
193
+ (
194
+ chain_id, repo,
195
+ json.dumps(chain.get("primitive_ids", [])),
196
+ chain.get("description", ""),
197
+ chain.get("chained_severity", "P3"),
198
+ chain.get("confidence", "LOW"),
199
+ json.dumps(chain.get("required_conditions", [])),
200
+ chain.get("theoretical_impact", ""),
201
+ time.time(),
202
+ ),
203
+ )
204
+ conn.commit()
205
+ conn.close()
206
+ return chains
207
+
208
+ except Exception as e:
209
+ return [{"error": str(e)}]
210
+
211
+
212
+ def get_pending_chains(repo: Optional[str] = None) -> list[dict]:
213
+ _init_db()
214
+ conn = sqlite3.connect(CHAIN_DB)
215
+ if repo:
216
+ rows = conn.execute(
217
+ """SELECT id, repo, description, chained_severity, confidence, status, created_at
218
+ FROM chains WHERE repo=? AND status='PENDING_HUMAN_REVIEW'
219
+ ORDER BY created_at DESC""",
220
+ (repo,),
221
+ ).fetchall()
222
+ else:
223
+ rows = conn.execute(
224
+ """SELECT id, repo, description, chained_severity, confidence, status, created_at
225
+ FROM chains WHERE status='PENDING_HUMAN_REVIEW'
226
+ ORDER BY created_at DESC""",
227
+ ).fetchall()
228
+ conn.close()
229
+ return [
230
+ {
231
+ "id": r[0], "repo": r[1], "description": r[2],
232
+ "severity": r[3], "confidence": r[4], "status": r[5], "created_at": r[6],
233
+ }
234
+ for r in rows
235
+ ]
236
+
237
+
238
+ def get_all_primitives(repo: Optional[str] = None) -> list[dict]:
239
+ _init_db()
240
+ conn = sqlite3.connect(CHAIN_DB)
241
+ if repo:
242
+ rows = conn.execute(
243
+ "SELECT id, repo, gap_id, severity, description, triggered, confidence, created_at "
244
+ "FROM primitive_findings WHERE repo=? ORDER BY created_at DESC",
245
+ (repo,),
246
+ ).fetchall()
247
+ else:
248
+ rows = conn.execute(
249
+ "SELECT id, repo, gap_id, severity, description, triggered, confidence, created_at "
250
+ "FROM primitive_findings ORDER BY created_at DESC",
251
+ ).fetchall()
252
+ conn.close()
253
+ return [
254
+ {
255
+ "id": r[0], "repo": r[1], "gap_id": r[2], "severity": r[3],
256
+ "description": r[4], "triggered": bool(r[5]),
257
+ "confidence": r[6], "created_at": r[7],
258
+ }
259
+ for r in rows
260
+ ]
261
+
262
+
263
+ def approve_chain(chain_id: str, reviewer: str) -> bool:
264
+ _init_db()
265
+ conn = sqlite3.connect(CHAIN_DB)
266
+ conn.execute(
267
+ "UPDATE chains SET status='HUMAN_APPROVED', human_approved=1, "
268
+ "human_reviewer=?, reviewed_at=? WHERE id=?",
269
+ (reviewer, time.time(), chain_id),
270
+ )
271
+ conn.commit()
272
+ conn.close()
273
+ return True
274
+
275
+
276
+ def reject_chain(chain_id: str, reviewer: str) -> bool:
277
+ _init_db()
278
+ conn = sqlite3.connect(CHAIN_DB)
279
+ conn.execute(
280
+ "UPDATE chains SET status='HUMAN_REJECTED', human_reviewer=?, reviewed_at=? WHERE id=?",
281
+ (reviewer, time.time(), chain_id),
282
+ )
283
+ conn.commit()
284
+ conn.close()
285
+ return True
disclosure_vault.py ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Rhodawk AI — Responsible Disclosure Vault
3
+ ==========================================
4
+ Manages the complete responsible disclosure lifecycle with a mandatory
5
+ human approval gate at every stage.
6
+
7
+ DISCLOSURE POLICY (non-negotiable):
8
+ 1. ALL findings start as DRAFT — nothing is shared externally
9
+ 2. Human operator must read the full dossier and click Approve
10
+ 3. After approval, the system generates a disclosure message —
11
+ the operator sends it via the maintainer's own security channel
12
+ 4. Standard 90-day responsible disclosure timeline is tracked
13
+ 5. Bug bounty submissions are prepared for human submission —
14
+ never automated
15
+ 6. No GitHub API writes in AVR mode
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import json
22
+ import os
23
+ import sqlite3
24
+ import time
25
+ from pathlib import Path
26
+ from typing import Optional
27
+
28
+ VAULT_DB = os.getenv("RHODAWK_VAULT_DB", "/data/disclosure_vault.sqlite")
29
+ VAULT_DIR = os.getenv("RHODAWK_VAULT_DIR", "/data/vault")
30
+ DISCLOSURE_DAYS = int(os.getenv("RHODAWK_DISCLOSURE_DAYS", "90"))
31
+
32
+
33
+ def _init_db() -> None:
34
+ os.makedirs(os.path.dirname(VAULT_DB), exist_ok=True)
35
+ os.makedirs(VAULT_DIR, exist_ok=True)
36
+ conn = sqlite3.connect(VAULT_DB)
37
+ conn.executescript("""
38
+ CREATE TABLE IF NOT EXISTS disclosures (
39
+ id TEXT PRIMARY KEY,
40
+ repo TEXT NOT NULL,
41
+ severity TEXT NOT NULL,
42
+ title TEXT NOT NULL,
43
+ status TEXT DEFAULT 'DRAFT',
44
+ created_at REAL,
45
+ human_approved INTEGER DEFAULT 0,
46
+ approved_by TEXT,
47
+ approved_at REAL,
48
+ disclosed_at REAL,
49
+ deadline_at REAL,
50
+ dossier_path TEXT,
51
+ bug_bounty_program TEXT,
52
+ maintainer_contact TEXT
53
+ );
54
+ """)
55
+ conn.commit()
56
+ conn.close()
57
+
58
+
59
+ def compile_dossier(
60
+ repo: str,
61
+ semantic_graph: dict,
62
+ assumption_gap: dict,
63
+ harness_result: dict,
64
+ chain_analysis: Optional[list] = None,
65
+ bug_bounty_program: str = "",
66
+ maintainer_contact: str = "",
67
+ ) -> str:
68
+ """
69
+ Compile a structured responsible disclosure dossier.
70
+ Stored locally — NOT sent anywhere until a human operator approves.
71
+ Returns the disclosure ID.
72
+ """
73
+ _init_db()
74
+
75
+ disclosure_id = hashlib.sha256(
76
+ f"{repo}:{assumption_gap.get('id','')}:{time.time()}".encode()
77
+ ).hexdigest()[:16]
78
+
79
+ deadline_ts = time.time() + (DISCLOSURE_DAYS * 86400)
80
+ severity = assumption_gap.get("severity_hypothesis", "P3")
81
+ gap_desc = assumption_gap.get("description", "N/A")[:120]
82
+
83
+ triggered_str = str(harness_result.get("triggered", "Not tested"))
84
+ poc_output = harness_result.get("stdout", "N/A")[:1500]
85
+ chain_block = (
86
+ json.dumps(chain_analysis, indent=2)
87
+ if chain_analysis
88
+ else "No chains identified."
89
+ )
90
+
91
+ trust_states = json.dumps(semantic_graph.get("trust_states", []), indent=2)[:2000]
92
+ transitions = json.dumps(semantic_graph.get("transitions", []), indent=2)[:1000]
93
+
94
+ dossier = f"""# Responsible Disclosure Report — {disclosure_id}
95
+
96
+ > **STATUS: DRAFT — PENDING HUMAN OPERATOR REVIEW**
97
+ > This report has NOT been sent to any maintainer or bug bounty programme.
98
+ > No live system has been tested or attacked.
99
+
100
+ ---
101
+
102
+ | Field | Value |
103
+ |---|---|
104
+ | **Disclosure ID** | `{disclosure_id}` |
105
+ | **Repository** | `{repo}` |
106
+ | **Severity Hypothesis** | **{severity}** |
107
+ | **Disclosure Deadline** | {time.strftime('%Y-%m-%d', time.localtime(deadline_ts))} ({DISCLOSURE_DAYS}-day standard) |
108
+ | **Bug Bounty Programme** | {bug_bounty_program or "Not specified"} |
109
+ | **Maintainer Contact** | {maintainer_contact or "See SECURITY.md"} |
110
+ | **Created** | {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())} |
111
+
112
+ ---
113
+
114
+ ## ⚠️ Operator Action Required
115
+
116
+ Before this disclosure proceeds, you must:
117
+
118
+ - [ ] Read and understand the full dossier
119
+ - [ ] Independently verify the assumption gap description is accurate
120
+ - [ ] Confirm the PoC result matches what is claimed
121
+ - [ ] Verify the target repo has a responsible disclosure policy or bug bounty programme
122
+ - [ ] Click **Approve** in the Rhodawk Security Research dashboard
123
+
124
+ ---
125
+
126
+ ## 1. Executive Summary
127
+
128
+ **Finding:** {assumption_gap.get('description', 'N/A')}
129
+
130
+ **File:** `{assumption_gap.get('file', 'N/A')}`
131
+ **Location:** `{assumption_gap.get('line_hint', 'N/A')}`
132
+ **Confidence:** {assumption_gap.get('confidence', 'UNKNOWN')}
133
+
134
+ ---
135
+
136
+ ## 2. State Machine Analysis
137
+
138
+ **Untrusted Input Path:**
139
+ {assumption_gap.get('untrusted_input', 'N/A')}
140
+
141
+ **Bypassed or Insufficient Check:**
142
+ {assumption_gap.get('bypassed_check', 'N/A')}
143
+
144
+ **Theoretical Impact:**
145
+ {assumption_gap.get('potential_impact', 'N/A')}
146
+
147
+ ### Trust States Identified
148
+
149
+ ```json
150
+ {trust_states}
151
+ ```
152
+
153
+ ### State Transitions
154
+
155
+ ```json
156
+ {transitions}
157
+ ```
158
+
159
+ ---
160
+
161
+ ## 3. Proof of Concept (Local Sandbox Only)
162
+
163
+ > All PoC testing was performed against a locally cloned copy of the repository.
164
+ > No live/production system was accessed.
165
+
166
+ **Gap Triggered in Sandbox:** {triggered_str}
167
+ **Exit Code:** {harness_result.get('exit_code', 'N/A')}
168
+ **Timed Out:** {harness_result.get('timed_out', False)}
169
+
170
+ **Sandbox Output:**
171
+ ```
172
+ {poc_output}
173
+ ```
174
+
175
+ ---
176
+
177
+ ## 4. Vulnerability Chain Analysis (Theoretical)
178
+
179
+ {chain_block}
180
+
181
+ > All chain proposals above are theoretical and require independent human verification.
182
+
183
+ ---
184
+
185
+ ## 5. Responsible Disclosure Next Steps
186
+
187
+ 1. **Operator** reviews and verifies this dossier
188
+ 2. **Operator** approves disclosure via Rhodawk dashboard
189
+ 3. **Operator** contacts maintainer via their `SECURITY.md` / `security@` policy
190
+ 4. Submit to bug bounty programme if applicable: `{bug_bounty_program or 'N/A'}`
191
+ 5. Allow **{DISCLOSURE_DAYS} days** for maintainer to produce a fix
192
+ 6. Coordinate public disclosure date with maintainer
193
+
194
+ ---
195
+
196
+ *Generated by Rhodawk AI Ethical Security Research Platform*
197
+ *All findings require human verification and explicit approval before disclosure*
198
+ *No automated exploitation of live systems is performed*
199
+ """
200
+
201
+ dossier_path = os.path.join(VAULT_DIR, f"{disclosure_id}.md")
202
+ Path(dossier_path).write_text(dossier, encoding="utf-8")
203
+
204
+ conn = sqlite3.connect(VAULT_DB)
205
+ conn.execute(
206
+ """INSERT INTO disclosures
207
+ (id, repo, severity, title, status, created_at, deadline_at,
208
+ dossier_path, bug_bounty_program, maintainer_contact)
209
+ VALUES (?,?,?,?, 'DRAFT',?,?, ?,?,?)""",
210
+ (
211
+ disclosure_id, repo, severity,
212
+ f"{severity} — {gap_desc}",
213
+ time.time(), deadline_ts,
214
+ dossier_path, bug_bounty_program, maintainer_contact,
215
+ ),
216
+ )
217
+ conn.commit()
218
+ conn.close()
219
+
220
+ return disclosure_id
221
+
222
+
223
+ def get_pending_disclosures() -> list[dict]:
224
+ _init_db()
225
+ conn = sqlite3.connect(VAULT_DB)
226
+ rows = conn.execute(
227
+ """SELECT id, repo, severity, title, status, created_at, deadline_at, bug_bounty_program
228
+ FROM disclosures WHERE status IN ('DRAFT','HUMAN_APPROVED')
229
+ ORDER BY created_at DESC"""
230
+ ).fetchall()
231
+ conn.close()
232
+ now = time.time()
233
+ return [
234
+ {
235
+ "id": r[0], "repo": r[1], "severity": r[2], "title": r[3],
236
+ "status": r[4], "created_at": r[5],
237
+ "days_remaining": max(0, int((r[6] - now) / 86400)) if r[6] else DISCLOSURE_DAYS,
238
+ "bug_bounty_program": r[7] or "N/A",
239
+ }
240
+ for r in rows
241
+ ]
242
+
243
+
244
+ def get_all_disclosures() -> list[dict]:
245
+ _init_db()
246
+ conn = sqlite3.connect(VAULT_DB)
247
+ rows = conn.execute(
248
+ "SELECT id, repo, severity, title, status, created_at, deadline_at, bug_bounty_program "
249
+ "FROM disclosures ORDER BY created_at DESC"
250
+ ).fetchall()
251
+ conn.close()
252
+ now = time.time()
253
+ return [
254
+ {
255
+ "id": r[0], "repo": r[1], "severity": r[2], "title": r[3],
256
+ "status": r[4], "created_at": r[5],
257
+ "days_remaining": max(0, int((r[6] - now) / 86400)) if r[6] else DISCLOSURE_DAYS,
258
+ "bug_bounty_program": r[7] or "N/A",
259
+ }
260
+ for r in rows
261
+ ]
262
+
263
+
264
+ def read_dossier(disclosure_id: str) -> str:
265
+ _init_db()
266
+ conn = sqlite3.connect(VAULT_DB)
267
+ row = conn.execute(
268
+ "SELECT dossier_path FROM disclosures WHERE id=?", (disclosure_id,)
269
+ ).fetchone()
270
+ conn.close()
271
+ if not row or not row[0]:
272
+ return f"Dossier not found for ID: {disclosure_id}"
273
+ try:
274
+ return Path(row[0]).read_text(encoding="utf-8")
275
+ except Exception as e:
276
+ return f"Error reading dossier: {e}"
277
+
278
+
279
+ def approve_disclosure(disclosure_id: str, approved_by: str) -> bool:
280
+ """Human operator explicitly approves a finding for disclosure."""
281
+ _init_db()
282
+ conn = sqlite3.connect(VAULT_DB)
283
+ conn.execute(
284
+ "UPDATE disclosures SET status='HUMAN_APPROVED', human_approved=1, "
285
+ "approved_by=?, approved_at=? WHERE id=?",
286
+ (approved_by, time.time(), disclosure_id),
287
+ )
288
+ conn.commit()
289
+ conn.close()
290
+ return True
291
+
292
+
293
+ def reject_disclosure(disclosure_id: str, reason: str = "") -> bool:
294
+ """Human operator rejects / archives a finding."""
295
+ _init_db()
296
+ conn = sqlite3.connect(VAULT_DB)
297
+ conn.execute(
298
+ "UPDATE disclosures SET status='REJECTED' WHERE id=?",
299
+ (disclosure_id,),
300
+ )
301
+ conn.commit()
302
+ conn.close()
303
+ return True
304
+
305
+
306
+ def prepare_disclosure_message(disclosure_id: str) -> str:
307
+ """
308
+ After human approval, generate the message for the operator to send
309
+ to the maintainer via THEIR preferred channel (SECURITY.md / email / HackerOne).
310
+
311
+ The operator sends this manually — it is never automated.
312
+ """
313
+ _init_db()
314
+ conn = sqlite3.connect(VAULT_DB)
315
+ row = conn.execute(
316
+ "SELECT repo, severity, title, bug_bounty_program, human_approved, approved_by "
317
+ "FROM disclosures WHERE id=?",
318
+ (disclosure_id,),
319
+ ).fetchone()
320
+ conn.close()
321
+
322
+ if not row:
323
+ return "Disclosure not found."
324
+ if not row[4]:
325
+ return "ERROR: Human approval is required before generating a disclosure message."
326
+
327
+ repo, severity, title, bounty, _, approved_by = row
328
+
329
+ msg = f"""Subject: Responsible Disclosure — {severity} Finding in {repo}
330
+
331
+ Hello {repo.split('/')[0]} security team,
332
+
333
+ I am reaching out as part of responsible security research conducted through the Rhodawk AI ethical research platform.
334
+
335
+ We have identified a potential {severity}-severity security finding in `{repo}`.
336
+
337
+ **Finding:** {title}
338
+
339
+ **Disclosure ID:** `{disclosure_id}`
340
+ **Disclosure Deadline:** {DISCLOSURE_DAYS} days from today (industry standard)
341
+ **Bug Bounty Programme:** {bounty or "N/A"}
342
+
343
+ We have prepared a full technical dossier including:
344
+ - State machine analysis
345
+ - Proof-of-concept (local sandbox only — no live systems tested)
346
+ - Theoretical vulnerability chain analysis
347
+
348
+ We would like to coordinate disclosure privately before any public disclosure.
349
+ Please let us know your preferred communication channel and we will share the full dossier.
350
+
351
+ This report was reviewed and approved for disclosure by a human researcher before being sent.
352
+
353
+ Respectfully,
354
+ Rhodawk AI Security Research Team
355
+ """
356
+
357
+ conn = sqlite3.connect(VAULT_DB)
358
+ conn.execute(
359
+ "UPDATE disclosures SET status='DISCLOSED', disclosed_at=? WHERE id=?",
360
+ (time.time(), disclosure_id),
361
+ )
362
+ conn.commit()
363
+ conn.close()
364
+
365
+ return msg
harness_factory.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Rhodawk AI — Ethical PoC Harness Factory
3
+ =========================================
4
+ Generates minimal proof-of-concept test harnesses that exercise identified
5
+ assumption gaps LOCALLY in an isolated sandbox.
6
+
7
+ ETHICAL CONSTRAINTS (hard-coded, not configurable):
8
+ - Generated harnesses target only locally cloned source code
9
+ - No network calls from within generated harnesses
10
+ - Execution is time-limited (default 30 s)
11
+ - All secrets stripped from sandbox environment
12
+ - Harness code is shown to operator BEFORE execution — never auto-run
13
+ - Output is PoC-grade only: demonstrates behaviour, not weaponised
14
+
15
+ Orchestrated by Nous Hermes 3 via OpenRouter.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ import re
22
+ import subprocess
23
+ import tempfile
24
+ import time
25
+ from pathlib import Path
26
+ from typing import Optional
27
+
28
+ import requests
29
+
30
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
31
+ HERMES_MODEL = os.getenv(
32
+ "RHODAWK_RESEARCH_MODEL",
33
+ "nousresearch/hermes-3-llama-3.1-405b:free",
34
+ )
35
+ HARNESS_TIMEOUT = int(os.getenv("RHODAWK_HARNESS_TIMEOUT", "30"))
36
+
37
+ _SECRETS = [
38
+ "OPENROUTER_API_KEY", "GITHUB_TOKEN", "GITHUB_PERSONAL_ACCESS_TOKEN",
39
+ "TELEGRAM_BOT_TOKEN", "SLACK_WEBHOOK_URL", "RHODAWK_WEBHOOK_SECRET",
40
+ ]
41
+
42
+ _HARNESS_PREAMBLE = """\
43
+ # ETHICAL POC — FOR RESPONSIBLE DISCLOSURE ONLY
44
+ # Generated by Rhodawk AI Ethical Security Research Platform
45
+ # This harness tests local code only — no network calls, no privilege escalation
46
+ # REVIEW CAREFULLY before authorising sandbox execution
47
+ """
48
+
49
+
50
+ def _hermes(system: str, user: str) -> str:
51
+ headers = {
52
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
53
+ "Content-Type": "application/json",
54
+ "HTTP-Referer": "https://rhodawk.ai",
55
+ }
56
+ payload = {
57
+ "model": HERMES_MODEL,
58
+ "messages": [
59
+ {"role": "system", "content": system},
60
+ {"role": "user", "content": user},
61
+ ],
62
+ "max_tokens": 4096,
63
+ "temperature": 0.1,
64
+ }
65
+ resp = requests.post(
66
+ "https://openrouter.ai/api/v1/chat/completions",
67
+ headers=headers, json=payload, timeout=120,
68
+ )
69
+ resp.raise_for_status()
70
+ return resp.json()["choices"][0]["message"]["content"]
71
+
72
+
73
+ def generate_poc_harness(assumption_gap: dict, repo_dir: str) -> dict:
74
+ """
75
+ Ask Hermes to generate a minimal PoC harness for a specific assumption gap.
76
+
77
+ The harness is returned for HUMAN REVIEW — it is NOT executed here.
78
+ Status is always 'PENDING_HUMAN_REVIEW' until the operator explicitly
79
+ calls run_harness_in_sandbox().
80
+ """
81
+ gap_id = assumption_gap.get("id", "unknown")
82
+ gap_file = assumption_gap.get("file", "unknown")
83
+ gap_desc = assumption_gap.get("description", "")
84
+ gap_input = assumption_gap.get("untrusted_input", "")
85
+ gap_check = assumption_gap.get("bypassed_check", "")
86
+ gap_impact = assumption_gap.get("potential_impact", "")
87
+
88
+ src_snippet = ""
89
+ if gap_file and gap_file != "unknown":
90
+ full_path = os.path.join(repo_dir, gap_file)
91
+ if os.path.exists(full_path):
92
+ try:
93
+ lines = Path(full_path).read_text(
94
+ encoding="utf-8", errors="replace"
95
+ ).splitlines()[:120]
96
+ src_snippet = "\n".join(lines)
97
+ except Exception:
98
+ pass
99
+
100
+ system = (
101
+ "You are a responsible security researcher generating a minimal proof-of-concept. "
102
+ "RULES:\n"
103
+ "1. The harness imports and tests LOCAL code only — absolutely no network calls, "
104
+ " no socket connections, no subprocess calls to external tools.\n"
105
+ "2. It must run in under 30 seconds.\n"
106
+ "3. Last line must print exactly: TRIGGERED: True OR TRIGGERED: False\n"
107
+ "4. It must NOT write files outside /tmp, escalate privileges, or exfiltrate data.\n"
108
+ "5. PoC-grade only — demonstrates the behaviour, is not a weapon.\n"
109
+ "6. Begin with the ethical preamble comment block shown in the user message."
110
+ )
111
+
112
+ user = (
113
+ f"Generate a minimal Python PoC harness for this assumption gap.\n\n"
114
+ f"Gap ID: {gap_id}\n"
115
+ f"File: {gap_file}\n"
116
+ f"Description: {gap_desc}\n"
117
+ f"Untrusted input path: {gap_input}\n"
118
+ f"Bypassed check: {gap_check}\n"
119
+ f"Theoretical impact: {gap_impact}\n"
120
+ f"Repo path: {repo_dir}\n\n"
121
+ f"Relevant source snippet:\n```\n{src_snippet[:3000]}\n```\n\n"
122
+ f"Start the file with this exact preamble:\n{_HARNESS_PREAMBLE}\n"
123
+ f"Output ONLY the Python harness code — no explanation, no markdown fences."
124
+ )
125
+
126
+ try:
127
+ raw = _hermes(system, user)
128
+ code = re.sub(r"^```(?:python)?\n?", "", raw.strip(), flags=re.MULTILINE)
129
+ code = re.sub(r"\n?```$", "", code.strip())
130
+
131
+ if _HARNESS_PREAMBLE.strip().splitlines()[0] not in code:
132
+ code = _HARNESS_PREAMBLE + "\n" + code
133
+
134
+ return {
135
+ "gap_id": gap_id,
136
+ "file": gap_file,
137
+ "harness_code": code,
138
+ "status": "PENDING_HUMAN_REVIEW",
139
+ "executed": False,
140
+ "result": None,
141
+ "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
142
+ }
143
+ except Exception as e:
144
+ return {
145
+ "gap_id": gap_id,
146
+ "error": str(e),
147
+ "status": "GENERATION_FAILED",
148
+ }
149
+
150
+
151
+ def run_harness_in_sandbox(
152
+ harness_code: str,
153
+ repo_dir: str,
154
+ venv_dir: str = "/data/target_venv",
155
+ ) -> dict:
156
+ """
157
+ Execute a HUMAN-REVIEWED harness in an isolated local sandbox.
158
+
159
+ This function must only be called after the operator has:
160
+ 1. Read the harness code
161
+ 2. Clicked "I have reviewed this code" in the UI
162
+
163
+ No network access, secrets stripped, time-limited.
164
+ """
165
+ if not harness_code.strip():
166
+ return {"error": "Empty harness code.", "triggered": False}
167
+
168
+ python_bin = (
169
+ os.path.join(venv_dir, "bin", "python")
170
+ if venv_dir and os.path.exists(os.path.join(venv_dir, "bin", "python"))
171
+ else "python3"
172
+ )
173
+
174
+ fd, harness_path = tempfile.mkstemp(
175
+ prefix="rhodawk_poc_", suffix=".py", dir="/tmp"
176
+ )
177
+ try:
178
+ with os.fdopen(fd, "w") as f:
179
+ f.write(harness_code)
180
+
181
+ env = os.environ.copy()
182
+ for secret in _SECRETS:
183
+ env.pop(secret, None)
184
+ env["PYTHONPATH"] = repo_dir
185
+ env["PYTHONDONTWRITEBYTECODE"] = "1"
186
+
187
+ proc = subprocess.run(
188
+ [python_bin, harness_path],
189
+ capture_output=True,
190
+ text=True,
191
+ timeout=HARNESS_TIMEOUT,
192
+ cwd=repo_dir,
193
+ env=env,
194
+ )
195
+
196
+ stdout = proc.stdout[:3000]
197
+ triggered = "TRIGGERED: True" in stdout
198
+
199
+ return {
200
+ "exit_code": proc.returncode,
201
+ "stdout": stdout,
202
+ "stderr": proc.stderr[:1000],
203
+ "triggered": triggered,
204
+ "timed_out": False,
205
+ "executed_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
206
+ }
207
+
208
+ except subprocess.TimeoutExpired:
209
+ return {
210
+ "triggered": False,
211
+ "timed_out": True,
212
+ "error": f"Harness timed out after {HARNESS_TIMEOUT}s — gap may not be triggerable.",
213
+ }
214
+ except Exception as e:
215
+ return {"triggered": False, "error": str(e)}
216
+ finally:
217
+ try:
218
+ os.unlink(harness_path)
219
+ except OSError:
220
+ pass
semantic_extractor.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Rhodawk AI — Semantic Logic Extractor (Ethical Research Mode)
3
+ =============================================================
4
+ STATIC ANALYSIS ONLY — no code is executed by this module.
5
+
6
+ Maps the application's trust state machine across files to identify
7
+ "Assumption Gaps" — points where developer intent diverges from actual
8
+ code behaviour. All output is JSON for human review.
9
+
10
+ Orchestrated by Nous Hermes 3 via OpenRouter.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import glob
16
+ import json
17
+ import os
18
+ import re
19
+ import time
20
+ from pathlib import Path
21
+ from typing import Optional
22
+
23
+ import requests
24
+
25
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
26
+ HERMES_MODEL = os.getenv(
27
+ "RHODAWK_RESEARCH_MODEL",
28
+ "nousresearch/hermes-3-llama-3.1-405b:free",
29
+ )
30
+
31
+ _PRIORITY_KEYWORDS = [
32
+ "auth", "token", "session", "permission", "privilege", "trust",
33
+ "validate", "sanitize", "parse", "decode", "deserialize", "marshal",
34
+ "memory", "alloc", "buffer", "exec", "eval", "inject", "sign", "verify",
35
+ "secret", "password", "credential", "acl", "role", "scope", "grant",
36
+ ]
37
+
38
+ _SKIP_DIRS = {".git", "vendor", "node_modules", "__pycache__", ".tox", "dist", "build"}
39
+
40
+
41
+ def _hermes(system: str, user: str, max_tokens: int = 4096) -> str:
42
+ """Call Nous Hermes 3 via OpenRouter."""
43
+ headers = {
44
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
45
+ "Content-Type": "application/json",
46
+ "HTTP-Referer": "https://rhodawk.ai",
47
+ "X-Title": "Rhodawk Ethical Security Research",
48
+ }
49
+ payload = {
50
+ "model": HERMES_MODEL,
51
+ "messages": [
52
+ {"role": "system", "content": system},
53
+ {"role": "user", "content": user},
54
+ ],
55
+ "max_tokens": max_tokens,
56
+ "temperature": 0.1,
57
+ }
58
+ resp = requests.post(
59
+ "https://openrouter.ai/api/v1/chat/completions",
60
+ headers=headers, json=payload, timeout=120,
61
+ )
62
+ resp.raise_for_status()
63
+ return resp.json()["choices"][0]["message"]["content"]
64
+
65
+
66
+ def _find_relevant_files(repo_dir: str, language: str) -> list[str]:
67
+ lang_patterns: dict[str, list[str]] = {
68
+ "python": ["**/*.py"],
69
+ "javascript": ["**/*.js", "**/*.mjs"],
70
+ "typescript": ["**/*.ts"],
71
+ "go": ["**/*.go"],
72
+ "java": ["**/*.java"],
73
+ "rust": ["**/*.rs"],
74
+ "c": ["**/*.c", "**/*.h"],
75
+ "cpp": ["**/*.cpp", "**/*.hpp", "**/*.h"],
76
+ "ruby": ["**/*.rb"],
77
+ }
78
+ patterns = lang_patterns.get(language.lower(), ["**/*.py"])
79
+
80
+ all_files: list[str] = []
81
+ for pat in patterns:
82
+ for path in glob.glob(os.path.join(repo_dir, pat), recursive=True):
83
+ rel = os.path.relpath(path, repo_dir)
84
+ if any(s in rel for s in _SKIP_DIRS):
85
+ continue
86
+ all_files.append(rel)
87
+
88
+ priority = [f for f in all_files if any(kw in f.lower() for kw in _PRIORITY_KEYWORDS)]
89
+ rest = [f for f in all_files if f not in priority]
90
+ return (priority + rest)[:30]
91
+
92
+
93
+ def _read_file_head(repo_dir: str, rel_path: str, max_lines: int = 200) -> str:
94
+ try:
95
+ text = Path(os.path.join(repo_dir, rel_path)).read_text(
96
+ encoding="utf-8", errors="replace"
97
+ )
98
+ return "\n".join(text.splitlines()[:max_lines])
99
+ except Exception:
100
+ return ""
101
+
102
+
103
+ def _extract_json(text: str) -> dict:
104
+ match = re.search(r"\{[\s\S]*\}", text)
105
+ if match:
106
+ try:
107
+ return json.loads(match.group())
108
+ except json.JSONDecodeError:
109
+ pass
110
+ return {}
111
+
112
+
113
+ def extract_trust_boundaries(repo_dir: str, file_paths: list[str]) -> dict:
114
+ """
115
+ Static analysis pass: asks Hermes to map trust states and find assumption gaps.
116
+ Returns a JSON-serialisable state machine graph.
117
+ """
118
+ snippets = []
119
+ for rel in file_paths[:20]:
120
+ head = _read_file_head(repo_dir, rel)
121
+ if head:
122
+ snippets.append(f"=== {rel} ===\n{head}")
123
+
124
+ combined = "\n\n".join(snippets)[:14000]
125
+
126
+ system = (
127
+ "You are a senior security researcher conducting responsible vulnerability research. "
128
+ "You perform STATIC analysis only — you never execute code. "
129
+ "Your goal is to map trust state machines and identify assumption gaps where developer "
130
+ "intent diverges from actual code behaviour. "
131
+ "Output valid JSON only — no prose outside the JSON block."
132
+ )
133
+
134
+ user = f"""Analyse the source code below and output a trust state machine graph.
135
+
136
+ Identify:
137
+ 1. Where data enters the system (UNTRUSTED)
138
+ 2. Validation / sanitisation steps (TRANSITION)
139
+ 3. Where data is treated as trusted (TRUSTED)
140
+ 4. ASSUMPTION GAPS — points where the code assumes safety without sufficient proof
141
+
142
+ Return ONLY this JSON structure:
143
+ {{
144
+ "language": "detected language",
145
+ "trust_states": [
146
+ {{"id": "s1", "name": "...", "type": "UNTRUSTED|TRANSITION|TRUSTED",
147
+ "files": ["file.py"], "description": "..."}}
148
+ ],
149
+ "transitions": [
150
+ {{"from": "s1", "to": "s2", "condition": "...", "file": "file.py", "line_hint": "..."}}
151
+ ],
152
+ "assumption_gaps": [
153
+ {{
154
+ "id": "gap_001",
155
+ "severity_hypothesis": "P1|P2|P3",
156
+ "file": "relative/path.py",
157
+ "line_hint": "function name or ~line number",
158
+ "description": "What the developer assumed vs what can actually reach this point",
159
+ "untrusted_input": "What untrusted data reaches here",
160
+ "bypassed_check": "What validation is missing or insufficient",
161
+ "potential_impact": "Theoretical worst-case consequence",
162
+ "confidence": "HIGH|MEDIUM|LOW",
163
+ "requires_human_verification": true
164
+ }}
165
+ ]
166
+ }}
167
+
168
+ SOURCE CODE:
169
+ {combined}"""
170
+
171
+ try:
172
+ raw = _hermes(system, user)
173
+ result = _extract_json(raw)
174
+ if result:
175
+ return result
176
+ except Exception as e:
177
+ return {"error": str(e), "assumption_gaps": []}
178
+
179
+ return {"assumption_gaps": []}
180
+
181
+
182
+ def run_semantic_extraction(repo_dir: str, language: str = "python") -> dict:
183
+ """
184
+ Main entry point for the semantic analysis pipeline.
185
+ Pure static analysis — no code is executed.
186
+
187
+ Returns a dict containing:
188
+ - trust_states
189
+ - transitions
190
+ - assumption_gaps (each tagged requires_human_verification=True)
191
+ - analyzed_files
192
+ - status: always "PENDING_HUMAN_REVIEW"
193
+ """
194
+ relevant_files = _find_relevant_files(repo_dir, language)
195
+ if not relevant_files:
196
+ return {
197
+ "error": "No source files found for the detected language.",
198
+ "assumption_gaps": [],
199
+ "status": "PENDING_HUMAN_REVIEW",
200
+ }
201
+
202
+ result = extract_trust_boundaries(repo_dir, relevant_files)
203
+ result["analyzed_files"] = relevant_files
204
+ result["repo_dir"] = repo_dir
205
+ result["language"] = language
206
+ result["extracted_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
207
+ result["status"] = "PENDING_HUMAN_REVIEW"
208
+
209
+ for gap in result.get("assumption_gaps", []):
210
+ gap["requires_human_verification"] = True
211
+
212
+ return result