pwn 0.5.733 → 0.5.734

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +5 -0
  3. data/Gemfile +1 -1
  4. data/documentation/Agent-Tool-Registry.md +1 -1
  5. data/documentation/Configuration.md +26 -6
  6. data/documentation/General-PWN-Usage.md +1 -1
  7. data/documentation/Recon-Findings-API.md +4 -0
  8. data/documentation/pwn-REPL.md +21 -1
  9. data/etc/default_skills/pwn/ai/agent/swarm/SKILL.md +2 -0
  10. data/etc/default_skills/pwn/plugins/findings/SKILL.md +4 -0
  11. data/etc/default_skills/pwn/plugins/github/SKILL.md +1 -0
  12. data/etc/default_skills/pwn/plugins/repl/SKILL.md +27 -23
  13. data/etc/default_skills/pwn/plugins/repl/ai/SKILL.md +47 -0
  14. data/etc/default_skills/pwn/plugins/repl/asm/SKILL.md +47 -0
  15. data/etc/default_skills/pwn/plugins/repl/irc/SKILL.md +47 -0
  16. data/etc/default_skills/pwn/plugins/repl/mesh/SKILL.md +47 -0
  17. data/etc/default_skills/pwn/plugins/repl/vault/SKILL.md +47 -0
  18. data/lib/pwn/ai/agent/loop.rb +8 -1
  19. data/lib/pwn/ai/agent/swarm.rb +64 -0
  20. data/lib/pwn/ai/agent/tools/finding_record.rb +19 -2
  21. data/lib/pwn/ai/agent/tools/swarm.rb +19 -0
  22. data/lib/pwn/config.rb +15 -2
  23. data/lib/pwn/plugins/findings.rb +171 -0
  24. data/lib/pwn/plugins/github.rb +44 -0
  25. data/lib/pwn/plugins/repl/ai.rb +1056 -0
  26. data/lib/pwn/plugins/repl/asm.rb +55 -0
  27. data/lib/pwn/plugins/repl/irc.rb +153 -0
  28. data/lib/pwn/plugins/repl/mesh.rb +1779 -0
  29. data/lib/pwn/plugins/repl/vault.rb +97 -0
  30. data/lib/pwn/plugins/repl.rb +94 -1419
  31. data/lib/pwn/version.rb +1 -1
  32. data/spec/lib/pwn/ai/agent/loop_issue_work_spec.rb +31 -0
  33. data/spec/lib/pwn/ai/agent/swarm_roster_spec.rb +29 -0
  34. data/spec/lib/pwn/ai/agent/tools/finding_record_spec.rb +5 -0
  35. data/spec/lib/pwn/config_spec.rb +14 -1
  36. data/spec/lib/pwn/plugins/findings_structured_spec.rb +33 -0
  37. data/spec/lib/pwn/plugins/github_spec.rb +21 -0
  38. data/spec/lib/pwn/plugins/repl/ai_spec.rb +21 -0
  39. data/spec/lib/pwn/plugins/repl/asm_spec.rb +21 -0
  40. data/spec/lib/pwn/plugins/repl/irc_spec.rb +21 -0
  41. data/spec/lib/pwn/plugins/repl/mesh_spec.rb +21 -0
  42. data/spec/lib/pwn/plugins/repl/vault_spec.rb +21 -0
  43. data/spec/lib/pwn/plugins/repl_mesh_channel_route_spec.rb +247 -0
  44. data/spec/lib/pwn/plugins/repl_mesh_console_spec.rb +130 -0
  45. data/spec/lib/pwn/plugins/repl_mesh_dispatch_spec.rb +113 -0
  46. data/spec/lib/pwn/plugins/repl_mesh_dm_spec.rb +72 -0
  47. data/spec/lib/pwn/plugins/repl_mesh_rx_paint_spec.rb +71 -0
  48. data/spec/lib/pwn/plugins/repl_pwn_vault_spec.rb +5 -9
  49. data/spec/lib/pwn/plugins/repl_spec.rb +599 -3
  50. data/third_party/pwn_rdoc.jsonl +62 -27
  51. metadata +25 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b64f724a6d7fa420f4a252521269532cf4aa40700b7153a605595909fe0834e7
4
- data.tar.gz: dd488cc0a1f6a824802cbeceb95a253d1c7de219baa15ae0a2773f9a9946454d
3
+ metadata.gz: 35f85751c76226260916fb5b417180f8c10a1fe53eb8913295895aec83da8eaa
4
+ data.tar.gz: 7808ea07ce71dd2576b168acf9cb1c429c0f9f4dc098570c13ffa81d416e6ffc
5
5
  SHA512:
