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