File size: 11,411 Bytes
9452af2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | """
Rhodawk AI β Responsible Disclosure Vault
==========================================
Manages the complete responsible disclosure lifecycle with a mandatory
human approval gate at every stage.
DISCLOSURE POLICY (non-negotiable):
1. ALL findings start as DRAFT β nothing is shared externally
2. Human operator must read the full dossier and click Approve
3. After approval, the system generates a disclosure message β
the operator sends it via the maintainer's own security channel
4. Standard 90-day responsible disclosure timeline is tracked
5. Bug bounty submissions are prepared for human submission β
never automated
6. No GitHub API writes in AVR mode
"""
from __future__ import annotations
import hashlib
import json
import os
import sqlite3
import time
from pathlib import Path
from typing import Optional
VAULT_DB = os.getenv("RHODAWK_VAULT_DB", "/data/disclosure_vault.sqlite")
VAULT_DIR = os.getenv("RHODAWK_VAULT_DIR", "/data/vault")
DISCLOSURE_DAYS = int(os.getenv("RHODAWK_DISCLOSURE_DAYS", "90"))
def _init_db() -> None:
os.makedirs(os.path.dirname(VAULT_DB), exist_ok=True)
os.makedirs(VAULT_DIR, exist_ok=True)
conn = sqlite3.connect(VAULT_DB)
conn.executescript("""
CREATE TABLE IF NOT EXISTS disclosures (
id TEXT PRIMARY KEY,
repo TEXT NOT NULL,
severity TEXT NOT NULL,
title TEXT NOT NULL,
status TEXT DEFAULT 'DRAFT',
created_at REAL,
human_approved INTEGER DEFAULT 0,
approved_by TEXT,
approved_at REAL,
disclosed_at REAL,
deadline_at REAL,
dossier_path TEXT,
bug_bounty_program TEXT,
maintainer_contact TEXT
);
""")
conn.commit()
conn.close()
def compile_dossier(
repo: str,
semantic_graph: dict,
assumption_gap: dict,
harness_result: dict,
chain_analysis: Optional[list] = None,
bug_bounty_program: str = "",
maintainer_contact: str = "",
) -> str:
"""
Compile a structured responsible disclosure dossier.
Stored locally β NOT sent anywhere until a human operator approves.
Returns the disclosure ID.
"""
_init_db()
disclosure_id = hashlib.sha256(
f"{repo}:{assumption_gap.get('id','')}:{time.time()}".encode()
).hexdigest()[:16]
deadline_ts = time.time() + (DISCLOSURE_DAYS * 86400)
severity = assumption_gap.get("severity_hypothesis", "P3")
gap_desc = assumption_gap.get("description", "N/A")[:120]
triggered_str = str(harness_result.get("triggered", "Not tested"))
poc_output = harness_result.get("stdout", "N/A")[:1500]
chain_block = (
json.dumps(chain_analysis, indent=2)
if chain_analysis
else "No chains identified."
)
trust_states = json.dumps(semantic_graph.get("trust_states", []), indent=2)[:2000]
transitions = json.dumps(semantic_graph.get("transitions", []), indent=2)[:1000]
dossier = f"""# Responsible Disclosure Report β {disclosure_id}
> **STATUS: DRAFT β PENDING HUMAN OPERATOR REVIEW**
> This report has NOT been sent to any maintainer or bug bounty programme.
> No live system has been tested or attacked.
---
| Field | Value |
|---|---|
| **Disclosure ID** | `{disclosure_id}` |
| **Repository** | `{repo}` |
| **Severity Hypothesis** | **{severity}** |
| **Disclosure Deadline** | {time.strftime('%Y-%m-%d', time.localtime(deadline_ts))} ({DISCLOSURE_DAYS}-day standard) |
| **Bug Bounty Programme** | {bug_bounty_program or "Not specified"} |
| **Maintainer Contact** | {maintainer_contact or "See SECURITY.md"} |
| **Created** | {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())} |
---
## β οΈ Operator Action Required
Before this disclosure proceeds, you must:
- [ ] Read and understand the full dossier
- [ ] Independently verify the assumption gap description is accurate
- [ ] Confirm the PoC result matches what is claimed
- [ ] Verify the target repo has a responsible disclosure policy or bug bounty programme
- [ ] Click **Approve** in the Rhodawk Security Research dashboard
---
## 1. Executive Summary
**Finding:** {assumption_gap.get('description', 'N/A')}
**File:** `{assumption_gap.get('file', 'N/A')}`
**Location:** `{assumption_gap.get('line_hint', 'N/A')}`
**Confidence:** {assumption_gap.get('confidence', 'UNKNOWN')}
---
## 2. State Machine Analysis
**Untrusted Input Path:**
{assumption_gap.get('untrusted_input', 'N/A')}
**Bypassed or Insufficient Check:**
{assumption_gap.get('bypassed_check', 'N/A')}
**Theoretical Impact:**
{assumption_gap.get('potential_impact', 'N/A')}
### Trust States Identified
```json
{trust_states}
```
### State Transitions
```json
{transitions}
```
---
## 3. Proof of Concept (Local Sandbox Only)
> All PoC testing was performed against a locally cloned copy of the repository.
> No live/production system was accessed.
**Gap Triggered in Sandbox:** {triggered_str}
**Exit Code:** {harness_result.get('exit_code', 'N/A')}
**Timed Out:** {harness_result.get('timed_out', False)}
**Sandbox Output:**
```
{poc_output}
```
---
## 4. Vulnerability Chain Analysis (Theoretical)
{chain_block}
> All chain proposals above are theoretical and require independent human verification.
---
## 5. Responsible Disclosure Next Steps
1. **Operator** reviews and verifies this dossier
2. **Operator** approves disclosure via Rhodawk dashboard
3. **Operator** contacts maintainer via their `SECURITY.md` / `security@` policy
4. Submit to bug bounty programme if applicable: `{bug_bounty_program or 'N/A'}`
5. Allow **{DISCLOSURE_DAYS} days** for maintainer to produce a fix
6. Coordinate public disclosure date with maintainer
---
*Generated by Rhodawk AI Ethical Security Research Platform*
*All findings require human verification and explicit approval before disclosure*
*No automated exploitation of live systems is performed*
"""
dossier_path = os.path.join(VAULT_DIR, f"{disclosure_id}.md")
Path(dossier_path).write_text(dossier, encoding="utf-8")
conn = sqlite3.connect(VAULT_DB)
conn.execute(
"""INSERT INTO disclosures
(id, repo, severity, title, status, created_at, deadline_at,
dossier_path, bug_bounty_program, maintainer_contact)
VALUES (?,?,?,?, 'DRAFT',?,?, ?,?,?)""",
(
disclosure_id, repo, severity,
f"{severity} β {gap_desc}",
time.time(), deadline_ts,
dossier_path, bug_bounty_program, maintainer_contact,
),
)
conn.commit()
conn.close()
return disclosure_id
def get_pending_disclosures() -> list[dict]:
_init_db()
conn = sqlite3.connect(VAULT_DB)
rows = conn.execute(
"""SELECT id, repo, severity, title, status, created_at, deadline_at, bug_bounty_program
FROM disclosures WHERE status IN ('DRAFT','HUMAN_APPROVED')
ORDER BY created_at DESC"""
).fetchall()
conn.close()
now = time.time()
return [
{
"id": r[0], "repo": r[1], "severity": r[2], "title": r[3],
"status": r[4], "created_at": r[5],
"days_remaining": max(0, int((r[6] - now) / 86400)) if r[6] else DISCLOSURE_DAYS,
"bug_bounty_program": r[7] or "N/A",
}
for r in rows
]
def get_all_disclosures() -> list[dict]:
_init_db()
conn = sqlite3.connect(VAULT_DB)
rows = conn.execute(
"SELECT id, repo, severity, title, status, created_at, deadline_at, bug_bounty_program "
"FROM disclosures ORDER BY created_at DESC"
).fetchall()
conn.close()
now = time.time()
return [
{
"id": r[0], "repo": r[1], "severity": r[2], "title": r[3],
"status": r[4], "created_at": r[5],
"days_remaining": max(0, int((r[6] - now) / 86400)) if r[6] else DISCLOSURE_DAYS,
"bug_bounty_program": r[7] or "N/A",
}
for r in rows
]
def read_dossier(disclosure_id: str) -> str:
_init_db()
conn = sqlite3.connect(VAULT_DB)
row = conn.execute(
"SELECT dossier_path FROM disclosures WHERE id=?", (disclosure_id,)
).fetchone()
conn.close()
if not row or not row[0]:
return f"Dossier not found for ID: {disclosure_id}"
try:
return Path(row[0]).read_text(encoding="utf-8")
except Exception as e:
return f"Error reading dossier: {e}"
def approve_disclosure(disclosure_id: str, approved_by: str) -> bool:
"""Human operator explicitly approves a finding for disclosure."""
_init_db()
conn = sqlite3.connect(VAULT_DB)
conn.execute(
"UPDATE disclosures SET status='HUMAN_APPROVED', human_approved=1, "
"approved_by=?, approved_at=? WHERE id=?",
(approved_by, time.time(), disclosure_id),
)
conn.commit()
conn.close()
return True
def reject_disclosure(disclosure_id: str, reason: str = "") -> bool:
"""Human operator rejects / archives a finding."""
_init_db()
conn = sqlite3.connect(VAULT_DB)
conn.execute(
"UPDATE disclosures SET status='REJECTED' WHERE id=?",
(disclosure_id,),
)
conn.commit()
conn.close()
return True
def prepare_disclosure_message(disclosure_id: str) -> str:
"""
After human approval, generate the message for the operator to send
to the maintainer via THEIR preferred channel (SECURITY.md / email / HackerOne).
The operator sends this manually β it is never automated.
"""
_init_db()
conn = sqlite3.connect(VAULT_DB)
row = conn.execute(
"SELECT repo, severity, title, bug_bounty_program, human_approved, approved_by "
"FROM disclosures WHERE id=?",
(disclosure_id,),
).fetchone()
conn.close()
if not row:
return "Disclosure not found."
if not row[4]:
return "ERROR: Human approval is required before generating a disclosure message."
repo, severity, title, bounty, _, approved_by = row
msg = f"""Subject: Responsible Disclosure β {severity} Finding in {repo}
Hello {repo.split('/')[0]} security team,
I am reaching out as part of responsible security research conducted through the Rhodawk AI ethical research platform.
We have identified a potential {severity}-severity security finding in `{repo}`.
**Finding:** {title}
**Disclosure ID:** `{disclosure_id}`
**Disclosure Deadline:** {DISCLOSURE_DAYS} days from today (industry standard)
**Bug Bounty Programme:** {bounty or "N/A"}
We have prepared a full technical dossier including:
- State machine analysis
- Proof-of-concept (local sandbox only β no live systems tested)
- Theoretical vulnerability chain analysis
We would like to coordinate disclosure privately before any public disclosure.
Please let us know your preferred communication channel and we will share the full dossier.
This report was reviewed and approved for disclosure by a human researcher before being sent.
Respectfully,
Rhodawk AI Security Research Team
"""
conn = sqlite3.connect(VAULT_DB)
conn.execute(
"UPDATE disclosures SET status='DISCLOSED', disclosed_at=? WHERE id=?",
(time.time(), disclosure_id),
)
conn.commit()
conn.close()
return msg
|