6
- metadata.gz: 76ba4fd11ead7ac047accf43f8aebaf32a34b7848f241e6966adb77edd736415a797a899161ddf8dba3c81911d1fa9b1708956c7524b008586af7be3c7c591b6
7
- data.tar.gz: 49a67ddd31148ca211806ff3389484af19227efc5ceb789d216218160563b3b81a79e4f9a6fd6a93f6e3aa45e7ad1d68812dbb45f09d7b93a2fec6dce3e12fc1
6
+ metadata.gz: 933f35d3a0d28a1dfdb87eb826874e88b150738b333bfb2518a9366f61cd72b24c5fb7e64fe30e73a92086320d6d3df8bfb88b3d8047f9565eab9dd995780087
7
+ data.tar.gz: 5384de39a95c61302cd37f6a6bcdfbcd54f1f9b047a7588c990afef5b89a8e572007dab3abab3e35547d8dc35c6a43d4f2ec72997122e70ea86ea0a3cb21fc7d
data/.rubocop_todo.yml CHANGED
@@ -18,6 +18,11 @@ Layout/TrailingWhitespace:
18
18
  Lint/NestedMethodDefinition:
19
19
  Exclude:
20
20
  - 'lib/pwn/plugins/repl.rb'
21
+ - 'lib/pwn/plugins/repl/ai.rb'
22
+ - 'lib/pwn/plugins/repl/asm.rb'
23
+ - 'lib/pwn/plugins/repl/irc.rb'
24
+ - 'lib/pwn/plugins/repl/mesh.rb'
25
+ - 'lib/pwn/plugins/repl/vault.rb'
21
26
 
22
27
  # Offense count: 8
23
28
  # This cop supports safe autocorrection (--autocorrect).
data/Gemfile CHANGED
@@ -45,7 +45,7 @@ gem 'js-beautify', '0.1.8'
45
45
  gem 'json', '>=2.13.2'
46
46
  gem 'jsonpath', '1.1.5'
47
47
  gem 'json_schemer', '2.5.0'
48
- gem 'jwt', '3.2.0'
48
+ gem 'jwt', '3.3.0'
49
49
  gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
@@ -24,7 +24,7 @@ toolsets; the JSON-Schema for each tool is what the model actually sees.
24
24
  | `policy` | **`policy_stats`** · **`policy_evaluate`** · **`policy_recommend`** | `PWN::AI::Agent::Policy` → `~/.pwn/policy.json` + `~/.pwn/policy_traj.jsonl` |
25
25
  | `extrospection` | `extro_snapshot` · `extro_drift` · `extro_observe` · `extro_observations` · `extro_intel` · **`extro_watch`** · **`extro_verify`** · **`extro_rf_tune`** · **`extro_osint`** · **`extro_serial`** · **`extro_telecomm`** · **`extro_packet`** · **`extro_vision`** · **`extro_voice`** · `extro_correlate` · `extro_stats` · `extro_reset` · `extro_auto_toggle` | `PWN::AI::Agent::Extrospection` (+ Serial/Packet/OCR/Voice/BareSIP/TransparentBrowser/GQRX) → `~/.pwn/extrospection.json` |
26
26
  | `cron` | `cron_list` · `cron_create` · `cron_run` · `cron_enable` · `cron_disable` · `cron_remove` | `PWN::Cron` → `~/.pwn/cron/jobs.yml` |
27
- | `swarm` | `agent_list` · `agent_spawn` · `agent_ask` · `agent_debate` · `agent_broadcast` · `swarm_bus` · `swarm_list` | `PWN::AI::Agent::Swarm` → `~/.pwn/agents.yml` + `~/.pwn/swarm/` |
27
+ | `swarm` | `agent_list` · `agent_spawn` · `agent_ask` · `agent_debate` · `agent_broadcast` · `swarm_bus` · `swarm_list` · `agent_roster` | `PWN::AI::Agent::Swarm` → `~/.pwn/agents.yml` + `~/.pwn/swarm/` |
28
28
  | `manifest` | `host_os_type` | YAML tool declarations in `lib/pwn/ai/tools/*.yaml` via `PWN::AI::Agent::Manifest` |
29
29
 
30
30
  The `learning` toolset is deliberately large: **Mistakes** (negative feedback),
@@ -203,18 +203,29 @@ plugins:
203
203
  token: ... # Jira Personal Access Token for PWN::Plugins::JiraDataCenter. Redacted.
204
204
  meshtastic:
205
205
  admin_key: ... # Public key authorised to send admin messages to mesh nodes via `pwn-mesh`.
206
+ transport: auto # auto | serial | bluetooth | tcp | mqtt — `auto` probes serial → bluetooth → tcp → mqtt; `/transport` pins one.
207
+ dispatch_to_pwn_ai: false # `/toggle-dispatch-to-pwn-ai` — encrypted + ai_whitelist + `@ai` prefix → pwn-ai via Env ai.active; DMs if the channel is on ai_whitelist.
208
+ ai_whitelist: [] # Channel names pwn-ai may answer on (e.g. LongFast). Empty = none.
206
209
  serial:
