farai 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,6 +33,7 @@
33
33
  "hashcat",
34
34
  "hydra",
35
35
  "impacket-scripts",
36
+ "imagemagick",
36
37
  "iproute2",
37
38
  "iptables",
38
39
  "iputils-ping",
@@ -56,6 +57,7 @@
56
57
  "nuclei",
57
58
  "openssh-client",
58
59
  "openssl",
60
+ "poppler-utils",
59
61
  "pipx",
60
62
  "procps",
61
63
  "python3",
@@ -75,6 +77,7 @@
75
77
  "steghide",
76
78
  "subfinder",
77
79
  "tcpdump",
80
+ "tesseract-ocr",
78
81
  "testssl.sh",
79
82
  "theharvester",
80
83
  "tree",
@@ -98,14 +101,17 @@
98
101
  "curl",
99
102
  "dig",
100
103
  "file",
104
+ "identify",
101
105
  "ip",
102
106
  "iptables",
103
107
  "jq",
104
108
  "nc",
105
109
  "openssl",
110
+ "pdftotext",
106
111
  "ping",
107
112
  "rg",
108
113
  "ssh",
114
+ "tesseract",
109
115
  "unzip",
110
116
  "wget",
111
117
  "whois",
@@ -5,8 +5,10 @@ from __future__ import annotations
5
5
  import base64
6
6
  import json
7
7
  import os
8
+ import re
8
9
  import sqlite3
9
10
  import sys
11
+ import uuid
10
12
  from datetime import datetime, timezone
11
13
  from typing import Any
12
14
 
@@ -28,15 +30,16 @@ def env_int(name: str, default: int, minimum: int, maximum: int) -> int:
28
30
  MAX_BODY_PREVIEW = env_int("FARAI_PROXY_BODY_PREVIEW_BYTES", 65536, 1024, 1024 * 1024)
29
31
  MAX_MESSAGE_PREVIEW = env_int("FARAI_PROXY_MESSAGE_PREVIEW_BYTES", 16384, 1024, 256 * 1024)
30
32
  MAX_MESSAGES = env_int("FARAI_PROXY_MAX_MESSAGES", 200, 1, 1000)
33
+ REPLAY_CORRELATION_HEADER = "X-Farai-Replay-Correlation"
31
34
 
32
35
 
33
- class FlowStoreV2:
36
+ class FlowStore:
34
37
  def __init__(self, db_path: str):
35
38
  self.db_path = db_path
36
39
  with self._connect() as conn:
37
40
  conn.execute(
38
41
  """
39
- CREATE TABLE IF NOT EXISTS farai_flows_v2 (
42
+ CREATE TABLE IF NOT EXISTS farai_flows (
40
43
  id TEXT PRIMARY KEY,
41
44
  kind TEXT NOT NULL,
42
45
  timestamp REAL NOT NULL,
@@ -46,12 +49,12 @@ class FlowStoreV2:
46
49
  """
47
50
  )
48
51
  conn.execute(
49
- "CREATE INDEX IF NOT EXISTS idx_farai_flows_v2_timestamp "
50
- "ON farai_flows_v2(timestamp)"
52
+ "CREATE INDEX IF NOT EXISTS idx_farai_flows_timestamp "
53
+ "ON farai_flows(timestamp)"
51
54
  )
52
55
  conn.execute(
53
- "CREATE INDEX IF NOT EXISTS idx_farai_flows_v2_kind "
54
- "ON farai_flows_v2(kind)"
56
+ "CREATE INDEX IF NOT EXISTS idx_farai_flows_kind "
57
+ "ON farai_flows(kind)"
55
58
  )
56
59
 
57
60
  def _connect(self) -> sqlite3.Connection:
@@ -65,7 +68,7 @@ class FlowStoreV2:
65
68
  with self._connect() as conn:
