mirror of
https://github.com/Strategic-Automation/violin.git
synced 2026-08-14 12:33:37 +02:00
Add CTF bootstrap and repair guard CLI docs
This commit is contained in:
@@ -85,6 +85,8 @@ def _create_artifact(
|
||||
rel: Path,
|
||||
template_rel: str | None,
|
||||
placeholder: str | None,
|
||||
host: str | None = None,
|
||||
ctf: bool = False,
|
||||
) -> None:
|
||||
target = eng_dir / rel
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -96,7 +98,7 @@ def _create_artifact(
|
||||
content = src.read_text(encoding="utf-8")
|
||||
if rel == Path("scope/scope.yaml"):
|
||||
data = yaml.safe_load(content)
|
||||
data["targets"]["ip_addresses"] = [_derive_host(eng_dir)]
|
||||
data["targets"]["ip_addresses"] = [host or _derive_host(eng_dir)]
|
||||
data["engagement"]["date"] = date.today().isoformat()
|
||||
content = yaml.safe_dump(data, sort_keys=False, default_flow_style=False)
|
||||
if rel == Path("state/ptt.md"):
|
||||
@@ -105,10 +107,50 @@ def _create_artifact(
|
||||
f"*Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*",
|
||||
content,
|
||||
)
|
||||
if ctf:
|
||||
content = _ctf_ptt(host or _derive_host(eng_dir))
|
||||
target.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def init_engagement(eng_dir: str | Path, host: str | None = None) -> int:
|
||||
def _ctf_ptt(host: str) -> str:
|
||||
today = date.today().isoformat()
|
||||
return f"""# CTF Task Tree — {host} {today}
|
||||
|
||||
*Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*
|
||||
|
||||
## Phase: RECON
|
||||
| ID | Status | Task | Evidence / Notes |
|
||||
|----|--------|------|------------------|
|
||||
| PT-CTF-001 | [~] | Enumerate services and attack surface | evidence/recon/ |
|
||||
|
||||
## Phase: EXPLOITATION
|
||||
| ID | Status | Task | Evidence / Notes |
|
||||
|----|--------|------|------------------|
|
||||
| PT-CTF-002 | [ ] | Validate an in-scope foothold | evidence/exploitation/ |
|
||||
|
||||
## Phase: PRIVESC
|
||||
| ID | Status | Task | Evidence / Notes |
|
||||
|----|--------|------|------------------|
|
||||
| PT-CTF-003 | [ ] | Enumerate and validate privilege escalation | evidence/exploitation/ |
|
||||
|
||||
## Phase: FLAGS
|
||||
| ID | Status | Task | Evidence / Notes |
|
||||
|----|--------|------|------------------|
|
||||
| PT-CTF-004 | [ ] | Capture user.txt and root.txt | evidence/flags/ |
|
||||
"""
|
||||
|
||||
|
||||
def _ctf_scope(host: str) -> dict:
|
||||
return {
|
||||
"targets": {"ip_addresses": [host], "in_scope_urls": []},
|
||||
"authorized_parties": ["lab owner (user)"],
|
||||
"rules_of_engagement": {"allowed_actions": ["host/port discovery", "banner grabbing", "version detection", "vulnerability scanning", "exploit validation (in-scope, non-destructive)", "privilege escalation", "flag capture (user.txt, root.txt)"], "forbidden_actions": []},
|
||||
"authorisation": {"confirmed": True, "confirmed_by": "user (HTB lab owner)"},
|
||||
"engagement": {"name": f"CTF {host}", "date": date.today().isoformat(), "type": "ctf", "mode": "standard-pentest", "depth": "black-box", "focus_areas": ["recon", "exploitation", "privilege-escalation", "flag-capture"]},
|
||||
}
|
||||
|
||||
|
||||
def init_engagement(eng_dir: str | Path, host: str | None = None, *, ctf: bool = False, session_id: str = "") -> int:
|
||||
"""Create a complete, guard-clean engagement directory from templates."""
|
||||
eng_dir = Path(eng_dir)
|
||||
result = BootstrapResult()
|
||||
@@ -119,9 +161,18 @@ def init_engagement(eng_dir: str | Path, host: str | None = None) -> int:
|
||||
target = eng_dir / rel
|
||||
if target.exists():
|
||||
continue
|
||||
_create_artifact(eng_dir, rel, template_rel, placeholder)
|
||||
_create_artifact(eng_dir, rel, template_rel, placeholder, host, ctf)
|
||||
result.add_info(f"created {rel}")
|
||||
|
||||
if ctf:
|
||||
scope_path = eng_dir / "scope" / "scope.yaml"
|
||||
scope_path.write_text(yaml.safe_dump(_ctf_scope(host), sort_keys=False), encoding="utf-8")
|
||||
result.add_info("wrote CTF scope")
|
||||
if session_id:
|
||||
marker = eng_dir / "state" / f".skill-loaded-{session_id}"
|
||||
marker.write_text("skill-loaded: ctf bootstrap\n", encoding="utf-8")
|
||||
result.add_info(f"marked skill loaded for session {session_id}")
|
||||
|
||||
if result.errors or result.warnings:
|
||||
result.add_error("init-engagement produced an incomplete or non-compliant engagement")
|
||||
result.print()
|
||||
@@ -267,4 +318,4 @@ def _auto_repair_corrupt_artifacts(
|
||||
for w in result.warnings:
|
||||
new_warnings.append(w)
|
||||
|
||||
return BootstrapResult(errors=new_errors, warnings=new_warnings, infos=new_infos)
|
||||
return BootstrapResult(errors=new_errors, warnings=new_warnings, infos=new_infos)
|
||||
|
||||
+21
-4
@@ -42,7 +42,16 @@ def cmd_check_bootstrap(args: argparse.Namespace) -> int:
|
||||
|
||||
|
||||
def cmd_init_engagement(args: argparse.Namespace) -> int:
|
||||
return bootstrap.init_engagement(args.eng_dir, host=args.host)
|
||||
return bootstrap.init_engagement(args.eng_dir, host=args.host, ctf=args.ctf, session_id=args.session_id)
|
||||
|
||||
|
||||
def cmd_validate_scope(args: argparse.Namespace) -> int:
|
||||
result = command.validate_scope(Path(args.scope))
|
||||
code = result.exit_code()
|
||||
label = "OK" if code == 0 else "REVIEW" if code == 2 else "BLOCK"
|
||||
messages = result.errors or result.warnings or result.infos or ["scope valid"]
|
||||
print(f"{label}: {messages[0]}")
|
||||
return code
|
||||
|
||||
|
||||
def cmd_check_skill_loaded(args: argparse.Namespace) -> int:
|
||||
@@ -114,10 +123,11 @@ def cmd_message_tick(args: argparse.Namespace) -> int:
|
||||
def cmd_eng_root(args: argparse.Namespace) -> int:
|
||||
from plugins.violin_guard.core import state
|
||||
|
||||
eng_root = state._eng_dir(args.eng_dir) if args.eng_dir else state._eng_dir("")
|
||||
eng_dir = args.eng_dir_option or args.eng_dir
|
||||
eng_root = state._eng_dir(eng_dir) if eng_dir else state._eng_dir("")
|
||||
print(f"ENG_ROOT={eng_root}")
|
||||
if args.eng_dir:
|
||||
resolved = state._eng_dir(args.eng_dir)
|
||||
if eng_dir:
|
||||
resolved = state._eng_dir(eng_dir)
|
||||
print(f"resolved={resolved}")
|
||||
return 0
|
||||
|
||||
@@ -154,6 +164,8 @@ def main() -> int:
|
||||
p = sub.add_parser("init-engagement", help="Create guard-clean engagement")
|
||||
p.add_argument("eng_dir")
|
||||
p.add_argument("--host", default="")
|
||||
p.add_argument("--ctf", action="store_true", help="Create an HTB/CTF-ready scope and PTT")
|
||||
p.add_argument("--session-id", default="", help="Mark this session skill-loaded for CTF bootstrap")
|
||||
p.set_defaults(func=cmd_init_engagement)
|
||||
|
||||
# check-skill-loaded
|
||||
@@ -197,8 +209,13 @@ def main() -> int:
|
||||
# eng-root
|
||||
p = sub.add_parser("eng-root", help="Print canonical engagement root")
|
||||
p.add_argument("eng_dir", nargs="?")
|
||||
p.add_argument("--eng-dir", dest="eng_dir_option")
|
||||
p.set_defaults(func=cmd_eng_root)
|
||||
|
||||
p = sub.add_parser("validate-scope", help="Validate scope.yaml")
|
||||
p.add_argument("--scope", required=True)
|
||||
p.set_defaults(func=cmd_validate_scope)
|
||||
|
||||
p = sub.add_parser("check-release", help="Run release checks")
|
||||
p.set_defaults(func=cmd_check_release)
|
||||
|
||||
|
||||
@@ -110,12 +110,12 @@ The phase workflow is mandatory for the entire session, including long, compress
|
||||
- `violin_record_ptt` changes task lifecycle state; `violin_record_hypothesis` records semantic hypothesis changes.
|
||||
- `violin_sync_done` verifies the explicit post-batch PTT update and unlocks the next target batch.
|
||||
- `violin_heartbeat_done` clears the periodic review lock after re-reading this skill and reviewing engagement files.
|
||||
- `sync-clear` is for session bootstrap/manual reconciliation of prior-session locks only; never use it to skip documenting a command that just ran.
|
||||
|
||||
0. **Bootstrap gate** — at session start, after `/goal set`, or after context compression that loses track of state, verify the engagement is bootstrapped: `python $HOME/.hermes/profiles/violin/scripts/violin_guard.py check-bootstrap --eng-dir "$ENG_DIR"`. Exit `0` = proceed. Exit `1` = **STOP and run `playbooks/scoping.md §0`** (creates `$ENG_DIR/`, `scope/scope.yaml`, `state/ptt.md`, `hypotheses.md`, `state/history.md`). Exit `2` = fix the warning, then proceed. This gate is non-negotiable: no `curl`, `nmap`, `browser_navigate`, or other target-touching tool call is allowed until exit 0.
|
||||
0.1. **Skill-load gate** — before any target interaction at session start, after `/goal set`, or after context compression, create the skill-load marker: `python $HOME/.hermes/profiles/violin/scripts/violin_guard.py check-skill-loaded --eng-dir "$ENG_DIR" --session-id "<session label>"`. Then pass `--skill-loaded-file "$ENG_DIR/state/.skill-loaded-<session-id>"` to every subsequent `check-command` invocation. Missing or stale marker = **BLOCK**; reload SKILL.md §2 and recreate the marker.
|
||||
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, active RECON PTT row, and skill marker.
|
||||
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"
|
||||
|
||||
@@ -22,9 +22,24 @@
|
||||
|
||||
### 0. Bootstrap: Create the Engagement Project
|
||||
|
||||
Use the guard CLI rather than manually copying templates. It creates the
|
||||
scope, PTT, hypothesis board, and command history:
|
||||
|
||||
```bash
|
||||
ENG_DIR="engagements/<target-name>-$(date +%F)"
|
||||
python3 scripts/violin_guard.py init-engagement --host <target-ip> "$ENG_DIR"
|
||||
|
||||
# Authorized HTB/CTF lab: ready-to-test scope, active RECON task, and skill marker.
|
||||
# python3 scripts/violin_guard.py init-engagement --ctf --session-id htb1 --host <target-ip> "$ENG_DIR"
|
||||
```
|
||||
|
||||
The legacy manual bootstrap below is retained only as a recovery reference;
|
||||
use `init-engagement` for normal work.
|
||||
|
||||
Run this before capturing any data — it creates the directory structure, PTT, and hypothesis board for this engagement:
|
||||
|
||||
```bash
|
||||
<!-- Legacy manual recovery reference.
|
||||
# Resolve an ABSOLUTE ENG_DIR under the canonical engagement root so the skill
|
||||
# tree and the violin-guard plugin tree never diverge (root-cause fix). The
|
||||
# `eng-root` subcommand strips any leading "engagements/" and resolves the path
|
||||
@@ -79,6 +94,7 @@ data.update({
|
||||
})
|
||||
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
PY
|
||||
-->
|
||||
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user