207
- port: /dev/ttyUSB0 # Serial device path for a locally-attached Meshtastic node.
210
+ port: /dev/ttyACM0 # Serial device path (`Meshtastic::Serial.connect` block_dev).
208
211
  baud: 115200 # Serial baud rate.
209
- bits: 8 # Serial data bits.
210
- stop: 1 # Serial stop bits.
212
+ bits: 8 # Serial data bits (`data_bits`).
213
+ stop: 1 # Serial stop bits (`stop_bits`).
211
214
  parity: none # Serial parity (none | even | odd).
215
+ bluetooth:
216
+ address: AA:BB:CC:DD:EE:FF # BLE MAC/address for `Meshtastic::Bluetooth.connect`.
217
+ tcp:
218
+ host: 127.0.0.1 # IP/hostname for `Meshtastic::TCP.connect`.
219
+ port: 4403 # TCP port for a Meshtastic node (default 4403).
212
220
  mqtt:
213
221
  host: mqtt.meshtastic.org # MQTT broker hostname for Meshtastic-over-MQTT.
214
222
  port: 1883 # MQTT broker port (8883 for TLS).
215
223
  tls: false # Use TLS to the MQTT broker.
216
224
  user: meshdev # MQTT username (public Meshtastic broker default shown).
217
225
  pass: large4cats # MQTT password (public Meshtastic broker default shown). Redacted.
226
+ client_id: # Optional MQTT client id (`Meshtastic::MQTT.connect`).
227
+ keep_alive: 60 # Optional MQTT keep-alive seconds.
228
+ ack_timeout: 5 # Optional MQTT ack timeout seconds.
218
229
  channel:
219
230
  active: LongFast # Which named channel block below `pwn-mesh` uses for TX/RX.
220
231
  LongFast: # Channel definition - name is arbitrary, referenced by `active:` above.
@@ -367,16 +378,25 @@ PWN::Config.refresh_env
367
378
  | `plugins.jira_data_center.base_uri` | String | - | `PWN::Plugins::JiraDataCenter` | Jira DC REST base (e.g. `https://jira.company.com/rest/api/latest`). |
368
379
  | `plugins.jira_data_center.token` | String | - | `PWN::Plugins::JiraDataCenter` | Jira Personal Access Token. |
369
380
  | `plugins.meshtastic.admin_key` | String | - | `PWN::Plugins::REPL` (`pwn-mesh`) | Public key authorised to send admin messages to mesh nodes. |
370
- | `plugins.meshtastic.serial.port` | String | `/dev/ttyUSB0` | `pwn-mesh` (serial) | Serial device path for a locally-attached Meshtastic node. |
381
+ | `plugins.meshtastic.transport` | String | `auto` | `pwn-mesh` | `auto` probes serial bluetooth tcp → mqtt. Pin with `/transport <kind>`. |
382
+ | `plugins.meshtastic.dispatch_to_pwn_ai` | Boolean | `false` | `pwn-mesh` `/toggle-dispatch-to-pwn-ai` | When true, pwn-ai may answer TEXT_MESSAGE_APP on a securely encrypted channel that is listed in `ai_whitelist` and whose text begins with `@ai`. DMs are allowed when they reside on a whitelisted channel. |
383
+ | `plugins.meshtastic.ai_whitelist` | Array | `[]` | `pwn-mesh` dispatch | Channel names pwn-ai is allowed to answer on. Empty means none. |
384
+ | `plugins.meshtastic.serial.port` | String | `/dev/ttyACM0` | `pwn-mesh` → `Meshtastic::Serial.connect` `block_dev` | Serial device path. |
371
385
  | `plugins.meshtastic.serial.baud` | Integer | `115200` | `pwn-mesh` (serial) | Serial baud rate. |
372
- | `plugins.meshtastic.serial.bits` | Integer | `8` | `pwn-mesh` (serial) | Serial data bits. |
373
- | `plugins.meshtastic.serial.stop` | Integer | `1` | `pwn-mesh` (serial) | Serial stop bits. |
386
+ | `plugins.meshtastic.serial.bits` | Integer | `8` | `pwn-mesh` (serial) `data_bits` | Serial data bits. |
387
+ | `plugins.meshtastic.serial.stop` | Integer | `1` | `pwn-mesh` (serial) `stop_bits` | Serial stop bits. |
374
388
  | `plugins.meshtastic.serial.parity` | Symbol | `:none` | `pwn-mesh` (serial) | Serial parity. |