66
69
  conn.execute(
67
70
  """
68
- INSERT INTO farai_flows_v2(id, kind, timestamp, summary_json, detail_json)
71
+ INSERT INTO farai_flows(id, kind, timestamp, summary_json, detail_json)
69
72
  VALUES (?, ?, ?, ?, ?)
70
73
  ON CONFLICT(id) DO UPDATE SET
71
74
  kind=excluded.kind,
@@ -78,7 +81,7 @@ class FlowStoreV2:
78
81
 
79
82
  def summaries(self, limit: int, kind: str | None = None) -> list[dict[str, Any]]:
80
83
  bounded_limit = max(1, min(int(limit), 1000))
81
- sql = "SELECT summary_json FROM farai_flows_v2"
84
+ sql = "SELECT summary_json FROM farai_flows"
82
85
  params: list[Any] = []
83
86
  if kind:
84
87
  sql += " WHERE kind = ?"
@@ -92,22 +95,42 @@ class FlowStoreV2:
92
95
  def detail(self, flow_id: str) -> dict[str, Any] | None:
93
96
  with self._connect() as conn:
94
97
  row = conn.execute(
95
- "SELECT detail_json FROM farai_flows_v2 WHERE id = ?", (flow_id,)
98
+ "SELECT detail_json FROM farai_flows WHERE id = ?", (flow_id,)
96
99
  ).fetchone()
97
100
  return json.loads(row[0]) if row else None
98
101
 
99
102
  def clear(self) -> None:
100
103
  with self._connect() as conn:
101
- conn.execute("DELETE FROM farai_flows_v2")
104
+ conn.execute("DELETE FROM farai_flows")
102
105
 
103
106
 
104
107
  class FaraiTrafficRecorder(UPSTREAM_TRAFFIC_RECORDER):
105
108
  def __init__(self, scope: Any):
106
109
  super().__init__(scope)
107
- self.v2 = FlowStoreV2(self.db.db_path)
110
+ self.flows = FlowStore(self.db.db_path)
111
+ self.intercept_config: dict[str, Any] = {
112
+ "enabled": False,
113
+ "host_pattern": ".*",
114
+ "path_pattern": ".*",
115
+ "methods": [],
116
+ }
117
+ self.pending_intercepts: dict[str, http.HTTPFlow] = {}
118
+ self.replay_correlations: dict[str, dict[str, str]] = {}
108
119
 
109
120
  def request(self, flow: http.HTTPFlow) -> None:
121
+ correlation = flow.request.headers.get(REPLAY_CORRELATION_HEADER)
122
+ if correlation:
123
+ flow.request.headers.pop(REPLAY_CORRELATION_HEADER, None)
124
+ replay = self.replay_correlations.get(str(correlation))
125
+ if replay is not None:
126
+ replay["descendantFlowId"] = flow.id
127
+ metadata = getattr(flow, "metadata", None)
128
+ if isinstance(metadata, dict):
129
+ metadata["faraiReplayParentId"] = replay["parentFlowId"]
110
130
  super().request(flow)
131
+ if self._should_intercept(flow):
132
+ flow.intercept()
133
+ self.pending_intercepts[flow.id] = flow
111
134
  self._capture(flow)
112
135
 
113
136
  def response(self, flow: http.HTTPFlow) -> None:
@@ -160,24 +183,106 @@ class FaraiTrafficRecorder(UPSTREAM_TRAFFIC_RECORDER):
160
183
  def dns_error(self, flow: dns.DNSFlow) -> None:
161
184
  self._capture(flow)
162
185
 
163
- def get_flow_summary_v2(self, limit: int = 20, kind: str | None = None) -> list[dict[str, Any]]:
164
- return self.v2.summaries(limit, kind)
186
+ def farai_flow_summaries(self, limit: int = 20, kind: str | None = None) -> list[dict[str, Any]]:
187
+ return self.flows.summaries(limit, kind)
165
188
 
166
- def get_flow_detail_v2(self, flow_id: str) -> dict[str, Any] | None:
167
- return self.v2.detail(flow_id)
189
+ def farai_flow_detail(self, flow_id: str) -> dict[str, Any] | None:
190
+ return self.flows.detail(flow_id)
168
191
 
169
192
  def clear(self) -> None:
170
193
  super().clear()
171
- self.v2.clear()
194
+ self.flows.clear()
195
+
196
+ def scope_state(self) -> dict[str, Any]:
197
+ return {"allowedDomains": list(self.scope.config.allowed_domains)}
198
+
199
+ def configure_intercept(
200
+ self,
201
+ enabled: bool,
202
+ host_pattern: str = ".*",
203
+ path_pattern: str = ".*",
204
+ methods: list[str] | None = None,
205
+ ) -> dict[str, Any]:
206
+ re.compile(host_pattern)
207
+ re.compile(path_pattern)
208
+ self.intercept_config = {
209
+ "enabled": bool(enabled),
210
+ "host_pattern": host_pattern,
211
+ "path_pattern": path_pattern,
212
+ "methods": sorted({method.upper() for method in (methods or [])}),
213
+ }
214
+ return {**self.intercept_config, "pending": len(self.pending_intercepts)}
215
+
216
+ def list_intercepts(self) -> list[dict[str, Any]]:
217
+ result: list[dict[str, Any]] = []
218
+ for flow in self.pending_intercepts.values():
219
+ summary, _, _ = serialize_http(flow)
220
+ result.append(summary)
221
+ return sorted(result, key=lambda item: str(item.get("timestamp", "")))
222
+
223
+ def begin_replay(self, parent_flow_id: str) -> tuple[str, dict[str, str]]:
224
+ token = uuid.uuid4().hex
225
+ state = {"parentFlowId": parent_flow_id}
226
+ self.replay_correlations[token] = state
227
+ return token, state
228
+
229
+ def finish_replay(self, token: str) -> dict[str, str]:
230
+ return self.replay_correlations.pop(token, {})
231
+
232
+ def resolve_intercept(
233
+ self,
234
+ flow_id: str,
235
+ action: str,
236
+ method: str | None = None,
237
+ url: str | None = None,
238
+ headers: dict[str, str] | None = None,
239
+ body: str | None = None,
240
+ ) -> dict[str, Any]:
241
+ flow = self.pending_intercepts.pop(flow_id, None)
242
+ if flow is None:
243
+ raise ValueError(f"Unknown pending intercepted flow: {flow_id}")
244
+ if action == "drop":
245
+ flow.kill()
246
+ return {"flowId": flow_id, "action": "drop"}
247
+ if action not in {"forward", "edit"}:
248
+ self.pending_intercepts[flow_id] = flow
249
+ raise ValueError("action must be forward, edit, or drop")
250
+ if action == "edit":
251
+ if method:
252
+ flow.request.method = method.upper()
253
+ if url:
254
+ flow.request.url = url
255
+ for key, value in (headers or {}).items():
256
+ flow.request.headers[key] = value
257
+ if body is not None:
258
+ flow.request.text = body
259
+ flow.resume()
260
+ return {"flowId": flow_id, "action": action}
261
+
262
+ def _should_intercept(self, flow: http.HTTPFlow) -> bool:
263
+ config = self.intercept_config
264
+ if not config["enabled"] or flow.id in self.pending_intercepts:
265
+ return False
266
+ methods = config["methods"]
267
+ return (
268
+ (not methods or flow.request.method.upper() in methods)
269
+ and re.search(config["host_pattern"], http_display_host(flow.request)) is not None
270
+ and re.search(config["path_pattern"], flow.request.path) is not None
271
+ )
172
272
 
173
273
  def _capture(self, flow: Any) -> None:
174
274
  try:
175
275
  if not self._is_allowed(flow):
176
276
  return
177
277
  summary, detail, timestamp = serialize_flow(flow)
178
- self.v2.save(summary, detail, timestamp)
278
+ metadata = getattr(flow, "metadata", None)
279
+ parent_flow_id = metadata.get("faraiReplayParentId") if isinstance(metadata, dict) else None
280
+ if parent_flow_id:
281
+ summary["parentFlowId"] = parent_flow_id
282
+ detail["parentFlowId"] = parent_flow_id
283
+ self.flows.save(summary, detail, timestamp)
179
284
  except Exception as exc:
180
- print(f"Failed to save Farai v2 flow: {exc}", file=sys.stderr)
285
+ print(f"Failed to save Farai flow: {exc}", file=sys.stderr)
181
286
 
182
287
  def _is_allowed(self, flow: Any) -> bool:
183
288
  if isinstance(flow, http.HTTPFlow):
@@ -537,24 +642,150 @@ def optional(target: dict[str, Any], key: str, value: Any) -> None:
537
642
 
538
643
 
539
644
  @server.mcp.tool()
540
- async def get_flow_summary_v2(limit: int = 20, kind: str | None = None) -> str:
645
+ async def proxy_flow_summaries(limit: int = 20, kind: str | None = None) -> str:
541
646
  """Return HTTP, WebSocket, TCP, UDP, and DNS flow summaries."""
542
647
  recorder = server.controller.recorder
543
648
  if not isinstance(recorder, FaraiTrafficRecorder):
544
649
  return "[]"
545
- return json.dumps(recorder.get_flow_summary_v2(limit, kind), indent=2)
650
+ return json.dumps(recorder.farai_flow_summaries(limit, kind), indent=2)
546
651
 
547
652
 
548
653
  @server.mcp.tool()
549
- async def inspect_flow_v2(flow_id: str) -> str:
654
+ async def proxy_flow_inspect(flow_id: str) -> str:
550
655
  """Return protocol-aware detail for a captured flow."""
551
656
  recorder = server.controller.recorder
552
657
  if not isinstance(recorder, FaraiTrafficRecorder):
553
658
  return "Couldn't find that flow."
554
- detail = recorder.get_flow_detail_v2(flow_id)
659
+ detail = recorder.farai_flow_detail(flow_id)
555
660
  return json.dumps(detail, indent=2) if detail else "Couldn't find that flow."
556
661
 
557
662
 
663
+ @server.mcp.tool()
664
+ async def proxy_replay_correlated(
665
+ flow_id: str,
666
+ method: str | None = None,
667
+ headers_json: str | None = None,
668
+ body: str | None = None,
669
+ timeout: float = 30.0,
670
+ ) -> str:
671
+ """Replay an HTTP flow and return its exact captured descendant flow id."""
672
+ recorder = server.controller.recorder
673
+ if not isinstance(recorder, FaraiTrafficRecorder):
674
+ return json.dumps({"ok": False, "error": "Farai recorder is unavailable."})
675
+ if recorder.farai_flow_detail(flow_id) is None:
676
+ return json.dumps({"ok": False, "error": f"Couldn't find flow {flow_id}."})
677
+ try:
678
+ headers = json.loads(headers_json) if headers_json else {}
679
+ except json.JSONDecodeError as exc:
680
+ return json.dumps({"ok": False, "error": f"Invalid headers_json: {exc}"})
681
+ if not isinstance(headers, dict) or not all(isinstance(key, str) and isinstance(value, str) for key, value in headers.items()):
682
+ return json.dumps({"ok": False, "error": "headers_json must encode a string-to-string object."})
683
+
684
+ # Upstream treats None as "reuse the captured body"; an empty string omits it.
685
+ resolved_body = "" if body == "__omit__" else body
686
+ variables = getattr(server.controller, "session_variables", {})
687
+ resolver = getattr(server, "_resolve_template", None)
688
+ if variables and callable(resolver):
689
+ headers = json.loads(resolver(json.dumps(headers), variables))
690
+ if resolved_body:
691
+ resolved_body = resolver(resolved_body, variables)
692
+
693
+ token, correlation = recorder.begin_replay(flow_id)
694
+ headers[REPLAY_CORRELATION_HEADER] = token
695
+ try:
696
+ message = await server.controller.replay_request(flow_id, method, headers, resolved_body, timeout)
697
+ result = recorder.finish_replay(token)
698
+ except Exception as exc:
699
+ recorder.finish_replay(token)
700
+ return json.dumps({"ok": False, "parentFlowId": flow_id, "error": str(exc)})
701
+ ok = not any(marker in message.lower() for marker in ("couldn't find", "didn't work", "invalid"))
702
+ return json.dumps(
703
+ {
704
+ "ok": ok,
705
+ **correlation,
706
+ **result,
707
+ "message": message,
708
+ **({} if ok else {"error": message}),
709
+ },
710
+ indent=2,
711
+ )
712
+
713
+
714
+ @server.mcp.tool()
715
+ async def proxy_scope_get() -> str:
716
+ """Return Farai's active mitmproxy capture scope."""
717
+ recorder = server.controller.recorder
718
+ if not isinstance(recorder, FaraiTrafficRecorder):
719
+ return json.dumps({"allowedDomains": []})
720
+ return json.dumps(recorder.scope_state(), indent=2)
721
+
722
+
723
+ @server.mcp.tool()
724
+ async def proxy_intercept_configure(
725
+ enabled: bool,
726
+ host_pattern: str = ".*",
727
+ path_pattern: str = ".*",
728
+ methods: list[str] | None = None,
729
+ ) -> str:
730
+ """Enable or disable Farai's manual request interception queue."""
731
+ recorder = server.controller.recorder
732
+ if not isinstance(recorder, FaraiTrafficRecorder):
733
+ raise RuntimeError("Farai recorder is unavailable.")
734
+ try:
735
+ return json.dumps(
736
+ recorder.configure_intercept(enabled, host_pattern, path_pattern, methods),
737
+ indent=2,
738
+ )
739
+ except re.error as exc:
740
+ raise ValueError(f"Invalid interception pattern: {exc}") from exc
741
+
742
+
743
+ @server.mcp.tool()
744
+ async def proxy_intercept_get() -> str:
745
+ """Return Farai's manual interception configuration and queue size."""
746
+ recorder = server.controller.recorder
747
+ if not isinstance(recorder, FaraiTrafficRecorder):
748
+ return json.dumps({"enabled": False, "pending": 0})
749
+ return json.dumps(
750
+ {**recorder.intercept_config, "pending": len(recorder.pending_intercepts)},
751
+ indent=2,
752
+ )
753
+
754
+
755
+ @server.mcp.tool()
756
+ async def proxy_intercept_list() -> str:
757
+ """List requests currently paused in Farai's interception queue."""
758
+ recorder = server.controller.recorder
759
+ if not isinstance(recorder, FaraiTrafficRecorder):
760
+ return "[]"
761
+ return json.dumps(recorder.list_intercepts(), indent=2)
762
+
763
+
764
+ @server.mcp.tool()
765
+ async def proxy_intercept_resolve(
766
+ flow_id: str,
767
+ action: str,
768
+ method: str | None = None,
769
+ url: str | None = None,
770
+ headers_json: str | None = None,
771
+ body: str | None = None,
772
+ ) -> str:
773
+ """Forward, edit, or drop one request from Farai's interception queue."""
774
+ recorder = server.controller.recorder
775
+ if not isinstance(recorder, FaraiTrafficRecorder):
776
+ raise RuntimeError("Farai recorder is unavailable.")
777
+ headers = json.loads(headers_json) if headers_json else None
778
+ if headers is not None and (
779
+ not isinstance(headers, dict)
780
+ or not all(isinstance(key, str) and isinstance(value, str) for key, value in headers.items())
781
+ ):
782
+ raise ValueError("headers_json must encode a string-to-string object.")
783
+ return json.dumps(
784
+ recorder.resolve_intercept(flow_id, action, method, url, headers, body),
785
+ indent=2,
786
+ )
787
+
788
+
558
789
  server.TrafficRecorder = FaraiTrafficRecorder
559
790
 
560
791
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "farai",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Cyber-first freestyle local AI agent",
@@ -60,6 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@opentui/core": "0.5.8",
63
+ "ajv": "8.20.0",
63
64
  "bun-pty": "0.4.10",
64
65
  "entities": "7.0.1",
65
66
  "fuzzysort": "3.1.0",
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: binary-exploitation
3
+ description: "Workflow for pwn and memory-corruption challenges: interface discovery, mitigations, crash triage, primitive construction, local exploit validation, and remote adaptation. Use for buffer overflows, format strings, heap bugs, ROP, shellcode, race exploitation, or exploit development against a supplied binary/service."
4
+ ---
5
+
6
+ # binary exploitation
7
+
8
+ build and validate one exploitation primitive at a time.
9
+
10
+ 1. identify architecture, mitigations, linkage, libc/loader information, protocol or menu behavior, and how input reaches the process.
11
+ 2. reproduce a controlled failure locally. determine the exact offset and root cause before building a payload around it.
12
+ 3. name the primitive you have and the next primitive required: instruction-pointer control, leak, arbitrary read/write, allocation control, stack pivot, or code execution.
13
+ 4. construct the smallest payload that proves each transition. inspect registers, memory, stack alignment, bad bytes, and process state rather than inferring success from a disconnect.
14
+ 5. script the interaction early enough to make attempts deterministic, but keep payload stages observable while debugging.
15
+ 6. only after the local path is stable, account for remote differences such as libc, loader, ASLR, buffering, timing, file descriptors, and network framing.
16
+
17
+ ## recovery
18
+
19
+ - inconsistent offset: verify the exact input path, newline handling, encoding, and crash context.
20
+ - gadget or address failure: re-check architecture, module base, PIE, stack alignment, and bad bytes.
21
+ - local success but remote failure: preserve the local proof, then isolate one environmental difference at a time.
22
+ - no obvious memory bug: return to input boundaries and state transitions; do not force a ROP approach onto a logic or crypto challenge.
23
+
24
+ ## completion
25
+
26
+ confirm the requested effect through process behavior or recovered output. a crash, probable gadget chain, or unexplained connection close is not proof of exploitation.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: binary-reversing
3
+ description: Workflow for understanding native, managed, Go, Rust, JVM, Android, firmware, or obfuscated binaries and recovering hidden logic or data. Use for reverse-engineering challenges, crackmes, malware logic, decompilation, key/flag recovery, or when source is unavailable; pair with binary-exploitation only when memory corruption is the objective.
4
+ ---
5
+
6
+ # binary reversing
7
+
8
+ move from cheap structural facts to the smallest control-flow slice that answers the objective.
9
+
10
+ 1. identify format, architecture, linkage, symbols, protections, runtime, imports, sections, and obvious embedded artifacts. record facts before interpreting them.
11
+ 2. run the program with bounded, representative inputs when safe and useful. observe files, arguments, environment, stdout/stderr, and system interactions instead of guessing its interface from strings alone.
12
+ 3. locate the success, comparison, decryption, parsing, or output path. work backward through callers and data references; avoid decompiling the entire binary without a question.
13
+ 4. reconcile decompiler output with disassembly or runtime state whenever types, compiler optimizations, obfuscation, or stripped symbols make the pseudocode ambiguous.
14
+ 5. extract constants and encode the recovered transformation in a small script. validate it against known program behavior or by round-tripping an input.
15
+
16
+ ## runtime-aware guidance
17
+
18
+ - Go/Rust/managed binaries often retain metadata that is more useful than generic string scraping; use runtime-aware symbols and metadata before treating the file as ordinary stripped native code.
19
+ - packed or self-modifying code may require observing the unpacked memory image before static analysis becomes meaningful.
20
+ - when execution is silent, inspect exit status and side effects and trace the relevant path; do not repeatedly rerun with random input.
21
+
22
+ ## completion
23
+
24
+ the result is complete when the recovered logic or data is independently reproduced, not when a likely function or interesting string has merely been identified.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: crypto-solving
3
+ description: "Workflow for cryptography challenges and custom encoding: formalize the construction, identify exploitable structure, implement attacks, and validate recovered plaintext or keys. Use for classical ciphers, RSA/ECC mistakes, symmetric misuse, PRNG attacks, hashes/MACs, secret sharing, lattice-style tasks, or layered encodings."
4
+ ---
5
+
6
+ # crypto solving
7
+
8
+ model the exact construction before choosing an attack.
9
+
10
+ 1. extract all known values, unknowns, equations, encodings, lengths, randomness assumptions, and attacker capabilities from source and output. preserve byte-versus-text and endian boundaries.
11
+ 2. identify the primitive and the implementation weakness separately. the name of a cipher does not establish the vulnerability.
12
+ 3. test the simplest structural explanation first: classical transformation, reused nonce/keystream, weak randomness, small parameter, oracle, algebraic relation, truncation, or encoding confusion.
13
+ 4. create a small script that reproduces the known output before using it to recover unknown values. validate intermediate equations on toy or supplied samples.
14
+ 5. use brute force only after bounding the search space and exploiting every available constraint. report the actual tested space and stopping condition.
15
+
16
+ ## recovery
17
+
18
+ - plausible but unreadable plaintext: re-check alphabet, offsets, block boundaries, padding, endian order, and whether another encoding layer remains.
19
+ - attack almost works: compare the implementation line by line with the mathematical assumption; challenge code often differs from the standard primitive in one decisive detail.
20
+ - multiple candidates: use format-independent constraints or re-encryption, not an assumed flag prefix alone, to select the answer.
21
+
22
+ ## completion
23
+
24
+ validate recovered plaintext, key material, or forged output by reversing the construction, re-encrypting, or satisfying the challenge verifier whenever possible.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: ctf-solving
3
+ description: End-to-end workflow for solving CTF challenges and benchmark tasks across web, crypto, pwn, reversing, forensics, and misc. Use when the user asks to solve a challenge, recover a flag, or continue a stalled challenge run; combine it with one domain skill when the category is known.
4
+ ---
5
+
6
+ # ctf solving
7
+
8
+ drive the challenge to a validated answer, not merely an analysis report.
9
+
10
+ 1. identify the exact objective, supplied artifacts, reachable targets, constraints, and current working directory. verify that required files or services are actually accessible before analyzing them.
11
+ 2. classify the dominant domain from evidence, then load at most the relevant specialist skill. a mixed challenge may change domains later; do not preload every playbook.
12
+ 3. establish one concrete hypothesis and run the cheapest discriminating test. preserve useful artifacts, commands, decoded values, offsets, endpoints, and credentials as the solve progresses.
13
+ 4. when a path fails, explain what the result ruled out and change the method. do not spend the run repeating filesystem searches, scanners, decoders, or equivalent payload variants.
14
+ 5. automate repetitive transformations or interaction once the manual primitive is understood. keep scripts in the workspace when they are part of the reproducible solve.
15
+ 6. continue past reconnaissance and partial reverse engineering until the requested objective is reached or a specific missing dependency blocks progress.
16
+
17
+ ## completion
18
+
19
+ - validate the answer against the challenge behavior or available oracle when possible.
20
+ - do not assume a fixed flag prefix or format.
21
+ - report the answer first, then the shortest reproducible explanation and any remaining uncertainty.
22
+
23
+ ## recovery
24
+
25
+ - missing artifact: verify the manifest, workspace mapping, archive contents, and target lifecycle once; report the exact missing input instead of searching the entire system repeatedly.
26
+ - insufficient tooling: use an available Kali alternative or write a focused parser/script rather than stopping at a tool name.
27
+ - remote/local mismatch: compare architecture, libc, protocol, timing, and paths; preserve the working local primitive before adapting it.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: digital-forensics
3
+ description: Evidence-preserving workflow for disk, filesystem, memory, archive, document, image, log, browser, and metadata forensics. Use when the task asks what happened, who acted, when an event occurred, or to recover deleted/hidden content from supplied artifacts; use packet-analysis for capture-centric evidence.
4
+ ---
5
+
6
+ # digital forensics
7
+
8
+ preserve provenance while reducing a large artifact set to evidence relevant to the question.
9
+
10
+ 1. inventory supplied artifacts, container/archive layers, sizes, timestamps, types, and hashes. work from copies or extracted views when an operation may alter metadata.
11
+ 2. translate the user question into evidence categories and a timeline: identities, execution, persistence, access, deletion, transfer, or hidden content.
12
+ 3. inspect high-signal metadata and indexes before broad carving or string searches. correlate independent sources rather than trusting one timestamp or parser.
13
+ 4. recover embedded, deleted, encoded, or renamed content with format-aware tools. verify recovered object types and relationships to the parent artifact.
14
+ 5. keep observed timestamps, normalized time zones, inferred ordering, and uncertain clock assumptions separate.
15
+
16
+ ## recovery
17
+
18
+ - parser failure: verify file signatures, truncation, compression, encryption, and tool support; use an alternate parser or inspect the structure directly.
19
+ - no result from a keyword search: broaden through metadata, encodings, alternate names, and timeline neighbors rather than repeating the same strings command.
20
+ - conflicting timestamps: identify each timestamp's semantics and corroborate with logs or adjacent events.
21
+
22
+ ## completion
23
+
24
+ answer the forensic question with a traceable artifact or correlation. absence from one parser or index is not proof that evidence never existed.
@@ -3,11 +3,11 @@ name: ffuf
3
3
  description: Wordlist strategy, soft-404 filtering, and failure recovery for directory/file fuzzing via the dir_enum tool (ffuf-backed). Use this whenever the user wants to enumerate hidden directories/files/endpoints on a web target, or mentions ffuf, gobuster, or fuzzing.
4
4
  ---
5
5
 
6
- # Directory Enumeration Playbook (dir_enum / ffuf)
6
+ # directory enumeration
7
7
 
8
- Fuzz the `FUZZ` keyword position in the URL against a wordlist.
8
+ use `dir_enum` for the common URL + wordlist case. use `shell_exec` with `ffuf` only when advanced matchers, filters, recursion, headers, methods, or multiple fuzz positions are required.
9
9
 
10
- Useful defaults:
10
+ useful defaults:
11
11
  - Filter noisy 200s from a catch-all page: match by size/words rather than status when the
12
12
  target returns 200 for everything (soft-404) — compare a known-bad path's response size first.
13
13
  - Start with a small, common wordlist (common.txt / raft-small) before escalating to a large one —
@@ -16,7 +16,7 @@ Useful defaults:
16
16
  rather than fuzzing extensions blindly from the start.
17
17
  - Recurse only into directories that returned a real (not soft-404) response.
18
18
 
19
- ## Failure recovery
19
+ ## failure recovery
20
20
  - All-200 responses with identical body size → soft-404 page; filter by size (`-fs <bytes>`) or
21
21
  by matching a known regex instead of status code.
22
22
  - No hits at all → verify the FUZZ position/URL is actually correct with one manual request first,
@@ -3,11 +3,11 @@ name: nmap
3
3
  description: Nmap CLI syntax, safe two-pass scan patterns, and failure recovery (filtered ports, timeouts) for the nmap_scan tool. Use this whenever the user asks to port-scan, enumerate services, or check what's open on a target, even before they mention nmap by name.
4
4
  ---
5
5
 
6
- # Nmap Playbook
6
+ # nmap
7
7
 
8
- Canonical syntax: `nmap [Scan Type(s)] [Options] {target specification}`
8
+ use `port_scan` for the standard `-Pn -sV -sC` scan. use `shell_exec` with `nmap` when the task needs explicit ports, scan types, scripts, timing, UDP, or a deliberate two-pass workflow.
9
9
 
10
- High-signal flags:
10
+ high-signal flags:
11
11
  - `-n` skip DNS resolution
12
12
  - `-Pn` skip host discovery when ICMP/ping is filtered (common in lab/VPN targets)
13
13
  - `-sS` SYN scan (needs privilege); `-sT` TCP connect scan (no raw-socket privilege needed)
@@ -18,11 +18,11 @@ High-signal flags:
18
18
  - `-T4` reasonable speed for lab targets
19
19
  - `--max-retries 1 --host-timeout 90s` bound worst-case runtime
20
20
 
21
- ## Two-pass workflow (preferred over one giant scan)
21
+ ## two-pass workflow
22
22
  1. Fast discovery pass: `nmap -n -Pn --top-ports 100 --open -T4 --max-retries 1 --host-timeout 90s <target>`
23
23
  2. Enrichment pass on discovered ports only: `nmap -n -Pn -sV -sC -p <comma_ports> --script-timeout 30s --host-timeout 3m <target>`
24
24
 
25
- ## Failure recovery
25
+ ## failure recovery
26
26
  - Host looks down unexpectedly → add `-Pn` (many lab/CTF targets block ICMP).
27
27
  - Scan stalls or times out → tighten `-p`/`--top-ports` and lower `--max-retries`.
28
28
  - A "filtered" result that you expect to be open may be transient (target-side rate limiting from
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: packet-analysis
3
+ description: Workflow for PCAP, PCAPNG, network-forensics, protocol-reassembly, malware traffic, and encrypted C2 challenges. Use when packet captures or recorded network streams are primary evidence; combine with binary-reversing when an extracted executable defines encoding or encryption.
4
+ ---
5
+
6
+ # packet analysis
7
+
8
+ turn the capture into a timeline of conversations, artifacts, and transformations.
9
+
10
+ 1. preserve the original capture and record its basic properties. inventory endpoints, protocols, ports, conversations, packet counts, durations, and obvious anomalies before applying narrow filters.
11
+ 2. build a timeline around the objective. identify which stream contains setup, authentication, delivery, command/control, exfiltration, or the final answer.
12
+ 3. reassemble application streams and extract transferred objects with protocol-aware tools when possible. verify extracted file type and hash before analysis.
13
+ 4. distinguish capture bytes from dissector interpretation. inspect raw stream bytes when framing, retransmission, encoding, or a custom protocol makes decoded fields misleading.
14
+ 5. if content is encoded or encrypted, locate keys and transformations in associated binaries, scripts, configuration, handshakes, or repeated structure; then implement a reproducible decoder.
15
+
16
+ ## recovery
17
+
18
+ - no useful high-level decode: follow individual streams, inspect raw payloads, and infer framing from direction and length.
19
+ - apparent missing data: check packet loss, truncation, out-of-order segments, retransmissions, alternate channels, and archive contents before concluding absence.
20
+ - too much traffic: rank conversations by timing, volume, protocol, and relationship to the known event instead of applying random display filters.
21
+
22
+ ## completion
23
+
24
+ support the answer with the relevant stream, extracted artifact, timeline, or decoder output. protocol labels and suspicious traffic alone are not the conclusion.