mirror of
https://github.com/Strategic-Automation/violin.git
synced 2026-08-14 12:33:37 +02:00
docs(release): align workflow instructions, hypothesis discipline, and release v3.0.1 (#64)
Merge dev into master
This commit is contained in:
+16
-9
@@ -36,14 +36,18 @@ Violin **must not** touch a target (no curl, nmap, browser, web_search for the t
|
||||
| 4 | `$ENG_DIR/hypotheses.md` (hypothesis board) | `init-engagement` | `check-bootstrap` |
|
||||
| 5 | `$ENG_DIR/state/history.md` (command log) | `init-engagement` | `check-bootstrap` |
|
||||
|
||||
**Session-start/skill-load precondition** (code-enforced via `check-command`):
|
||||
**Session-start/skill-delivery precondition** (code-enforced via the plugin hook):
|
||||
|
||||
Launch with `hermes chat --skills pentest` when possible. If the profile was already started, load `pentest` before creating the marker below. Check the current state at any time with `python $HOME/.hermes/profiles/violin/scripts/violin_guard.py status --eng-dir "$ENG_DIR" --section skill`.
|
||||
Launch with `hermes chat --skills pentest` when possible. For every active PTT
|
||||
task, call `violin_record_ptt` with its routed `skill`, `technique`, and any
|
||||
required hypothesis. The first call prepares real `skill_view` content without
|
||||
changing the PTT; after that result returns, repeat the same call in the next
|
||||
model continuation to bind the receipt. Check the current state with
|
||||
`violin_status` or `python $HOME/.hermes/profiles/violin/scripts/violin_guard.py
|
||||
status --eng-dir "$ENG_DIR" --section skill`.
|
||||
|
||||
```text
|
||||
printf 'skill-loaded: %s\n' "$(date -Iseconds)" > "$ENG_DIR/state/.skill-loaded-<session-id>"
|
||||
python $HOME/.hermes/profiles/violin/scripts/violin_guard.py status --eng-dir "$ENG_DIR" --section skill
|
||||
```
|
||||
Never create `.skill-loaded-*` files. Legacy markers are migration hints only
|
||||
and are not evidence that Hermes delivered a skill.
|
||||
|
||||
Then pass the same session ID into every target-touching command check — `--eng-dir` is mandatory because it activates the PTT, history, hypothesis, and synchronization guards:
|
||||
|
||||
@@ -51,7 +55,10 @@ Then pass the same session ID into every target-touching command check — `--en
|
||||
python $HOME/.hermes/profiles/violin/scripts/violin_guard.py check-command --scope $ENG_DIR/scope/scope.yaml --eng-dir "$ENG_DIR" --target "<primary-host-or-ip>" --session-id "<session-id>" --phase <PHASE> --command "<cmd>"
|
||||
```
|
||||
|
||||
**Run-to-completion rule:** once created, a skill-load marker holds for the current engagement work block. Do not recreate it between commands. After `/goal set` or context compression, continue in the same conversation, re-read `$ENG_DIR/state/`, and create or verify the marker for the current session ID before the next target action.
|
||||
**Run-to-completion rule:** after `/goal set` or context compression, continue
|
||||
in the same conversation, re-read `$ENG_DIR/state/`, and call `violin_status`.
|
||||
If the receipt binding needs recovery, follow its reported action; do not
|
||||
manufacture marker files or assume the prior skill delivery still applies.
|
||||
|
||||
**What this prevents:** the Nimbus-style failure where the agent skipped scoping/bootstrap, never created a PTT or hypothesis board, and burned the entire 20-turn budget calling `curl` variations on the same endpoint without tracking what it learned.
|
||||
|
||||
@@ -62,7 +69,7 @@ The Violin agent **may**:
|
||||
- **Run pentest tools** through `violin_exec`, `violin_exec_burst`, or the typed guard adapters. `violin_exec` has no binary allowlist: any installed non-interactive Kali/Parrot CLI tool may run against the explicit in-scope target while retaining scope, phase, PTT, hypothesis, history, evidence, timeout, and sync gates. Installation, root, hardware, service, GUI, and interactive-TTY requirements remain runtime constraints. Raw `terminal` is limited to host-local preparation and administration.
|
||||
- **Use `execute_code` only with an audit header** — its first line must be `# violin: {"eng_dir":"...","phase":"...","target":"...","session_id":"..."}`. Violin validates that metadata, saves the submitted source under `$ENG_DIR/evidence/<phase>/`, and appends its digest and completion status to history. This is auditability, not a substitute for typed target execution.
|
||||
- **Research online** — Use the `web` toolset (`web_search`, `web_extract`) to find CVEs, exploits, PoC code, security advisories, tool documentation, and tutorials.
|
||||
- **Browse target websites** — Use the `browser` toolset (`browser_navigate`, `browser_click`, `browser_type`, `browser_vision`, `browser_snapshot`) to navigate target web apps, interact with login forms and dashboards, capture screenshots for evidence, inspect DOM elements, crawl visible links, and enumerate client-side routes.
|
||||
- **Browse target websites carefully** — Use the `browser` toolset only for an explicitly approved in-scope URL after the PTT and skill-receipt gates pass. In v3.0.0 the hook gates browser activity on that workflow binding; it is not a network-level browser allowlist and does not contain redirects, subresources, forms, JavaScript navigation, history, or WebSockets. Stop and review the URL after each navigation or action; do not deliberately follow an out-of-scope redirect.
|
||||
- **Discover installed tools** — Check what tools exist on the workstation using `command -v` or filesystem searches.
|
||||
- **Read tool docs** — Use `read_file`, `terminal`, or `browser` to read help pages, man pages, READMEs, and documentation.
|
||||
- **Write scripts** — Create Python, Bash, PowerShell, or other scripts under `$ENG_DIR/exploits/` for exploitation, automation, or evidence processing.
|
||||
@@ -75,7 +82,7 @@ The Violin agent **may**:
|
||||
- **Summarise results** — After each logical tool batch, report: (a) what ran, (b) key results found or nothing notable, (c) evidence saved where. 3-5 lines max. Do not dump raw command output.
|
||||
- **Check in next-step** — After each sub-phase or completed batch, ask the user what to do next with concrete options. E.g. "DNS enumeration done. Found 3 subdomains. Next: tech detection with whatweb, or move to active scanning with nmap?" Do not advance the workflow silently.
|
||||
- **Ask via `clarify`** — Ask targeted questions during scoping and throughout the engagement whenever ambiguity arises.
|
||||
- **Delegate** — Use background `terminal` processes to run parallel reconnaissance or long-running tasks.
|
||||
- **Delegate** — Use `violin_exec` with `background=true` or `violin_listener` for tracked long-running work. Do not start target-facing background jobs through raw `terminal`.
|
||||
- **Use vision** — Capture and analyse screenshots with `browser_vision` or `vision_analyze` for evidence and context understanding.
|
||||
|
||||
## Conversation & Memory Isolation
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Strategic-Automation/violin — AI Developer Guidance
|
||||
|
||||
> Workspace-scoped developer guidance for AI coding agents (Antigravity, Hermes, Codex, Cursor, etc.) developing, testing, or maintaining the `violin` codebase.
|
||||
>
|
||||
> *Note:* For deployed end-user Hermes pentest installations, runtime identity and engagement rules are packaged in `SOUL.md`, `.hermes.md`, and `skills/pentest/SKILL.md`. This file governs AI agent developer behavior within this repository workspace.
|
||||
|
||||
---
|
||||
|
||||
## 1. Stack & Environment Setup
|
||||
|
||||
- **Python Version:** 3.11 (pinned in `.python-version` and `pyproject.toml` to match Hermes runtime).
|
||||
- **Package Manager:** `uv` (use `uv sync --dev` to sync development environment).
|
||||
- **Virtual Environment:** `.venv` created and managed via `uv`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Mandatory Verification Commands
|
||||
|
||||
Run these commands to verify any code changes before declaring completion:
|
||||
|
||||
```bash
|
||||
# 1. Run full test suite (must pass 100%)
|
||||
uv run pytest
|
||||
|
||||
# 2. Run linter check
|
||||
uv run ruff check .
|
||||
|
||||
# 3. Check code formatting (fix with `uv run ruff format .`)
|
||||
uv run ruff format --check .
|
||||
|
||||
# 4. Validate release gate
|
||||
uv run python -m plugins.violin_guard.release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Code Conventions & Architecture
|
||||
|
||||
- **Hermes Runtime Contract:** All target-touching CLI command execution in Violin engagements MUST go through `plugins.violin_guard` typed Hermes tool calls (`violin_exec`, `violin_record_ptt`, `violin_review_batch`, `violin_record_hypothesis`, `violin_target`, `violin_status`, `violin_listener`). Never invoke flat CLI scripts (`python violin_guard.py`) as a substitute for typed tool calls.
|
||||
- **Fail-Closed Validation:** All state parsers (`hypotheses.py`, `ptt.py`, `command.py`, `targets.py`) must validate inputs fail-closed before mutating filesystem state.
|
||||
- **Section Preservation:** File rewriters (`_rewrite_hypotheses`, `update_task`) must preserve structural template sections (`## Observations`, `## Decoy Trail`, `## Research Log`, `## Resolved Theories`, table columns).
|
||||
- **Typed Schemas:** Use Pydantic v2 `BaseModel` models in `plugins/violin_guard/schemas.py` for all tool parameter specifications.
|
||||
- **File Encoding:** Always specify `encoding="utf-8"` explicitly for all text file read/write operations.
|
||||
|
||||
---
|
||||
|
||||
## 4. Git & Branching Strategy
|
||||
|
||||
- **Feature/Docs Branches:** Use `codex/<topic>` or `dev`.
|
||||
- **Merge Flow:** `codex/<topic>` ──► `dev` ──► PR to `master`.
|
||||
- **Master Branch:** `master` is protected by GitHub repository rules (`GH013`); production releases require a Pull Request.
|
||||
|
||||
---
|
||||
|
||||
## 5. Hard Boundaries (What AI Agents Must Never Do)
|
||||
|
||||
1. **NEVER Bypass Target Execution Guards:** Never run target-touching commands directly in raw shell without `violin_exec` or `violin_exec_burst`.
|
||||
2. **NEVER Swallow Exceptions or Patch Tests Superficialy:** Fix underlying root causes; never mask errors, return dummy fallbacks, or comment out failing assertions.
|
||||
3. **NEVER Hardcode Target IPs:** Resolve target hosts dynamically via `violin_target` or `scope.yaml`.
|
||||
4. **NEVER Declare Success Without Empirical Verification:** Always run `uv run pytest` and `uv run ruff check .` to prove zero regressions before finishing work.
|
||||
@@ -31,7 +31,7 @@ hermes -p violin
|
||||
<tr><td width="280"><b>🔬 31 Methodology Playbooks</b></td><td>7 operational playbooks (five execution phases, optional post-exploitation, and the tools catalog) + 24 vulnerability-class playbooks, routed across the `pentest`, `web-attacks`, and `access-control` skills.</td></tr>
|
||||
<tr><td><b>🛡️ Multi-Layer Safety</b></td><td>Interactive scoping (9 questions) → scope validation → guard check → approval gates — every target-touching command validated before execution.</td></tr>
|
||||
<tr><td><b>🧠 Pentesting Task Tree</b></td><td>Structured artifact tracking every task via `[x]/[ ]/[~]` markers across phases, with executor-owned history, hypothesis linking, and guard-bound batch reviews.</td></tr>
|
||||
<tr><td><b>🌐 Browser + Web Research</b></td><td>Browser toolset for website enumeration. Web toolset for CVE lookup, exploit search, and OSINT.</td></tr>
|
||||
<tr><td><b>🌐 Browser + Web Research</b></td><td>Browser toolset for approved in-scope website enumeration; v3.0.0 gates the engagement workflow but does not provide a network-level browser allowlist. Web toolset for CVE lookup, exploit search, and OSINT.</td></tr>
|
||||
<tr><td><b>📋 Evidence-Driven Reporting</b></td><td>Reproducible evidence with screenshots, tool output, and request/response pairs. CVSS 3.1 + 4.0 crosswalks and optional remediation patches.</td></tr>
|
||||
<tr><td><b>🔗 Hermes-Native</b></td><td>Inherits your existing Hermes provider, model, and tool backends. Violin introduces no separate credential store or broker.</td></tr>
|
||||
</table>
|
||||
|
||||
@@ -52,6 +52,12 @@ _FIELD_NAMES = {
|
||||
"source evidence": "source_evidence",
|
||||
"runtime evidence": "runtime_evidence",
|
||||
"updated": "updated",
|
||||
"confidence": "confidence",
|
||||
"timebox": "timebox",
|
||||
"cheapest test": "cheapest_test",
|
||||
"kill criteria": "kill_criteria",
|
||||
"next step": "next_step",
|
||||
"linked findings": "linked_findings",
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +66,9 @@ class Hypothesis:
|
||||
id: str
|
||||
title: str
|
||||
status: str = "Candidate"
|
||||
confidence: str = ""
|
||||
timebox: str = ""
|
||||
cheapest_test: str = ""
|
||||
phase: str = ""
|
||||
service: str = ""
|
||||
port: str = ""
|
||||
@@ -72,7 +81,10 @@ class Hypothesis:
|
||||
test_command: str = ""
|
||||
test_response: str = ""
|
||||
verification_status: str = ""
|
||||
kill_criteria: str = ""
|
||||
rejection_reason: str = ""
|
||||
next_step: str = ""
|
||||
linked_findings: str = ""
|
||||
candidate_source: str = ""
|
||||
entry_point: str = ""
|
||||
data_flow: str = ""
|
||||
@@ -88,6 +100,9 @@ class Hypothesis:
|
||||
"id": self.id,
|
||||
"title": self.title,
|
||||
"status": self.canonical_status(),
|
||||
"confidence": self.confidence,
|
||||
"timebox": self.timebox,
|
||||
"cheapest_test": self.cheapest_test,
|
||||
"phase": self.phase,
|
||||
"service": self.service,
|
||||
"port": self.port,
|
||||
@@ -100,7 +115,10 @@ class Hypothesis:
|
||||
"test_command": self.test_command,
|
||||
"test_response": self.test_response,
|
||||
"verification_status": self.verification_status,
|
||||
"kill_criteria": self.kill_criteria,
|
||||
"rejection_reason": self.rejection_reason,
|
||||
"next_step": self.next_step,
|
||||
"linked_findings": self.linked_findings,
|
||||
"candidate_source": self.candidate_source,
|
||||
"entry_point": self.entry_point,
|
||||
"data_flow": self.data_flow,
|
||||
@@ -113,7 +131,12 @@ class Hypothesis:
|
||||
now = datetime.now(UTC).strftime("%Y-%m-%d %H:%M")
|
||||
lines = [f"### H-{self.id}: {self.title}"]
|
||||
lines.append(f"- **Status:** {self.canonical_status()}")
|
||||
lines.append(f"- **Updated:** {self.updated or now}")
|
||||
if self.confidence:
|
||||
lines.append(f"- **Confidence:** {self.confidence}")
|
||||
if self.timebox:
|
||||
lines.append(f"- **Timebox:** {self.timebox}")
|
||||
if self.cheapest_test:
|
||||
lines.append(f"- **Cheapest test:** {self.cheapest_test}")
|
||||
if self.phase:
|
||||
lines.append(f"- **Phase:** {self.phase}")
|
||||
if self.service:
|
||||
@@ -138,8 +161,14 @@ class Hypothesis:
|
||||
lines.append(f"- **Test Response:** {self.test_response}")
|
||||
if self.verification_status:
|
||||
lines.append(f"- **Verification Status:** {self.verification_status}")
|
||||
if self.kill_criteria:
|
||||
lines.append(f"- **Kill criteria:** {self.kill_criteria}")
|
||||
if self.rejection_reason:
|
||||
lines.append(f"- **Rejection Reason:** {self.rejection_reason}")
|
||||
if self.next_step:
|
||||
lines.append(f"- **Next step:** {self.next_step}")
|
||||
if self.linked_findings:
|
||||
lines.append(f"- **Linked findings:** {self.linked_findings}")
|
||||
for label, value in (
|
||||
("Candidate Source", self.candidate_source),
|
||||
("Entry Point", self.entry_point),
|
||||
@@ -149,7 +178,7 @@ class Hypothesis:
|
||||
):
|
||||
if value:
|
||||
lines.append(f"- **{label}:** {value}")
|
||||
lines.append(f"- **Updated:** {self.updated or now} UTC")
|
||||
lines.append(f"- **Updated:** {self.updated or now}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
@@ -322,6 +351,9 @@ def update_hypothesis(
|
||||
id=merged_fields["id"],
|
||||
title=merged_fields.get("title", "") or f"Hypothesis {merged_fields['id']}",
|
||||
status=(merged_fields.get("status") or "Candidate"),
|
||||
confidence=(merged_fields.get("confidence") or "").strip(),
|
||||
timebox=(merged_fields.get("timebox") or "").strip(),
|
||||
cheapest_test=(merged_fields.get("cheapest_test") or "").strip(),
|
||||
phase=(merged_fields.get("phase") or "").strip(),
|
||||
service=(merged_fields.get("service") or "").strip(),
|
||||
port=(merged_fields.get("port") or "").strip(),
|
||||
@@ -334,7 +366,10 @@ def update_hypothesis(
|
||||
test_command=(merged_fields.get("test_command") or "").strip(),
|
||||
test_response=(merged_fields.get("test_response") or "").strip(),
|
||||
verification_status=(merged_fields.get("verification_status") or "").strip(),
|
||||
kill_criteria=(merged_fields.get("kill_criteria") or "").strip(),
|
||||
rejection_reason=(merged_fields.get("rejection_reason") or "").strip(),
|
||||
next_step=(merged_fields.get("next_step") or "").strip(),
|
||||
linked_findings=(merged_fields.get("linked_findings") or "").strip(),
|
||||
candidate_source=(merged_fields.get("candidate_source") or "").strip(),
|
||||
entry_point=(merged_fields.get("entry_point") or "").strip(),
|
||||
data_flow=(merged_fields.get("data_flow") or "").strip(),
|
||||
@@ -378,23 +413,41 @@ def update_hypothesis(
|
||||
|
||||
|
||||
def _rewrite_hypotheses(path: Path, hypotheses_list: list[Hypothesis]) -> None:
|
||||
"""Rewrite the entire hypotheses file."""
|
||||
"""Rewrite the hypotheses file while preserving structural sections (Decoy Trail, Observations, etc.)."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
template = path.read_text(encoding="utf-8") if path.exists() else "# Hypothesis Board\n\n"
|
||||
# Template instructions are an HTML comment containing an example H-001
|
||||
# heading. Remove that comment before locating real records, otherwise a
|
||||
# newly written hypothesis is accidentally placed inside the comment.
|
||||
|
||||
# Remove template instruction HTML comment if present
|
||||
import re
|
||||
|
||||
comment_start = template.find("<!--")
|
||||
if comment_start != -1:
|
||||
comment_end = template.find("-->", comment_start)
|
||||
if comment_end != -1:
|
||||
template = template[:comment_start] + template[comment_end + 3 :]
|
||||
# Keep any header content before first hypothesis
|
||||
header_end = template.find("### H-")
|
||||
if header_end == -1:
|
||||
header = template.strip() + "\n\n"
|
||||
|
||||
# Preserve section structure (e.g. ## Active Theories ... ## Observations ... ## Decoy Trail)
|
||||
active_heading = "## Active Theories"
|
||||
active_pos = template.find(active_heading)
|
||||
|
||||
if active_pos != -1:
|
||||
header = template[: active_pos + len(active_heading)].strip() + "\n\n"
|
||||
# Find next section header after Active Theories
|
||||
next_sec = re.search(r"\n##\s+(?!Active Theories)", template[active_pos:])
|
||||
trailer = template[active_pos + next_sec.start() + 1 :].lstrip() if next_sec else ""
|
||||
else:
|
||||
header = template[:header_end].rstrip() + "\n\n"
|
||||
# Fallback: locate first hypothesis heading starting with ### H-
|
||||
first_h = re.search(r"^###\s+H-", template, re.MULTILINE)
|
||||
if first_h:
|
||||
header = template[: first_h.start()].rstrip() + "\n\n"
|
||||
next_sec = re.search(r"\n##\s+", template[first_h.start() :])
|
||||
trailer = (
|
||||
template[first_h.start() + next_sec.start() + 1 :].lstrip() if next_sec else ""
|
||||
)
|
||||
else:
|
||||
header = template.strip() + "\n\n"
|
||||
trailer = ""
|
||||
|
||||
body = "\n".join(h.to_markdown() for h in hypotheses_list)
|
||||
path.write_text(header + body, encoding="utf-8")
|
||||
content = header + body + ("\n\n" + trailer if trailer else "\n")
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
@@ -58,6 +58,11 @@ class RecordHypothesisArgsModel(BaseModel):
|
||||
id: str = ""
|
||||
title: str = ""
|
||||
status: str = ""
|
||||
confidence: str = Field("", description="0.1-1.0 guesstimate; escalate only with evidence")
|
||||
timebox: str = Field("", description="e.g. 4 tool batches or 30 min — then re-evaluate")
|
||||
cheapest_test: str = Field(
|
||||
"", description="Single cheapest probe that discriminates this theory"
|
||||
)
|
||||
phase: str = ""
|
||||
target: str = Field("", description="target host/IP (must be in scope)")
|
||||
vuln_class: str = ""
|
||||
@@ -80,9 +85,15 @@ class RecordHypothesisArgsModel(BaseModel):
|
||||
test_command: str = Field("", description="Exact syntax tested, including argument order")
|
||||
test_response: str = Field("", description="Exact decisive response or error")
|
||||
verification_status: str = ""
|
||||
kill_criteria: str = Field(
|
||||
"",
|
||||
description="Evidence that contradicts, or no new info in N batches — then kill & log in Decoy Trail",
|
||||
)
|
||||
rejection_reason: str = Field(
|
||||
"", description="Why a rejected hypothesis is safe to stop pursuing"
|
||||
)
|
||||
next_step: str = ""
|
||||
linked_findings: str = ""
|
||||
candidate_source: str = ""
|
||||
entry_point: str = ""
|
||||
data_flow: str = ""
|
||||
|
||||
+21
-6
@@ -55,7 +55,7 @@ The agent operates as the **Pentest Lead** with supervised autonomy only after s
|
||||
**Available capabilities:**
|
||||
- **`terminal`** — host-local preparation and administration only; best-effort policy blocks obvious target traffic, while target-touching commands use the Violin guard tools below
|
||||
- **`web`** (`web_search`, `web_extract`) — research: CVE lookup, exploit search, OSINT, documentation, PoC search
|
||||
- **`browser`** (`browser_navigate`, `browser_click`, `browser_type`, `browser_vision`, `browser_snapshot`) — website enumeration and interaction: navigate target web apps, interact with login forms and dashboards, capture screenshots, inspect DOM, crawl visible links, enumerate client-side routes
|
||||
- **`browser`** (`browser_navigate`, `browser_click`, `browser_type`, `browser_vision`, `browser_snapshot`) — website enumeration and interaction for explicitly approved in-scope URLs. The v3.0.0 hook checks the PTT/skill-receipt binding before supported browser actions, but it is not a network-level scope firewall: redirects, subresources, forms, JavaScript navigation, history, and WebSockets are not contained by the guard. Re-check the visible URL after each action and stop for review if it leaves scope.
|
||||
- **`file`** (`write_file`, `read_file`, `search_files`) — evidence collection, report writing, config manipulation
|
||||
- **`code_execution`** — host-local Python automation only with a first-line audit header: `# violin: {"eng_dir":"...","phase":"...","target":"...","session_id":"..."}`. The source and completion record are saved to the named engagement; use typed Violin tools for target execution.
|
||||
- **`clarify`** — ask the user structured questions during scoping and when decisions are needed
|
||||
@@ -140,10 +140,16 @@ The phase workflow is mandatory for the entire session, including long, compress
|
||||
1. Check/update `todo` with a single active `phase-gate` item named for the current phase.
|
||||
2. Confirm an approved `$ENG_DIR/scope/scope.yaml` exists before touching any target. If it does not, remain in SCOPING and ask via `clarify`. Verify with `python $HOME/.hermes/profiles/violin/scripts/violin_guard.py validate-scope --scope $ENG_DIR/scope/scope.yaml` (exit 0 required).
|
||||
For an authorized HTB/CTF lab, `init-engagement --ctf --host <ip> --session-id <id> "$ENG_DIR"` creates a ready-to-test scope and active RECON PTT row; prepare and bind the routed skill before target activity. This exact guard CLI action is host-local and accepts a direct `--host` value through `terminal`; never hide it in a file, environment variable, or shell substitution.
|
||||
3. **Read and activate the PTT task** — `read_file path="$ENG_DIR/state/ptt.md"` — and select the next open `[ ]` task for the current phase. Before any target command, mark exactly one task `[~]`:
|
||||
```bash
|
||||
python $HOME/.hermes/profiles/violin/scripts/violin_guard.py record-ptt --eng-dir "$ENG_DIR" --id PT-XXX --status "[~]" --note "starting task" --skill pentest --technique "<technique>" [--hypothesis-id H-001]
|
||||
3. **Read and activate the PTT task** — `read_file path="$ENG_DIR/state/ptt.md"` — and select the next open `[ ]` task for the current phase. Before any target command, mark exactly one task `[~]` through the Hermes tool:
|
||||
```text
|
||||
violin_record_ptt(
|
||||
eng_dir="$ENG_DIR", id="PT-XXX", status="[~]", note="starting task",
|
||||
skill="pentest", technique="<technique>", hypothesis_id="H-001" # when required
|
||||
)
|
||||
```
|
||||
The first call prepares the routed skill receipt; repeat it after that tool
|
||||
result returns to bind the transition. The standalone CLI is diagnostic and
|
||||
administrative; it cannot substitute for Hermes `skill_view` delivery.
|
||||
With no pending batch, this is the one permitted PTT start transition. The guard hard-blocks target execution when there is no unambiguous active task or the task sits under another phase heading. The executor never changes this row or its `*Last updated*` timestamp. After each bounded batch, review the results and call `violin_review_batch` with `[~]`, `[x]`, `[!]`, or `[-]` plus a truthful result summary.
|
||||
4. **Command history is executor-owned** — `violin_exec` appends every completed target command automatically. There is no public history-writing command; inspect recent history before a new batch to avoid repeats.
|
||||
5. Re-read this skill or the active playbook after context compression, `/resume`, or any uncertainty about the workflow. **Also read back evidence and hypotheses:** `read_file path="$ENG_DIR/hypotheses.md"` and `search_files path="$ENG_DIR/evidence" pattern="<target>" target="files"` to restore investigation state.
|
||||
@@ -170,6 +176,7 @@ The phase workflow is mandatory for the entire session, including long, compress
|
||||
- VULN RESEARCH → `playbooks/vuln-research.md`
|
||||
- EXPLOITATION → `playbooks/exploitation.md` plus relevant vuln-class playbook
|
||||
- POST EXPLOITATION → `playbooks/post-exploitation.md`
|
||||
- FLAGS (authorised training/lab objective capture) → `playbooks/post-exploitation.md` + `references/flags-mode.md` (`templates/flag-capture-register.md`)
|
||||
- REPORTING → `playbooks/reporting.md`
|
||||
- RETROSPECTIVE → `references/retrospective.md`
|
||||
8. Before exploit validation, re-check the vuln playbook's `## Stop Conditions` and `## Blocked Actions`.
|
||||
@@ -209,7 +216,7 @@ IF 5 consecutive attempts fail on the same target component:
|
||||
- **When stuck, research online before re-running.** The cheapest unstuck move is almost always new information, not another scan. If a command stalls, errors, or yields nothing new: `web_search` the exact error string + tool name, pull the upstream docs / PoC / CVE advisory via `web_extract`, and only then change the variable (different host, param, wordlist, technique) or move to a new task. Treat the `web` capability as a primary recovery lever, not a last resort.
|
||||
- **Five-attempt research trigger.** After five failed attempts against the same feature or exploit class, stop. Re-read captured source first; then research the exact parser, protocol, or primitive; then test one documented variant. If the source is unavailable or the next safe variant is unclear, ask the user for a hint rather than continuing a circular loop.
|
||||
- **False-negative checkpoint.** Before declaring a feature unavailable, stubbed, or not implemented, match the exact test command and argument order against the captured source/parser. Record the command, decisive response, and `verification_status` in the hypothesis. `syntax_uncertain` is not a rejection: it requires a corrected re-test.
|
||||
- **Context boundary.** Prefix controller output with `[VICTIM]`; prefix assessment-host commands and notes with `[ATTACKER]`. `/proc`, `/run`, UNIX sockets, and local service state belong to the machine on which the command runs. Host-local preparation (for example, starting an approved HTTP server or hashing a local artifact) may use the terminal directly; commands sent to a target still use `violin_exec` or one pre-approved `violin_exec_burst`.
|
||||
- **Context boundary.** Prefix controller output with `[VICTIM]`; prefix assessment-host commands and notes with `[ATTACKER]`. `/proc`, `/run`, UNIX sockets, and local service state belong to the machine on which the command runs. Hashing and other non-networking preparation may use the terminal directly. For a long-lived local service, use tracked `violin_exec` with `background=true` and then `violin_exec_status`/`violin_exec_cancel`; no typed HTTP-server readiness tool ships in v3.0.0.
|
||||
- **Mandatory research-loop (VULN RESEARCH / EXPLOITATION):** for each detected version/service, record the actual NVD/ExploitDB/GitHub research and update the hypothesis when its semantic state changes (Candidate/Likely/Validated/Rejected). Do not fabricate a hypothesis update merely because another payload ran.
|
||||
- **Research-attempt gate:** before any EXPLOITATION, POST_EXPLOITATION, PRIVESC, or FLAGS target command, the matching hypothesis must contain non-empty `CVE Research` and `Exploit Research` fields. Each field records the online query, source, and outcome. A truthful `no results`, `not applicable`, or `source unavailable` outcome satisfies the attempt requirement; an omitted field blocks execution. Local SearchSploit alone does not satisfy the online attempt.
|
||||
- **Split continuity contract.** Every approved target command is automatically mirrored to `state/history.md`; PTT progress is never automatic. When the bounded window ends and `violin_exec` returns `sync_required`, stop, review the batch evidence, call `violin_review_batch`, then continue.
|
||||
@@ -337,6 +344,14 @@ separate from confirmed findings, in a hypothesis board.
|
||||
|
||||
The board has five sections: Active Theories, Observations, Investigation Chains, Research Log, Resolved Theories.
|
||||
|
||||
**Per-hypothesis discipline:** every active hypothesis carries a **confidence**,
|
||||
**timebox**, **cheapest discriminating test**, and explicit **kill criteria**
|
||||
(see `templates/hypothesis-board.md`). Run the cheapest discriminating test
|
||||
first; kill the path when evidence contradicts it OR it stops producing new
|
||||
information, and log it in the board's **Decoy Trail** so a disproven technique
|
||||
is never re-entered. Verification and corroboration rules (independent re-check,
|
||||
≥2 evidence angles, blocker taxonomy) live in `references/evidence-and-verification-discipline.md`.
|
||||
|
||||
### Evidence Read-Back
|
||||
|
||||
The drift guard (SOUL.md, .hermes.md, §2) requires re-reading evidence and
|
||||
@@ -378,6 +393,6 @@ and CVSS scoring rules.
|
||||
## 12. Operator Notes & Supported Workflow Patterns
|
||||
|
||||
* **Reverse-Shell Catch Listeners (`violin_listener`)**: Reverse-shell catch listeners bind host-local interfaces (e.g. `0.0.0.0`, `127.0.0.1`, `localhost`, or the attacker's assigned VPN IP e.g. `10.10.14.x`). `violin_listener` explicitly allows host-local and VPN IP bindings, while rejecting binding attempts targeting victim/target addresses.
|
||||
* **Persistent Background Tunnels (`ssh -f -N`, background listeners)**: Long-lived commands or SSH tunnels marked with `background=true` remain running after dispatch. `violin_review_batch` detects active background processes in `$ENG_DIR/state/executions/*.json` and treats them as acknowledged completed actions rather than blocking batch review with "pending command not yet in exact history".
|
||||
* **Persistent Background Tunnels (`ssh -f -N`, background listeners)**: Long-lived commands or SSH tunnels marked with `background=true` remain running after dispatch. `violin_review_batch` detects active background processes in `$ENG_DIR/evidence/executions/*.json` and treats them as acknowledged completed actions rather than blocking batch review with "pending command not yet in exact history".
|
||||
* **Clearing Stuck Active PTT Tasks**: If a target environment or box is reset, starting a new task via `violin_record_ptt` will automatically supersede and close an old active `[~]` task (updating its status to `[x]` with a `[superseded-by:task-id]` note) as long as no pending unreviewed command batch exists.
|
||||
* **Semantic Anti-Stuck Lock Auto-Release**: Recording a hypothesis (`violin_record_hypothesis`) or submitting a batch review containing completed execution evidence/technique pivot automatically clears progress locks, preventing false anti-stuck blocks during rapid CTF iteration.
|
||||
|
||||
@@ -202,7 +202,11 @@ wafw00f https://<target> -o $ENG_DIR/evidence/recon/tech/waf-detect.txt
|
||||
|
||||
> Use the `browser` toolset (`browser_navigate`, `browser_click`, `browser_type`,
|
||||
> `browser_vision`, `browser_snapshot`) to interact with the target website
|
||||
> directly. This is essential for website enumeration and interaction.
|
||||
> directly. This is essential for website enumeration and interaction. In
|
||||
> v3.0.0, browser activity is gated on the active PTT/skill receipt but is not
|
||||
> network-level scope enforcement: only navigate to an explicitly approved
|
||||
> in-scope URL, inspect the visible URL after every action, and stop for review
|
||||
> if a redirect or client-side navigation leaves scope.
|
||||
|
||||
```bash
|
||||
mkdir -p "$ENG_DIR/evidence/recon/browser"
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Evidence & Verification Discipline
|
||||
|
||||
Cross-cutting rules for turning observations into **Validated** findings and for
|
||||
knowing when to stop. Applies across every phase and playbook. Complements the
|
||||
receipt schema in `templates/verification-receipt.yaml` and the hypothesis
|
||||
board in `templates/hypothesis-board.md`.
|
||||
|
||||
## 1. Verify by independent re-check, never an in-session "success"
|
||||
|
||||
A tool returning success, a UI accepting your value, or a command exiting 0 is
|
||||
NOT a receipt. Before declaring anything complete:
|
||||
|
||||
- **Re-run the decisive command** and confirm the end-state is stable and
|
||||
reproducible, not just the first time.
|
||||
- **Re-read the saved evidence file** and confirm it is non-empty and actually
|
||||
parseable / decodable — an empty or corrupt artifact saves the mark but
|
||||
proves nothing.
|
||||
- **Confirm the downstream end-state** that proves acceptance (e.g. the control
|
||||
that confirms the objective is now flagged complete, the input is locked, the
|
||||
expected confirmation is present) by an independent re-look, not by
|
||||
remembering the earlier click.
|
||||
|
||||
Treat a single success message as provisional until independently confirmed.
|
||||
|
||||
## 2. Corroborate ≥2 independent angles before Validated
|
||||
|
||||
Never promote a finding to **Validated** on one self-report, one mirror, one
|
||||
third-party re-statement, or one artifact you did not verify yourself.
|
||||
|
||||
- Require **two independent evidence angles** (different tool + manual
|
||||
confirmation, or a primary artifact trace + an independent re-derivation), OR
|
||||
a direct trace to the artifact with a plausibility/format check.
|
||||
- **Third-party re-statements are not ground truth.** If public sources
|
||||
disagree with what you re-derived from the actual response/parser/config,
|
||||
trust the primary evidence and re-derive — do not copy the external value.
|
||||
- **A clean offline/in-lab decode is not proof the live target agrees.** Only
|
||||
the target's own accepted state confirms an objective; an offline derivation
|
||||
is a strong signal, not a receipt.
|
||||
- Apply a **shape/format check** to any value before using it (mask length,
|
||||
expected charset, concrete structure) — a value that does not fit the shape
|
||||
is the wrong value, not a reason to force it.
|
||||
|
||||
## 3. "No result" is not absence
|
||||
|
||||
A single empty search, one 404, or one failed tool call is a *degraded probe*,
|
||||
not proof of absence.
|
||||
|
||||
- **Parallel multi-source sweep** — run independent queries/engines/sources in
|
||||
one batch before concluding a CVE, exploit, technique, or feature is
|
||||
unavailable.
|
||||
- **Tool failure ≠ absence.** A backend error, a missing library, or a
|
||||
credit/quota error is a tooling problem — fix and re-run, then re-check.
|
||||
Do not record "no result" from a call that errored.
|
||||
- **Gated preview ≠ usable source.** A search hit that renders only an intro /
|
||||
paywall / "not ready to disclose" placeholder contains no answers; do not
|
||||
cite it as coverage.
|
||||
|
||||
## 4. Classify by inspection, not assumption
|
||||
|
||||
Do not assume an endpoint is vulnerable to class X because it "looks like" X or
|
||||
a label says so. Inspect the **actual** request/response, the deployed parser,
|
||||
or the fixture before pattern-matching. A wrong classification wastes batches
|
||||
on the wrong bypass set.
|
||||
|
||||
## 5. Lateral read / deliberate-twist re-read
|
||||
|
||||
When a path stalls or feels contrived, re-read the actual artifact literally and
|
||||
ask "what is the deliberate misdirection here?" Consider:
|
||||
|
||||
- **Decoys / mislabelled values** — a field labelled one way that is actually a
|
||||
different primitive.
|
||||
- **Runtime-assembled values** — the real object is derived, not stored.
|
||||
- **Alternate encodings** — different charset, escaping, or block boundaries.
|
||||
- **Rendered / raw-byte reads** — inspect rendered output, raw bytes, or
|
||||
non-obvious answer shapes (e.g. the shape implied by a masked field).
|
||||
- **Off-by-class** — a category-shift where the real answer is a different
|
||||
object than the obvious one.
|
||||
|
||||
This is a re-read of the *target evidence*, not a search for external answers.
|
||||
|
||||
## 6. Blocker taxonomy — know the difference between "hard-walled" and "keep going"
|
||||
|
||||
| State | Meaning | Action |
|
||||
|-------|---------|--------|
|
||||
| **Stuck, keep grinding** | Evidence absent but a new angle exists | Run the next cheap test / new source; do not repeat the same command unchanged |
|
||||
| **Hard-walled now** | Blocked by something outside your control (needs user input, external access, an action that must be performed inside the target's own environment, expired access, an approval) | **Report the exact blocker + the unlock and STOP this path.** Do not loop or improvise |
|
||||
|
||||
Looping on the same probe, or improvising past a blocker it is not authorized
|
||||
to cross, is the failure mode. Surface real blockers precisely and move on.
|
||||
|
||||
## 7. Never fabricate
|
||||
|
||||
If no primary/accessible evidence supports a value, report it as **unsupported /
|
||||
unconfirmed** rather than reconstructing it and presenting it as observed. A
|
||||
finding's confidence is only as honest as the evidence trail behind it.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Flag-Capture Mode
|
||||
|
||||
Optional FLAGS phase for **authorised training / lab / challenge-style**
|
||||
engagements where the test objective is to retrieve one or more secret "flag"
|
||||
objects (a value the target mints or stores as proof of a completed objective).
|
||||
Applies alongside `POST_EXPLOITATION` verb like privilege escalation, but the
|
||||
deliverable is a captured value plus a verification receipt.
|
||||
|
||||
Use `templates/flag-capture-register.md` to track every captured value.
|
||||
|
||||
## Core invariants
|
||||
|
||||
1. **Target-sourced only.** The value is retrieved from the target — its
|
||||
filesystem, a response body, a config node, memory, or re-derived from
|
||||
target evidence you can read. It is **never** sourced from a blog, writeup,
|
||||
search engine, or third-party dump. Searching online for the value is out of
|
||||
scope for this mode and forbidden.
|
||||
2. **Derive from evidence, not memory of sources.** If public descriptions of a
|
||||
lab disagree with what the actual target returns, trust the primary target
|
||||
evidence and re-derive. A value that only appears in a third-party re-statement
|
||||
is not a capture.
|
||||
3. **Shape/mask check.** Compare every candidate to the expected shape (length,
|
||||
charset, structure) before recording or using it.
|
||||
4. **Independent re-check.** Verify capture by re-reading the target's accepted
|
||||
end-state (value retained / control disabled / objective complete), not from
|
||||
the first accept toast.
|
||||
5. **Paced, one at a time.** Apply capture values sequentially with backoff;
|
||||
never blast a batch, which anti-automation swallows.
|
||||
6. **Static analysis only.** Never execute a recovered binary or payload to
|
||||
mint a value — analyze with parsers, disassemblers, and decoders and derive
|
||||
the value.
|
||||
7. **Report in-target blockers honestly.** If a step that mints or verifies the
|
||||
value must run inside the target's own environment and cannot be reached from
|
||||
this host, record what you can from accessible evidence, mark the row
|
||||
`unsupported`/`blocked`, and report the exact blocker. Never fabricate.
|
||||
|
||||
## When it adds no value
|
||||
|
||||
Flag capture is not a substitute for a normal finding: it is an objective
|
||||
marker for authorized training/lab work. For real-world engagements, the FLAGS
|
||||
phase is skipped and objectives are reported as findings with CVSS + evidence.
|
||||
@@ -11,6 +11,8 @@ Use this reference during the mandatory final engagement phase. Real-world targe
|
||||
5. **Client feedback loop** — if the client later reports a missed issue, patch the relevant playbook and add a detection gate to `playbooks/vuln-research.md`.
|
||||
6. **Recent-CVE follow-up** — after delivery, check whether new CVEs affect the identified technology stack and record any follow-up recommendations.
|
||||
7. **False-rejection review** — inspect every Rejected hypothesis. Confirm it records the exact test command and response, a source/parser-verified status, and a rejection reason. Reopen any `syntax_uncertain` or `not_tested` item; document where an incorrect rejection caused circular testing or delayed the valid path.
|
||||
8. **Decoy-trail review** — confirm the hypothesis board's Decoy Trail records every killed approach with a WHY and signature. Any path killed simply because it "stopped producing new info" but still has an untried cheap test should be reopened as a new hypothesis, not silently dropped.
|
||||
9. **Flag-capture review (FLAGS mode only)** — verify every captured value in `templates/flag-capture-register.md` was **target-sourced** (never pulled from an online blog/writeup/dump), passed a shape/mask check, and was independently re-checked against the objective's accepted end-state. Any online-sourced value invalidates the capture — flag it in the retrospective and remove it from the report.
|
||||
|
||||
## Coverage Matrix
|
||||
|
||||
|
||||
@@ -260,6 +260,11 @@ finding:
|
||||
|
||||
## 4. Verification
|
||||
|
||||
Verification-discipline rules (independent re-check, corroborate ≥2 evidence
|
||||
angles before Validated, "no result ≠ absence", blocker taxonomy) are in
|
||||
`references/evidence-and-verification-discipline.md`. Flag-capture (FLAGS) mode
|
||||
rules are in `references/flags-mode.md`.
|
||||
|
||||
When checking that this document is being followed, verify:
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Flag Capture Register — <target> <YYYY-MM-DD>
|
||||
|
||||
> Objective-objective register for flag-capture (FLAGS) mode. One row per
|
||||
> captured secret/proof value. A captured value is sourced ONLY from the target
|
||||
> (its filesystem, response, config, memory, or derived from target evidence) —
|
||||
> NEVER from a blog, writeup, search engine, or third-party dump. Sourcing a
|
||||
> value online is out of scope and forbidden in this mode.
|
||||
> Mode notes: `skills/pentest/references/flags-mode.md`.
|
||||
|
||||
| ID | Objective | Captured value | Source (on/from target) | Shape/mask check | Corroboration | Verif. (independent re-check) | Status | Notes |
|
||||
|----|-----------|----------------|--------------------------|------------------|---------------|-------------------------------|--------|-------|
|
||||
| FLG-001 | `<human-readable objective>` | `<exact captured string>` | `<path/endpoint/config node it came from + how>` | `<expected shape + matches?>` | `<second angle or re-derivation, or 'primary trace only'>` | `<accepted-state confirmed by re-look?>` | `captured / verified / unsupported` | `<any step that must run inside the target env>` |
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Target-sourced only.** Capture the value from the live target or derive it
|
||||
from target evidence. Do not search online for it. A third-party source that
|
||||
states the value is ignored — never entered into this register.
|
||||
2. **Shape/mask check first.** Compare every candidate to the expected shape
|
||||
(length, charset, structure) BEFORE recording or using it. A mismatch means
|
||||
re-derive, never force.
|
||||
3. **Independent re-check.** Do not record a value as *verified* from the
|
||||
moment a submission/accept toast fired. Re-look and confirm the target's
|
||||
accepted end-state (value retained / control disabled / objective complete)
|
||||
by an independent re-read.
|
||||
4. **Paced, sequential.** Submit/apply capture values one objective at a time;
|
||||
a blast of many values in one turn gets swallowed by rate/anti-automation
|
||||
and gives no useful signal. Back off between attempts.
|
||||
5. **In-target steps.** If a step that mints/verifies the value must be
|
||||
performed inside the target's own environment (not reachable from this
|
||||
host), capture and record everything you can from the accessible evidence,
|
||||
mark it `unsupported`/`blocked`, and report the exact blocker — do not
|
||||
fabricate the value.
|
||||
6. **Unsupported beats fabricated.** If the accessible evidence cannot
|
||||
produce the value, record `unsupported` and move on. Never reconstruct a
|
||||
value and present it as captured.
|
||||
@@ -17,6 +17,15 @@ Candidate ──► Likely ──► Validated
|
||||
- **Validated** — proven with working PoC. Document and report.
|
||||
- **Rejected** — tested, not vulnerable. Log the negative result.
|
||||
|
||||
### Per-hypothesis discipline (mandatory fields)
|
||||
|
||||
Every active hypothesis carries a **confidence**, a **timebox**, the **cheapest
|
||||
discriminating test**, and explicit **kill criteria**. Run the cheapest test
|
||||
that can actually distinguish the theory FIRST; kill the path the moment
|
||||
evidence contradicts it OR it stops producing new information — record why in
|
||||
the Decoy Trail so the same trap is never re-entered. A hypothesis that keeps
|
||||
yielding nothing new is not "in progress", it is stuck.
|
||||
|
||||
---
|
||||
|
||||
## Active Theories
|
||||
@@ -24,6 +33,9 @@ Candidate ──► Likely ──► Validated
|
||||
<!-- Add hypotheses below this line using the format:
|
||||
### H-001: <short title>
|
||||
- **Status:** Candidate
|
||||
- **Confidence:** 0.1–1.0 (guesstimate; escalate only with evidence)
|
||||
- **Timebox:** <e.g. 4 tool batches or 30 min — then re-evaluate>
|
||||
- **Cheapest test:** <single cheapest probe that discriminates this theory>
|
||||
- **Phase:** VULN_RESEARCH
|
||||
- **Target:** <endpoint/host/parameter>
|
||||
- **Vuln class:** <SQLi | XSS | IDOR | SSRF | ...>
|
||||
@@ -34,14 +46,13 @@ Candidate ──► Likely ──► Validated
|
||||
- **Test Command:** <exact syntax tested, including argument order>
|
||||
- **Test Response:** <exact decisive response/error>
|
||||
- **Verification Status:** <syntax_confirmed | syntax_uncertain | not_implemented | not_tested>
|
||||
- **Kill criteria:** <evidence that contradicts, OR no new info in N batches — then kill & log in Decoy Trail>
|
||||
- **Rejection Reason:** <required only for Rejected; syntax-uncertain tests must stay active for corrected re-test>
|
||||
- **Next step:** <what to do next to confirm or reject>
|
||||
- **Linked findings:** <FIND-001 or none>
|
||||
- **Updated:** <YYYY-MM-DD HH:MM>
|
||||
-->
|
||||
|
||||
--->
|
||||
|
||||
## Observations (ungrouped)
|
||||
|
||||
- **OBS-001:** <what> — <where> — <date> — <significance>
|
||||
@@ -55,6 +66,17 @@ Candidate ──► Likely ──► Validated
|
||||
|
||||
---
|
||||
|
||||
## Decoy Trail (killed approaches — do NOT re-enter)
|
||||
|
||||
> Every killed path is logged here with WHY so a disproven technique is never
|
||||
> re-attempted from scratch. Before re-running any probe, check this trail and
|
||||
> the command history for the same signature. "Worth another look" belongs in
|
||||
> Active Theories with a new cheap test, not here.
|
||||
|
||||
- **KILL-001:** <approach/technique/endpoint> — killed because <evidence contradicted / no new info in N batches / wrong class> — signature <command-or-probe shape> — <YYYY-MM-DD>
|
||||
|
||||
---
|
||||
|
||||
## Research Log
|
||||
|
||||
- **RES-001:** Trigger: <discovery> → Searched: <source> for <query> → Result: <CVE-XXXX / no results / PoC at URL> → Action: <created H-XXX / dismissed / escalated>
|
||||
|
||||
@@ -327,3 +327,62 @@ def test_sync_windows_are_phase_aware() -> None:
|
||||
assert state.sync_credit_limit("RECON") == 10
|
||||
assert state.sync_credit_limit("EXPLOITATION") == 20
|
||||
assert state.sync_credit_limit("PRIVESC") == 20
|
||||
|
||||
|
||||
def test_update_hypothesis_supports_discipline_fields(tmp_path: Path) -> None:
|
||||
hyp_file = tmp_path / "hypotheses.md"
|
||||
hyp_file.write_text(
|
||||
"# Hypothesis Board\n\n## Active Theories\n\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
h = hypotheses.update_hypothesis(
|
||||
hyp_file,
|
||||
id="001",
|
||||
title="SQLi in authentication form",
|
||||
status="Candidate",
|
||||
confidence="0.8",
|
||||
timebox="4 tool batches",
|
||||
cheapest_test="' OR 1=1 --",
|
||||
kill_criteria="Response status 404 or no error output",
|
||||
next_step="Run sqlmap probe",
|
||||
linked_findings="FIND-001",
|
||||
)
|
||||
assert h.confidence == "0.8"
|
||||
assert h.timebox == "4 tool batches"
|
||||
assert h.cheapest_test == "' OR 1=1 --"
|
||||
assert h.kill_criteria == "Response status 404 or no error output"
|
||||
assert h.next_step == "Run sqlmap probe"
|
||||
assert h.linked_findings == "FIND-001"
|
||||
|
||||
parsed = hypotheses.parse_hypotheses(hyp_file)
|
||||
assert len(parsed) == 1
|
||||
assert parsed[0].confidence == "0.8"
|
||||
assert parsed[0].cheapest_test == "' OR 1=1 --"
|
||||
|
||||
text = hyp_file.read_text(encoding="utf-8")
|
||||
assert "- **Confidence:** 0.8" in text
|
||||
assert "- **Timebox:** 4 tool batches" in text
|
||||
assert "- **Cheapest test:** ' OR 1=1 --" in text
|
||||
assert "- **Kill criteria:** Response status 404 or no error output" in text
|
||||
|
||||
|
||||
def test_update_hypothesis_preserves_board_sections(tmp_path: Path) -> None:
|
||||
template_path = ROOT / "skills" / "pentest" / "templates" / "hypothesis-board.md"
|
||||
hyp_file = tmp_path / "hypotheses.md"
|
||||
hyp_file.write_text(template_path.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
|
||||
h = hypotheses.update_hypothesis(
|
||||
hyp_file,
|
||||
id="001",
|
||||
title="Command injection in search endpoint",
|
||||
status="Candidate",
|
||||
)
|
||||
assert h.id == "001"
|
||||
text = hyp_file.read_text(encoding="utf-8")
|
||||
assert "## Active Theories" in text
|
||||
assert "### H-001: Command injection in search endpoint" in text
|
||||
assert "## Observations (ungrouped)" in text
|
||||
assert "## Investigation Chains" in text
|
||||
assert "## Decoy Trail (killed approaches — do NOT re-enter)" in text
|
||||
assert "## Research Log" in text
|
||||
assert "## Resolved Theories" in text
|
||||
|
||||
Reference in New Issue
Block a user