389
+ | `plugins.meshtastic.bluetooth.address` | String | - | `pwn-mesh` → `Meshtastic::Bluetooth.connect` | BLE MAC/address of the node. |
390
+ | `plugins.meshtastic.tcp.host` | String | `127.0.0.1` | `pwn-mesh` → `Meshtastic::TCP.connect` | TCP host of the node. |
391
+ | `plugins.meshtastic.tcp.port` | Integer | `4403` | `pwn-mesh` (tcp) | TCP port of the node. |
375
392
  | `plugins.meshtastic.mqtt.host` | String | `mqtt.meshtastic.org` | `pwn-mesh` → `Meshtastic::MQTT.connect` | MQTT broker hostname. |
376
393
  | `plugins.meshtastic.mqtt.port` | Integer | `1883` | `pwn-mesh` | MQTT broker port. |
377
394
  | `plugins.meshtastic.mqtt.tls` | Boolean | `false` | `pwn-mesh` | Use TLS to the MQTT broker. |
378
395
  | `plugins.meshtastic.mqtt.user` | String | `meshdev` | `pwn-mesh` | MQTT username. |
379
396
  | `plugins.meshtastic.mqtt.pass` | String | `large4cats` | `pwn-mesh` | MQTT password. |
397
+ | `plugins.meshtastic.mqtt.client_id` | String | - | `pwn-mesh` | Optional MQTT client id. |
398
+ | `plugins.meshtastic.mqtt.keep_alive` | Integer | `60` | `pwn-mesh` | Optional MQTT keep-alive seconds. |
399
+ | `plugins.meshtastic.mqtt.ack_timeout` | Integer | `5` | `pwn-mesh` | Optional MQTT ack timeout seconds. |
380
400
  | `plugins.meshtastic.channel.active` | String | `LongFast` | `pwn-mesh` | Which named channel block below is used for TX/RX. |
381
401
  | `plugins.meshtastic.channel.<NAME>.psk` | String (b64) | `AQ==` | `pwn-mesh` | Channel pre-shared key. |
382
402
  | `plugins.meshtastic.channel.<NAME>.region` | String | - | `pwn-mesh` | LoRa region tag (e.g. `US/UT`). |
@@ -39,7 +39,7 @@ history # what you've typed → copy into a driver
39
39
  |---|---|
40
40
  | `pwn-ai` | Enter the agent TUI (SHIFT+ENTER = newline, ENTER = submit) |
41
41
  | `pwn-asm` | Multi-line assembly ↔ opcode workbench |
42
- | `pwn-mesh` | Meshtastic serial / MQTT client (Ruby ≥ 4.0) |
42
+ | `pwn-mesh` | Meshtastic serial / bluetooth / TCP / MQTT client (Ruby ≥ 4.0) |
43
43
  | `pwn-vault` | Decrypt → edit `~/.pwn/pwn.yaml` in `$EDITOR` → re-encrypt |
44
44
  | `pwn-ai-memory` | Inspect / edit `~/.pwn/memory.json` |
45
45
  | `pwn-ai-sessions` | List / view / delete transcripts |
@@ -24,6 +24,10 @@ Use `affected_asset: asset[:id]` and `evidence_paths: asset[:evidence_paths]` in
24
24
 
25
25
  Legacy `Findings.record`, query/report, chain, render, and SARIF output remain callable; legacy record is not the strict API. Agent record and chain operations are strict. Existing query/export tool operations remain available. Accepted structured rows mark `verification_status: not_executed`; evidence hashes prove captured bytes, not exploit execution. Severity is derived from the validated score. No automatic escalation for linked findings.
26
26
 
27
+ `Findings.verify` / `finding_record op=verify` attests a working PoC from HTTP request/response files or a script execution log. The impact marker must appear in that evidence or the row stays `failed`. `Findings.retest` replays the same path after a fix (`still_open` vs `fixed`). `Findings.chain_impact` may raise combined severity only when a combined-impact file names every finding id. Issue work is unfinished while recorded findings remain `not_executed` (`issue_work_unverified`).
28
+
29
+ `PWN::AI::Agent::Swarm.ensure_specialists` / `agent_roster` writes recon, authz, injection, xss, and business_logic personas. SARIF export is `Findings.render`; `PWN::Plugins::Github.open_fix_pr` opens a remediation PR (tests stub the GitHub API).
30
+
27
31
  Markdown/HTML/JSON report payloads compose connected explicit references into attack-chain sections, preserving all finding fields. Combined severity is maximum recorded constituent severity with a rationale explicitly disclaiming escalation and combined exploitability. This is not a computed CVSS chain score.
28
32
 
29
33
  ## Verification boundaries
@@ -21,7 +21,7 @@ commands.
21
21
  | `pwn-ai` | `Agent::Loop` | Enter the AI agent TUI |
22
22
  | `pwn-vault` | `PWN::Plugins::Vault` | Decrypt, edit `~/.pwn/pwn.yaml`, re-encrypt. Stays in the editor until `PWN::Config` accepts the file. |
23
23
  | `pwn-asm` | `Plugins::Assembly` | Multiline asm ↔ opcodes workbench |
24
- | `pwn-mesh` | `Meshtastic` gem | Meshtastic serial / MQTT client (Ruby ≥ 4.0; installed via `pwn setup`) |
24
+ | `pwn-mesh` | `Meshtastic` gem | Meshtastic serial / bluetooth / TCP / MQTT client (Ruby ≥ 4.0; `plugins.meshtastic.transport`). Inside the mesh TUI, leading-slash menus (`/channel`, `/transport`, `/device`, `/status`, `/help`, `/back`) configure the session locally and are not sent as mesh text. TAB completes those commands. |
25
25
  | `pwn-ai-memory` | `PWN::Memory` | View/edit persistent memory |
26
26
  | `pwn-ai-sessions` | `PWN::Sessions` | List/view/delete transcripts |
27
27
  | `pwn-ai-cron` | `PWN::Cron` | List/run/toggle scheduled jobs |
@@ -34,6 +34,26 @@ commands.
34
34
  | `toggle-pager` | Pry | Page long output on/off |
35
35
  | `back` | - | Leave `pwn-ai` / `pwn-asm` / `pwn-mesh` sub-REPL |
36
36
 
37
+ ## pwn-mesh slash commands
38
+
39
+ Typed at the mesh TX prompt (leading `/`). `/` or `/menu` opens a boxed curses menu (arrows/j/k, Enter, Esc). `/channel`, `/transport`, and `/device` without an argument open the same kind of list. Explicit `/channel LongFast` still jumps without a menu. Commands are local — they are not published as Meshtastic text. Changes update `PWN::Env[:plugins][:meshtastic]` and persist to `~/.pwn/pwn.yaml` when the vault decryptor is available; a live session reconnects so RX follows the new channel or radio.
40
+
41
+ | Command | Purpose |
42
+ |---|---|
43
+ | `/help` | List mesh slash commands |
44
+ | `/status` | Show active transport, device, and channel |
45
+ | `/channel list` | List named `plugins.meshtastic.channel` blocks (`*` = active) |
46
+ | `/channel <name>` | Switch the active channel (e.g. `LongFast`) |
47
+ | `/transport list` | List `auto` / `serial` / `bluetooth` / `tcp` / `mqtt` |
48
+ | `/transport <kind>` | Pin a connection type (`auto` re-enables serial → bluetooth → tcp → mqtt probe) |
49
+ | `/device list` | List serial ports, BLE Meshtastic addresses, or the configured TCP/MQTT host |
50
+ | `/device <id>` | Switch radio for the active transport (`/dev/ttyACM0`, BLE MAC, `host:port`) |
51
+ | `/msg [!nodeid|channel] <text>` | Send a DM to `!nodeid`, or a broadcast on a named channel. Omit the target to reuse the last incoming DM sender or channel. A compose line without `/msg` goes to the active channel. Also `@!aabbccdd text` on a normal send. |
52
+ | `/toggle-dispatch-to-pwn-ai` | Off by default. When on, pwn-ai answers only if the source channel is encrypted, listed in `plugins.meshtastic.ai_whitelist`, and the text begins with `@ai`. DMs are allowed when they reside on a whitelisted channel. Uses `PWN::Env[:ai][:active]`. |
53
+ | `/back` | Leave pwn-mesh (same as `back` / CTRL+D) |
54
+
55
+ Typing `/` shows matching commands in the TX pane (ncurses hides Reline's dropdown). TAB still completes `/…` commands, channel names, transports, and device ids.
56
+
37
57
  ## Multi-line input
38
58
 
39
59
  `pwn-ai` and `pwn-asm` use a custom `PWNMultiLineInput` reader. Plain
@@ -48,6 +48,8 @@ PWN::AI::Agent::Swarm.personas(opts)
48
48
  - `facts_prompt`
49
49
  - `claim`
50
50
  - `pack_specialist`
51
+ - `specialist_roles`
52
+ - `ensure_specialists`
51
53
  - `child_inbox`
52
54
  - `child_honesty`
53
55
  - `honesty_unmet`
@@ -41,6 +41,10 @@ PWN::Plugins::Findings.required_bins(opts)
41
41
  - `query`
42
42
  - `chain`
43
43
  - `chain_score`
44
+ - `verify`
45
+ - `retest`
46
+ - `chain_impact`
47
+ - `issue_work_gaps`
44
48
  - `render`
45
49
  - `authors`
46
50
  - `help`
@@ -37,6 +37,7 @@ PWN::Plugins::Github.download_all_gists(opts)
37
37
  - `workflow_runs`
38
38
  - `workflow_run_jobs`
39
39
  - `job_log`
40
+ - `open_fix_pr`
40
41
  - `api`
41
42
  - `gh`
42
43
  - `authors`
@@ -38,38 +38,42 @@ PWN::Plugins::REPL.ready_tty(opts)
38
38
  - `refresh_ps1_proc`
39
39
  - `add_commands`
40
40
  - `add_hooks`
41
- - `pwn_ai_complete_kind`
41
+ - `leave_special_mode`
42
+ - `enable_autocomplete`
43
+ - `start`
44
+ - `authors`
45
+ - `help`
46
+ - `install_pwn_ai_completer!`
47
+ - `install_pwn_mesh_completer!`
48
+ - `leave_special_mode!`
49
+ - `mesh_menu_pick`
50
+ - `mesh_reset_input!`
51
+ - `persist_ai_selection`
52
+ - `persist_mesh_env`
53
+ - `pwn_ai_activation_session`
42
54
  - `pwn_ai_complete`
43
55
  - `pwn_ai_complete_command`
56
+ - `pwn_ai_complete_kind`
44
57
  - `pwn_ai_complete_path`
45
58
  - `pwn_ai_complete_ruby`
46
- - `install_pwn_ai_completer`
47
- - `restore_pwn_ai_completer`
48
- - `leave_special_mode`
49
- - `pwn_ai_activation_session`
50
- - `pwn_ai_profile_command`
51
- - `pwn_ai_memory_command`
52
- - `pwn_ai_dispatch_slash`
59
+ - `pwn_ai_dispatch_slash!`
60
+ - `pwn_ai_engine_model`
53
61
  - `pwn_ai_engines`
54
- - `pwn_ai_provider_class`
55
- - `pwn_ai_model_ids`
56
62
  - `pwn_ai_list_llms`
57
- - `pwn_ai_engine_model`
58
- - `pwn_ai_run_model`
59
- - `persist_ai_selection`
63
+ - `pwn_ai_memory_command`
64
+ - `pwn_ai_model_ids`
65
+ - `pwn_ai_profile_command`
66
+ - `pwn_ai_provider_class`
60
67
  - `pwn_ai_run_cron`
61
- - `pwn_ai_run_sessions`
62
- - `pwn_ai_run_memory`
63
68
  - `pwn_ai_run_learning`
64
- - `pwn_ai_run_skills`
65
69
  - `pwn_ai_run_mcp`
66
- - `enable_autocomplete`
67
- - `start`
68
- - `authors`
69
- - `help`
70
- - `install_pwn_ai_completer!`
71
- - `leave_special_mode!`
72
- - `pwn_ai_dispatch_slash!`
70
+ - `pwn_ai_run_memory`
71
+ - `pwn_ai_run_model`
72
+ - `pwn_ai_run_sessions`
73
+ - `pwn_ai_run_skills`
74
+ - `pwn_mesh_complete`
75
+ - `pwn_mesh_dispatch_slash!`
76
+ - `pwn_mesh_menu_rows`
73
77
  - `ready_tty!`
74
78
  - `restore_pwn_ai_completer!`
75
79
 
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-plugins-repl-ai
3
+ description: Drive PWN::Plugins::REPL::AI from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Plugins::REPL::AI
10
+ source: pwn/plugins/repl/ai.rb
11
+ ---
12
+
13
+ # PWN::Plugins::REPL::AI
14
+
15
+ pwn-ai REPL mode.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Plugins::REPL::AI` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/plugins/repl/ai.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Plugins::REPL::AI.help
31
+ PWN::Plugins::REPL::AI.add_commands(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `add_commands`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/plugins/repl/ai.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Plugins::REPL::AI.respond_to?(:add_commands)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-plugins-repl-asm
3
+ description: Drive PWN::Plugins::REPL::ASM from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Plugins::REPL::ASM
10
+ source: pwn/plugins/repl/asm.rb
11
+ ---
12
+
13
+ # PWN::Plugins::REPL::ASM
14
+
15
+ pwn-asm REPL mode.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Plugins::REPL::ASM` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/plugins/repl/asm.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Plugins::REPL::ASM.help
31
+ PWN::Plugins::REPL::ASM.add_commands(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `add_commands`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/plugins/repl/asm.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Plugins::REPL::ASM.respond_to?(:add_commands)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-plugins-repl-irc
3
+ description: Drive PWN::Plugins::REPL::IRC from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Plugins::REPL::IRC
10
+ source: pwn/plugins/repl/irc.rb
11
+ ---
12
+
13
+ # PWN::Plugins::REPL::IRC
14
+
15
+ pwn-irc REPL mode.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Plugins::REPL::IRC` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/plugins/repl/irc.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Plugins::REPL::IRC.help
31
+ PWN::Plugins::REPL::IRC.add_commands(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `add_commands`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/plugins/repl/irc.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Plugins::REPL::IRC.respond_to?(:add_commands)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-plugins-repl-mesh
3
+ description: Drive PWN::Plugins::REPL::Mesh from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Plugins::REPL::Mesh
10
+ source: pwn/plugins/repl/mesh.rb
11
+ ---
12
+
13
+ # PWN::Plugins::REPL::Mesh
14
+
15
+ pwn-mesh REPL mode.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Plugins::REPL::Mesh` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/plugins/repl/mesh.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Plugins::REPL::Mesh.help
31
+ PWN::Plugins::REPL::Mesh.add_commands(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `add_commands`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/plugins/repl/mesh.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Plugins::REPL::Mesh.respond_to?(:add_commands)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-plugins-repl-vault
3
+ description: Drive PWN::Plugins::REPL::Vault from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Plugins::REPL::Vault
10
+ source: pwn/plugins/repl/vault.rb
11
+ ---
12
+
13
+ # PWN::Plugins::REPL::Vault
14
+
15
+ pwn-vault REPL mode.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Plugins::REPL::Vault` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/plugins/repl/vault.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Plugins::REPL::Vault.help
31
+ PWN::Plugins::REPL::Vault.add_commands(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `add_commands`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/plugins/repl/vault.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Plugins::REPL::Vault.respond_to?(:add_commands)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -705,6 +705,11 @@ module PWN
705
705
  artifact[:unmet].each { |row| unmet << "#{row[:criterion]}:#{row[:detail]}" }
706
706
  trace_files = session_files(messages: opts[:messages])
707
707
  unmet << 'issue_work_proofs' if contract[:issue_work] && Array(contract[:proofs]).empty? && files.empty? && trace_files.empty?
708
+ if contract[:issue_work] && defined?(PWN::Plugins::Findings)
709
+ gaps = PWN::Plugins::Findings.issue_work_gaps(session_id: Thread.current[:pwn_session_id].to_s)
710
+ unmet << 'issue_work_unverified' if Array(gaps[:recorded]).any? && Array(gaps[:reproduced]).empty?
711
+ unmet << 'issue_work_unchained' if gaps[:unchained]
712
+ end
708
713
  unmet << 'skills' if declared_skills_missing?(skills: contract[:skills], request: request)
709
714
  blob = evidence_blob(messages: opts[:messages], files: files + trace_files)
710
715
  asked_hosts = Array(contract[:hosts]).select { |host| request.downcase.include?(host.to_s.downcase) }
@@ -2787,7 +2792,8 @@ module PWN
2787
2792
  bound = operator_bound_refusal(from: opts[:from] || opts[:account])
2788
2793
  return bound if bound
2789
2794
 
2790
- engine = active_engine
2795
+ engine = opts[:engine]
2796
+ engine = engine.to_s.empty? ? active_engine : engine.to_s.downcase.to_sym
2791
2797
  local = local_engine?(engine: engine)
2792
2798
 
2793
2799
  # Cheap intent/kind FIRST - before PromptBuilder / Registry / TaskSummarizer
@@ -3366,6 +3372,7 @@ module PWN
3366
3372
  account: 'optional - operator account id to bind',
3367
3373
  force_tools: 'optional - force tools value consumed by #run',
3368
3374
  nested: 'optional - true for Swarm/child Loop.run (skip RN footer)',
3375
+ engine: 'optional - provider name; defaults to PWN::Env ai.active',
3369
3376
  core_only: 'optional - restrict to CORE_TOOLS when true',
3370
3377
  trace: 'optional - enable TracePoint debug for this run',
3371
3378
  debug_tee: 'optional - IO to tee debug logs'
@@ -34,6 +34,33 @@ module PWN
34
34
  DEFAULT_TAIL = 12
35
35
  DEFAULT_TOOLSET = %w[terminal pwn memory skills sessions learning
36
36
  metrics extrospection].freeze
37
+ SPECIALIST_ROLES = {
38
+ recon: {
39
+ role: 'Map the operator-named target. Record assets and working probes. Do not invent hosts.',
40
+ skills: %w[osint penetration-testing],
41
+ toolsets: %w[pwn terminal extrospection http]
42
+ },
43
+ authz: {
44
+ role: 'Test authorization and IDOR on discovered assets. Reproduce with request/response PoCs.',
45
+ skills: %w[web-application-penetration-testing],
46
+ toolsets: %w[pwn terminal http]
47
+ },
48
+ injection: {
49
+ role: 'Test injection on discovered inputs. Attach a working PoC, not a scanner signature.',
50
+ skills: %w[web-application-penetration-testing deep-exploitation],
51
+ toolsets: %w[pwn terminal http]
52
+ },
53
+ xss: {
54
+ role: 'Test XSS and CSRF in a real browser session. Evidence is a reproduced payload, not a pattern hit.',
55
+ skills: %w[web-application-penetration-testing],
56
+ toolsets: %w[pwn terminal http]
57
+ },
58
+ business_logic: {
59
+ role: 'Chain authorization, injection, and workflow flaws into one evidenced impact path.',
60
+ skills: %w[web-application-penetration-testing bug-bounty-hunting],
61
+ toolsets: %w[pwn terminal http]
62
+ }
63
+ }.freeze
37
64
 
38
65
  # ------------------------------------------------------------------
39
66
  # Persona registry (~/.pwn/agents.yml)
@@ -524,6 +551,33 @@ module PWN
524
551
  { name: name, skills: skills, toolsets: toolsets }
525
552
  end
526
553
 
554
+ # Catalog of lead-spawned offensive specialists (recon through business logic).
555
+
556
+ public_class_method def self.specialist_roles(opts = {})
557
+ name = opts[:name].to_s
558
+ return SPECIALIST_ROLES.fetch(name.to_sym) unless name.empty?
559
+
560
+ SPECIALIST_ROLES
561
+ end
562
+
563
+ # Write ephemeral specialist personas for a swarm without running Loop.
564
+
565
+ public_class_method def self.ensure_specialists(opts = {})
566
+ sid = opts[:swarm_id].to_s
567
+ raise ArgumentError, 'swarm_id is required' if sid.empty?
568
+
569
+ SPECIALIST_ROLES.map do |name, spec|
570
+ spawn(
571
+ name: name.to_s,
572
+ role: spec[:role],
573
+ skills: spec[:skills],
574
+ toolsets: spec[:toolsets],
575
+ swarm_id: sid,
576
+ ephemeral: true
577
+ )
578
+ end
579
+ end
580
+
527
581
  public_class_method def self.child_inbox(opts = {})
528
582
  sid = opts[:session_id].to_s
529
583
  findings = if defined?(PWN::Plugins::Findings)
@@ -737,6 +791,16 @@ module PWN
737
791
  orchestrator: 'optional - true to keep the swarm toolset'
738
792
  )
739
793
 
794
+ # Catalog of lead-spawned specialists (recon, authz, injection, xss, business_logic).
795
+ #{self}.specialist_roles(
796
+ name: 'optional - one role name; omit to return the full catalog'
797
+ )
798
+
799
+ # Write ephemeral specialist personas for a swarm without running Loop.
800
+ #{self}.ensure_specialists(
801
+ swarm_id: 'required - swarm id from #create'
802
+ )
803
+
740
804
  # World-object inbox for a child session (finding ids, artifact shas).
741
805
  #{self}.child_inbox(
742
806
  session_id: 'optional - child PWN::Sessions id',
@@ -28,9 +28,18 @@ PWN::AI::Agent::Registry.register(
28
28
  poc: { type: 'string' },
29
29
  poc_artifacts: { type: 'array', items: { type: 'string' } },
30
30
  session_id: { type: 'string' },
31
- op: { type: 'string' },
31
+ op: { type: 'string', enum: %w[query export chain record verify retest chain_impact gaps] },
32
32
  parent_id: { type: 'string' },
33
- engagement_id: { type: 'string' }
33
+ engagement_id: { type: 'string' },
34
+ kind: { type: 'string', enum: %w[http script] },
35
+ impact: { type: 'string' },
36
+ request_path: { type: 'string' },
37
+ response_path: { type: 'string' },
38
+ execution_log: { type: 'string' },
39
+ ids: { type: 'array', items: { type: 'string' } },
40
+ combined_impact_path: { type: 'string' },
41
+ escalate: { type: 'boolean' },
42
+ combined_severity: { type: 'string' }
34
43
  },
35
44
  required: %w[],
36
45
  anyOf: [
@@ -52,6 +61,14 @@ PWN::AI::Agent::Registry.register(
52
61
  PWN::Plugins::Findings.record_structured(args.merge(attack_chain_refs: (Array(args[:attack_chain_refs]) + [parent]).uniq))
53
62
  when 'export'
54
63
  PWN::Plugins::Findings.render(args)
64
+ when 'verify'
65
+ PWN::Plugins::Findings.verify(args)
66
+ when 'retest'
67
+ PWN::Plugins::Findings.retest(args)
68
+ when 'chain_impact'
69
+ PWN::Plugins::Findings.chain_impact(args)
70
+ when 'gaps'
71
+ PWN::Plugins::Findings.issue_work_gaps(args)
55
72
  when 'record'
56
73
  PWN::Plugins::Findings.record_structured(args)
57
74
  else