pwn 0.5.724 → 0.5.725

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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -3
  3. data/bin/pwn-ai +6 -0
  4. data/bin/pwn-capd +5 -0
  5. data/documentation/AI-Integration.md +3 -2
  6. data/documentation/Agent-Tool-Registry.md +3 -1
  7. data/documentation/Capability-Broker-Sandbox.md +97 -0
  8. data/documentation/Proxy-Skills-Hygiene.md +102 -0
  9. data/documentation/Recon-Findings-API.md +31 -0
  10. data/documentation/Swarm.md +38 -3
  11. data/etc/default_skills/pwn/ai/agent/engagement_memory/SKILL.md +46 -0
  12. data/etc/default_skills/pwn/ai/agent/manifest/SKILL.md +49 -0
  13. data/etc/default_skills/pwn/ai/agent/profiles/SKILL.md +46 -0
  14. data/etc/default_skills/pwn/ai/agent/request_runtime/SKILL.md +46 -0
  15. data/etc/default_skills/pwn/ai/agent/skill_consolidation/SKILL.md +48 -0
  16. data/etc/default_skills/pwn/ai/agent/tool_guard/SKILL.md +3 -2
  17. data/etc/default_skills/pwn/ai/agent/tools/http_replay/SKILL.md +45 -0
  18. data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +45 -0
  19. data/etc/default_skills/pwn/ai/agent/tools/sandbox/SKILL.md +45 -0
  20. data/etc/default_skills/pwn/ai/agent/tools/skills_consolidate/SKILL.md +45 -0
  21. data/etc/default_skills/pwn/ai/agent/turn_finalizer/SKILL.md +3 -0
  22. data/etc/default_skills/pwn/ai/cli/SKILL.md +48 -0
  23. data/etc/default_skills/pwn/ai/context/SKILL.md +2 -0
  24. data/etc/default_skills/pwn/ai/context_ingestion/SKILL.md +56 -0
  25. data/etc/default_skills/pwn/ai/context_ingestion/references/urls.md +3 -0
  26. data/etc/default_skills/pwn/plugins/binary_analysis/SKILL.md +50 -0
  27. data/etc/default_skills/pwn/plugins/capability_broker/SKILL.md +47 -0
  28. data/etc/default_skills/pwn/plugins/findings/SKILL.md +1 -0
  29. data/etc/default_skills/pwn/plugins/mitm_proxy/SKILL.md +56 -0
  30. data/etc/default_skills/pwn/plugins/mitm_proxy/references/urls.md +3 -0
  31. data/etc/default_skills/pwn/plugins/process_tube/SKILL.md +1 -0
  32. data/etc/default_skills/pwn/plugins/radare2/SKILL.md +4 -0
  33. data/etc/default_skills/pwn/plugins/recon/SKILL.md +1 -0
  34. data/etc/default_skills/pwn/plugins/repl/SKILL.md +5 -0
  35. data/etc/default_skills/pwn/plugins/rop/SKILL.md +49 -0
  36. data/etc/default_skills/pwn/plugins/sandbox/SKILL.md +50 -0
  37. data/etc/default_skills/pwn/plugins/vault/SKILL.md +4 -2
  38. data/etc/default_skills/pwn/redaction/SKILL.md +48 -0
  39. data/etc/default_skills/pwn/reports/SKILL.md +1 -0
  40. data/etc/default_skills/pwn/session_trace/SKILL.md +50 -0
  41. data/lib/pwn/ai/agent/dispatch.rb +143 -6
  42. data/lib/pwn/ai/agent/engagement_memory.rb +137 -0
  43. data/lib/pwn/ai/agent/learning.rb +1 -1
  44. data/lib/pwn/ai/agent/loop.rb +97 -54
  45. data/lib/pwn/ai/agent/manifest.rb +178 -0
  46. data/lib/pwn/ai/agent/mistakes.rb +38 -3
  47. data/lib/pwn/ai/agent/profiles.rb +103 -0
  48. data/lib/pwn/ai/agent/registry.rb +2 -0
  49. data/lib/pwn/ai/agent/request_runtime.rb +94 -0
  50. data/lib/pwn/ai/agent/reward.rb +35 -22
  51. data/lib/pwn/ai/agent/skill_consolidation.rb +151 -0
  52. data/lib/pwn/ai/agent/swarm.rb +11 -25
  53. data/lib/pwn/ai/agent/tool_guard.rb +27 -2
  54. data/lib/pwn/ai/agent/tools/finding_record.rb +27 -23
  55. data/lib/pwn/ai/agent/tools/http_replay.rb +23 -0
  56. data/lib/pwn/ai/agent/tools/recon_run.rb +27 -0
  57. data/lib/pwn/ai/agent/tools/ruby_eval.rb +4 -8
  58. data/lib/pwn/ai/agent/tools/sandbox.rb +28 -0
  59. data/lib/pwn/ai/agent/tools/shell.rb +17 -27
  60. data/lib/pwn/ai/agent/tools/skills_consolidate.rb +34 -0
  61. data/lib/pwn/ai/agent/tools/swarm.rb +9 -3
  62. data/lib/pwn/ai/agent/turn_finalizer.rb +88 -19
  63. data/lib/pwn/ai/agent.rb +8 -3
  64. data/lib/pwn/ai/cli.rb +91 -0
  65. data/lib/pwn/ai/context.rb +7 -0
  66. data/lib/pwn/ai/context_ingestion.rb +213 -0
  67. data/lib/pwn/ai/gemini.rb +6 -2
  68. data/lib/pwn/ai/grok.rb +12 -1
  69. data/lib/pwn/ai/ollama.rb +3 -1
  70. data/lib/pwn/ai/open_ai.rb +40 -5
  71. data/lib/pwn/ai/open_web_ui.rb +3 -1
  72. data/lib/pwn/ai/tools/README.md +74 -0
  73. data/lib/pwn/ai/tools/core.yaml +20 -0
  74. data/lib/pwn/ai/tools/host.yaml +10 -0
  75. data/lib/pwn/ai.rb +1 -0
  76. data/lib/pwn/config.rb +7 -0
  77. data/lib/pwn/migrate.rb +21 -1
  78. data/lib/pwn/plugins/binary_analysis.rb +104 -0
  79. data/lib/pwn/plugins/capability_broker/daemon.py +145 -0
  80. data/lib/pwn/plugins/capability_broker.rb +52 -0
  81. data/lib/pwn/plugins/findings.rb +108 -10
  82. data/lib/pwn/plugins/ghidra_headless.rb +52 -33
  83. data/lib/pwn/plugins/ghidra_scripts/PwnExport.java +62 -0
  84. data/lib/pwn/plugins/log.rb +30 -11
  85. data/lib/pwn/plugins/mitm_proxy.rb +282 -0
  86. data/lib/pwn/plugins/monkey_patch.rb +5 -1
  87. data/lib/pwn/plugins/packet.rb +32 -129
  88. data/lib/pwn/plugins/process_tube.rb +12 -0
  89. data/lib/pwn/plugins/pwn_logger.rb +2 -1
  90. data/lib/pwn/plugins/radare2.rb +121 -0
  91. data/lib/pwn/plugins/recon.rb +173 -0
  92. data/lib/pwn/plugins/repl.rb +174 -60
  93. data/lib/pwn/plugins/rop.rb +103 -0
  94. data/lib/pwn/plugins/sandbox/Dockerfile +5 -0
  95. data/lib/pwn/plugins/sandbox/driver.py +187 -0
  96. data/lib/pwn/plugins/sandbox.rb +68 -0
  97. data/lib/pwn/plugins/transparent_browser.rb +11 -0
  98. data/lib/pwn/plugins/vault.rb +88 -21
  99. data/lib/pwn/plugins.rb +5 -0
  100. data/lib/pwn/redaction.rb +70 -0
  101. data/lib/pwn/reports/html.rb +9 -0
  102. data/lib/pwn/reports/json.rb +2 -1
  103. data/lib/pwn/reports/markdown.rb +6 -0
  104. data/lib/pwn/reports.rb +42 -0
  105. data/lib/pwn/session_trace.rb +163 -0
  106. data/lib/pwn/sessions.rb +17 -32
  107. data/lib/pwn/setup.rb +4 -0
  108. data/lib/pwn/version.rb +1 -1
  109. data/lib/pwn.rb +2 -0
  110. data/spec/integration/dispatch_spec.rb +2 -2
  111. data/spec/lib/pwn/ai/agent/dispatch_policy_spec.rb +142 -0
  112. data/spec/lib/pwn/ai/agent/engagement_memory_spec.rb +74 -0
  113. data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +48 -0
  114. data/spec/lib/pwn/ai/agent/loop_spec.rb +86 -6
  115. data/spec/lib/pwn/ai/agent/manifest_spec.rb +46 -0
  116. data/spec/lib/pwn/ai/agent/mistakes_precision_spec.rb +57 -0
  117. data/spec/lib/pwn/ai/agent/profiles_spec.rb +49 -0
  118. data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +55 -0
  119. data/spec/lib/pwn/ai/agent/reward_spec.rb +69 -2
  120. data/spec/lib/pwn/ai/agent/skill_consolidation_spec.rb +64 -0
  121. data/spec/lib/pwn/ai/agent/swarm_spec.rb +158 -0
  122. data/spec/lib/pwn/ai/agent/tool_guard_spec.rb +8 -0
  123. data/spec/lib/pwn/ai/agent/tools/finding_record_spec.rb +9 -0
  124. data/spec/lib/pwn/ai/agent/tools/http_replay_spec.rb +22 -0
  125. data/spec/lib/pwn/ai/agent/tools/recon_run_spec.rb +15 -0
  126. data/spec/lib/pwn/ai/agent/tools/sandbox_spec.rb +15 -0
  127. data/spec/lib/pwn/ai/agent/tools/shell_spec.rb +2 -2
  128. data/spec/lib/pwn/ai/agent/tools/skills_consolidate_spec.rb +22 -0
  129. data/spec/lib/pwn/ai/agent/tools/swarm_spec.rb +14 -0
  130. data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +55 -1
  131. data/spec/lib/pwn/ai/cli_spec.rb +102 -0
  132. data/spec/lib/pwn/ai/context_ingestion_spec.rb +80 -0
  133. data/spec/lib/pwn/ai/gemini_spec.rb +19 -0
  134. data/spec/lib/pwn/ai/open_ai_oauth_transport_spec.rb +35 -0
  135. data/spec/lib/pwn/ai/open_ai_spec.rb +12 -0
  136. data/spec/lib/pwn/config_spec.rb +10 -0
  137. data/spec/lib/pwn/migrate_spec.rb +28 -0
  138. data/spec/lib/pwn/plugins/binary_analysis_spec.rb +41 -0
  139. data/spec/lib/pwn/plugins/capability_broker_spec.rb +15 -0
  140. data/spec/lib/pwn/plugins/capd_test.py +27 -0
  141. data/spec/lib/pwn/plugins/findings_spec.rb +2 -1
  142. data/spec/lib/pwn/plugins/findings_structured_spec.rb +72 -0
  143. data/spec/lib/pwn/plugins/log_spec.rb +3 -3
  144. data/spec/lib/pwn/plugins/mitm_proxy_spec.rb +78 -0
  145. data/spec/lib/pwn/plugins/monkey_patch_spec.rb +7 -0
  146. data/spec/lib/pwn/plugins/packet_spec.rb +8 -0
  147. data/spec/lib/pwn/plugins/process_tube_spec.rb +7 -0
  148. data/spec/lib/pwn/plugins/recon_pipeline_spec.rb +129 -0
  149. data/spec/lib/pwn/plugins/repl_spec.rb +121 -0
  150. data/spec/lib/pwn/plugins/rop_spec.rb +14 -0
  151. data/spec/lib/pwn/plugins/sandbox_spec.rb +21 -0
  152. data/spec/lib/pwn/plugins/sandbox_test.py +47 -0
  153. data/spec/lib/pwn/plugins/transparent_browser_spec.rb +28 -0
  154. data/spec/lib/pwn/plugins/vault_credentials_spec.rb +54 -0
  155. data/spec/lib/pwn/redaction_spec.rb +126 -0
  156. data/spec/lib/pwn/session_trace_spec.rb +78 -0
  157. data/spec/spec_helper.rb +2 -0
  158. data/third_party/pwn_rdoc.jsonl +130 -7
  159. metadata +85 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1c409d6b05e3c061837ef187c74d22ff5e24d361aee4531be0943714a8f713b
4
- data.tar.gz: dacfce51246aee0f02c84b5a8eae1651fe2853db2e0f192343c5fc8cfbc5462b
3
+ metadata.gz: 3ada4e79d8d8864ce2ea48b7378b4d10e124e7925d050424d3ef10e269438d5f
4
+ data.tar.gz: 714c044b1ab48bf33ca175d73fee3997d0788ed9d34e6c124d59c7fdf630c01e
5
5
  SHA512:
6
- metadata.gz: 6c29fd2cde7775f1923249f166af195dcc99ed0614c86536d3aeb56bcc7a3d29a28a3483b1b2c25bb736b2b78b28ab92a0906d690cd71d002ff335c41e1e5d78
7
- data.tar.gz: cbcf28e1681df68a21a1da6f247bf645de861ee2a752af2f3735f09f2a59501bdd9d5fb5bf238dcc39ae2734ea173a3d515ddb3df1de2d7548de4fbbb67c5ea7
6
+ metadata.gz: 2472a6aa034cd281f4b2b67804558b7f40827abfca843857b7f713d1c492a9ea1e3c2bfd03282134bc2d2270e1db77cdc7c20876b0f4baf58d5b36c929ba536f
7
+ data.tar.gz: e7550328388a59e8d4047bceef0da28446850c2b0d40670930e104ea3ca7a89559aa65db510bd2d2e60f02053f85a98890a5bd96fee7d0435d1a53c845a5fe44
data/Gemfile CHANGED
@@ -49,7 +49,7 @@ gem 'jwt', '3.2.0'
49
49
  gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
52
- gem 'mcp', '1.5.0'
52
+ gem 'mcp', '1.5.1'
53
53
  gem 'meshtastic', '0.0.175'
54
54
  gem 'metasm', '1.0.6'
55
55
  gem 'mongo', '2.25.0'
@@ -89,8 +89,8 @@ gem 'ruby-nmap', '1.0.3'
89
89
  gem 'ruby-saml', '1.18.1'
90
90
  gem 'rvm', '1.11.3.9'
91
91
  gem 'savon', '2.17.4'
92
- gem 'selenium-devtools', '0.152.0'
93
- gem 'selenium-webdriver', '4.48.0'
92
+ gem 'selenium-devtools', '0.153.0'
93
+ gem 'selenium-webdriver', '4.49.0'
94
94
  gem 'slack-ruby-client', '3.2.0'
95
95
  gem 'socksify', '1.8.1'
96
96
  gem 'spreadsheet', '1.3.5'
data/bin/pwn-ai ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'pwn/ai/cli'
5
+
6
+ exit PWN::AI::CLI.run(argv: ARGV)
data/bin/pwn-capd ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Packaging-compatible driver; privileged deployments execute daemon.py directly.
5
+ exec('/usr/bin/python3', '-I', File.expand_path('../lib/pwn/plugins/capability_broker/daemon.py', __dir__), *ARGV)
@@ -113,8 +113,9 @@ puts resp[:content]
113
113
 
114
114
  ## Model diversity in Swarm
115
115
 
116
- Because each persona in [`agents.yml`](Swarm.md) can override `engine:`, an
117
- `agent_debate` can pit several *different providers* against each other - real
116
+ Because each persona in [`agents.yml`](Swarm.md) can override `engine:` and
117
+ `model:`, an `agent_debate` can compare different models on the same provider
118
+ or several *different providers* against each other - real
118
119
  antagonism, not one model role-playing three voices. The same mechanism backs
119
120
  `ai.agent.escalation_persona`: when a local model is stuck, `Loop.run` asks a
120
121
  frontier persona for a 3-line corrective hint and injects it as a synthetic
@@ -10,10 +10,11 @@ toolsets; the JSON-Schema for each tool is what the model actually sees.
10
10
 
11
11
  | Toolset | Tools | Backed by |
12
12
  |---|---|---|
13
+ | `http` | `http_proxy_start` · `http_proxy_stop` · `http_proxy_entries` · `http_proxy_rules` · `http_replay` | `PWN::Plugins::MitmProxy`: native HTTP HAR capture/replay; opaque CONNECT tunnels |
13
14
  | `terminal` | `shell` | `Open3.capture3` on the host, after `PWN::AI::Agent::ToolGuard` |
14
15
  | `pwn` | `pwn_eval` | `TOPLEVEL_BINDING.eval` in the live REPL process, after `ToolGuard` |
15
16
  | `memory` | `memory_remember` · `memory_recall` · `memory_forget` · `memory_clear` · **`memory_lean`** | `PWN::Memory` → `~/.pwn/memory.json` |
16
- | `skills` | **`skills_recall`** · `skill_list` · `skill_view` · `skill_create` · `skill_add_reference` · `skill_delete` · `skill_migrate_legacy` | `~/.pwn/skills/<name>/SKILL.md` (**[agentskills.io](https://agentskills.io) spec**; legacy flat `*.md` auto-migrated) |
17
+ | `skills` | `skills_consolidate` · **`skills_recall`** · `skill_list` · `skill_view` · `skill_create` · `skill_add_reference` · `skill_delete` · `skill_migrate_legacy` | `~/.pwn/skills/<name>/SKILL.md` (**[agentskills.io](https://agentskills.io) spec**; legacy flat `*.md` auto-migrated) |
17
18
  | `sessions` | **`session_recall`** · `sessions_list` · `sessions_view` · `sessions_current` · `sessions_delete` · `sessions_stats` · **`sessions_lean`** | `PWN::Sessions` → `~/.pwn/sessions/` |
18
19
  | `learning` | `learning_note_outcome` · `learning_reflect` · `learning_distill_skill` · `learning_stats` · `learning_outcomes` · `learning_consolidate` · `learning_reset` · `learning_auto_introspect_toggle` · **`learning_gc_stores`** · **`learning_purge_noise`** · **`mistakes_list`** · **`mistakes_record`** · **`mistakes_resolve`** · **`mistakes_reset`** · **`mistakes_lean`** · **`reward_judge`** · **`reward_prm`** · **`reward_sentinel`** · **`reward_preferences`** · **`reward_export_dpo`** · **`reward_warm_sentinel`** · **`reward_scrub_preferences`** · **`reward_preference_balance`** · **`curriculum_practice`** · **`curriculum_train`** · **`curriculum_hindsight`** · **`curriculum_offline_judge`** · **`curriculum_preference_balance`** | `PWN::AI::Agent::Learning` + `Mistakes` + `Reward` + `Curriculum` → `~/.pwn/learning.jsonl` + `~/.pwn/mistakes.json` + `~/.pwn/preferences.jsonl` + `~/.pwn/curriculum/` + `~/.pwn/finetune/` |
19
20
  | `reward` | **`reward_generator_mix`** | `PWN::AI::Agent::Reward.generator_mix` → online preference source-mix controller (`preferences.jsonl`) |
@@ -23,6 +24,7 @@ toolsets; the JSON-Schema for each tool is what the model actually sees.
23
24
  | `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` |
24
25
  | `cron` | `cron_list` · `cron_create` · `cron_run` · `cron_enable` · `cron_disable` · `cron_remove` | `PWN::Cron` → `~/.pwn/cron/jobs.yml` |
25
26
  | `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
+ | `manifest` | `host_os_type` | YAML tool declarations in `lib/pwn/ai/tools/*.yaml` via `PWN::AI::Agent::Manifest` |
26
28
 
27
29
  The `learning` toolset is deliberately large: **Mistakes** (negative feedback),
28
30
  **Reward** (outcome and process judges, sentinel, preference ledger) and
@@ -0,0 +1,97 @@
1
+ # Capability broker and disposable sandbox
2
+
3
+ ## pwn-capd (P8)
4
+
5
+ The Python stdlib helper speaks one bounded JSON request per Unix connection.
6
+ Linux SO_PEERCRED authenticates the configured caller UID; the client also
7
+ checks the daemon UID. The socket is mode 0600. Only explicitly configured
8
+ interfaces are accepted. Operations: `status`, `raw_send` (base64 Ethernet
9
+ frame), `capture` (bounded in-memory pcap), `arp` / `nd` (typed IP kernel
10
+ neighbor-cache queries). There is no command endpoint, arbitrary privileged
11
+ output path, firewall mutation, or implicit sudo. ARP/ND currently query cache;
12
+ they do not actively solicit neighbors or mutate entries.
13
+
14
+ Operator installation (NOT executed by the application): install
15
+ `lib/pwn/plugins/capability_broker/daemon.py` (not the Ruby packaging driver)
16
+ root-owned under `/usr/local/libexec/pwn-capd`, mode 0755, in a root-owned
17
+ non-writable directory. Do **not** setcap the system Ruby/Python interpreter
18
+ or this script: Linux ignores script file capabilities, and granting a shared
19
+ interpreter network privileges exposes every script. Use a dedicated systemd
20
+ service with `User=<operator>`, `AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN`,
21
+ `CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN`, `NoNewPrivileges=yes`,
22
+ `ProtectSystem=strict`, `ProtectHome=yes`, `RuntimeDirectory=pwn-capd`,
23
+ `RestrictAddressFamilies=AF_UNIX AF_PACKET AF_NETLINK`, and the reviewed helper
24
+ as ExecStart. The socket owner and configured peer UID must match that operator.
25
+ Enable a preconfigured unit with `sudo systemctl start pwn-capd`.
26
+ For an audited one-shot sudo launch use:
27
+
28
+ ```
29
+ sudo /usr/local/libexec/pwn-capd --uid "$(id -u)" --interface lo
30
+ ```
31
+
32
+ Never authorize unrestricted sudo access to arbitrary helper arguments. Pin
33
+ UID, socket and interface in sudoers. Only add engagement interfaces after
34
+ operator review. CAP_NET_ADMIN is reserved for future neighbor management;
35
+ current operations need CAP_NET_RAW, while neighbor queries are read-only.
36
+
37
+ Ruby API (explicit require until central autoload is integrated):
38
+
39
+ ```ruby
40
+ require 'pwn/plugins/capability_broker'
41
+ PWN::Plugins::CapabilityBroker.request(operation: 'status')
42
+ PWN::Plugins::CapabilityBroker.request(operation: 'arp', iface: 'lo', address: '127.0.0.1')
43
+ PWN::Plugins::CapabilityBroker.request(operation: 'nd', iface: 'lo', address: '::1')
44
+ # Packet.send(pkt:, iface:, socket:) and Packet.capture(path:, count:, timeout:, iface:, socket:)
45
+ ```
46
+
47
+ `PWN_CAPD_SOCKET` overrides `/run/pwn-capd/control.sock`. Capture artifacts are
48
+ written and read back by the unprivileged client, never by the daemon.
49
+ Absent broker returns degraded error and missing local CAP_NET_RAW accurately;
50
+ there is no fabricated capture path or privileged Ruby remediation.
51
+
52
+ ## Sandbox (P13)
53
+
54
+ ```ruby
55
+ require 'pwn/plugins/sandbox'
56
+ PWN::Plugins::Sandbox.run(binary: '/path/safe-fixture', argv: [], stdin: '', timeout: 10, memory_mb: 256)
57
+ PWN::Plugins::Sandbox.fuzz(target: '/path/safe-fixture', corpus: '/path/seeds', minutes: 1, seed: 0)
58
+ s = PWN::Plugins::Sandbox.snapshot(binary: '/path/safe-fixture')
59
+ PWN::Plugins::Sandbox.rollback(snapshot: s[:snapshot], backend: 'bwrap')
60
+ ```
61
+
62
+ Docker is default, image `pwn-sandbox:local`; it must be provisioned locally,
63
+ with `/usr/bin/python3`, strace, GDB, and target runtime libraries. No implicit
64
+ pull, installation, daemon startup or host-execution fallback occurs. Use the
65
+ included `lib/pwn/plugins/sandbox/Dockerfile` to build explicitly. Network none,
66
+ read-only root/artifacts, unprivileged UID, all caps dropped, no-new-privileges,
67
+ pid/cpu/memory/swap constraints, disposable bounded tmpfs, forced cleanup.
68
+ Instrumentation runs are separate replays with the same input, not observation
69
+ of the original execution. Signals, PC, backtrace, raw GDB output are returned;
70
+ exploitable classification is `unknown` if the GDB plugin is absent. No invented
71
+ classification. Current `-nx` configuration deliberately does not load arbitrary
72
+ user GDB startup scripts. Install a reviewed exploitable plugin into the image
73
+ and extend its explicit initialization if classification is required.
74
+
75
+ Explicit `backend: 'bwrap'` works without a Docker daemon where unprivileged
76
+ user namespaces are permitted. It exposes only read-only system runtime trees
77
+ and target artifact, creates fresh user/network/PID/mount namespaces, and hides
78
+ home. It is a **weaker resource tier**: per-process RLIMIT_AS, not an aggregate
79
+ cgroup budget or fork-bomb protection. Use Docker/microVM for hostile samples.
80
+ No automatic backend fallback. Snapshot/rollback means verified immutable input
81
+ copies and fresh disposable environments, **not live process/VM checkpoints**.
82
+ Fuzzing is bounded seeded bit-flip stdin mutation, not coverage-guided AFL.
83
+
84
+ Tools register via `require 'pwn/ai/agent/tools/sandbox'`: `sandbox_run` and
85
+ `sandbox_fuzz`; normal Registry discovery finds that file.
86
+
87
+ Verification:
88
+ ```
89
+ bundle exec rspec spec/lib/pwn/plugins/{packet,capability_broker,sandbox}_spec.rb
90
+ python3 spec/lib/pwn/plugins/capd_test.py
91
+ python3 spec/lib/pwn/plugins/sandbox_test.py
92
+ ```
93
+
94
+ Safe real Docker smoke after explicit provisioning:
95
+ ```
96
+ bundle exec ruby -Ilib -rpwn/plugins/sandbox -e 'p PWN::Plugins::Sandbox.run(binary: "/bin/true")'
97
+ ```
@@ -0,0 +1,102 @@
1
+ # Native HTTP capture and skill hygiene
2
+
3
+ ## Native HTTP proxy (P16)
4
+
5
+ `PWN::Plugins::MitmProxy` uses WEBrick's native proxy/CONNECT transport and
6
+ Net::HTTP for interception. It does not require mitmproxy, Burp, or ZAP.
7
+ Only backend `native` is accepted. HTTPS CONNECT tunnels work, but their
8
+ contents are **not decrypted**: captured entries explicitly have
9
+ `_capture: opaque_connect`. HTTP requests and responses are captured to HAR 1.2.
10
+
11
+ ```ruby
12
+ require 'pwn/plugins/mitm_proxy'
13
+ proxy = PWN::Plugins::MitmProxy.start(har_path: '/tmp/engagement.har')
14
+ browser = PWN::Plugins::TransparentBrowser.open(
15
+ browser_type: :headless_chrome, capture_proxy: proxy
16
+ )
17
+ # Navigate the browser using its normal API. Chrome loopback bypass is disabled.
18
+ entries = PWN::Plugins::MitmProxy.entries(proxy: proxy)
19
+ entry = PWN::Plugins::MitmProxy.http_replay(
20
+ proxy: proxy, request_id: entries.first[:_request_id],
21
+ mutations: { method: 'POST', path: '/fixture', headers: { 'X-Test' => 'yes' }, body: 'sample' }
22
+ )
23
+ # Close the browser separately, then stop its caller-owned proxy:
24
+ PWN::Plugins::MitmProxy.stop(proxy: proxy)
25
+ ```
26
+
27
+ Agent toolset `http`: `http_proxy_start`, `http_proxy_entries`,
28
+ `http_proxy_rules`, `http_replay`, `http_proxy_stop`. Lifecycle tools return
29
+ JSON descriptors; subsequent calls use `proxy_id: descriptor[:id]`.
30
+ The plugin also accepts `proxy:` descriptors. IDs are process-local, not
31
+ persistent replay handles after restart.
32
+
33
+ Replace the entire active rule list with `rules(proxy:, rules:)`. Rules are
34
+ literal substitutions (not regex):
35
+
36
+ ```ruby
37
+ [{ phase: 'request', field: 'header:x-test', match: 'old', replace: 'new' },
38
+ { phase: 'response', field: 'body', match: 'old', replace: 'new' }]
39
+ ```
40
+
41
+ Fields are `body`, request-only `url`, or `header:NAME`. Replay mutations are
42
+ `method`, `url`, `path`, `query`, `headers`, `body`; null header values remove
43
+ headers. Hop-by-hop/framing headers are regenerated. Binary HAR bodies use
44
+ base64 encoding. Replay does not mutate the original captured entry.
45
+
46
+ HAR files contain **unredacted traffic**, including any cookies and credentials
47
+ sent by the operator. They are created mode 0600; use engagement storage and
48
+ retention controls. Only loopback fixtures are used by the regression tests.
49
+ There is no built-in scope policy for proxy traffic; enforcement belongs in
50
+ engagement network isolation or the central scope layer. Binding is loopback
51
+ by default. Upstream HTTP timeout defaults to 30 seconds. Bodies and the
52
+ session capture collection are currently buffered in memory, not streamed.
53
+
54
+ ## On-demand skills consolidation (P19)
55
+
56
+ `PWN::AI::Agent::SkillConsolidation.consolidate(content:, evidence: {}, now:,
57
+ stale_days: 90)` returns a pure proposal: `content`, `version`, `changed`,
58
+ `deduplicated`, `promoted`, and `review`.
59
+
60
+ - Deduplicates RL notes, ignoring leading date annotations and whitespace.
61
+ - Conflicting text for one feedback ID, explicit negation against a SOP line,
62
+ regressed proofs, and stale verification move to a **not active SOP** review
63
+ section. Unmanaged SOP prose is never automatically deleted or rewritten.
64
+ - Promotions require resolved status, three distinct explicitly successful
65
+ verification sessions, and recent non-future verification. Failure counts
66
+ and failure-session IDs do not count as successful evidence.
67
+ - Managed promoted procedures are rechecked for stale or regressed proofs.
68
+ - Stamps `<!-- pwn-skill-version: HASH12 updated: ISO8601 -->` after YAML
69
+ frontmatter; unchanged content remains byte-identical on subsequent passes.
70
+
71
+ `consolidate_file(path:)` previews by default. Applying requires
72
+ `dry_run: false, confirm: true, expected_sha256: preview[:source_sha256]`.
73
+ It serializes cooperating consolidation writers, checks the original content,
74
+ keeps a content-addressed backup, atomically replaces the file, and verifies
75
+ readback. Symlink files are rejected. No weekly job is installed.
76
+
77
+ Agent tool `skills_consolidate(name:, dry_run: true)` targets an already
78
+ installed skill; it does not accept model-provided verification evidence.
79
+ It reloads the skill index after an explicitly confirmed write.
80
+
81
+ ## Precise correction evidence (P20) and integration hooks
82
+
83
+ Docker socket permissions, registry pull denial and template parsing remain
84
+ distinct classes/signatures. Generic path fixes cannot resolve non-path
85
+ failures; raw-socket fixes cannot resolve ordinary filesystem/daemon
86
+ permissions. Legacy incompatible fixes are excluded from KNOWN FIXES.
87
+
88
+ For promotion evidence, the successful verification hook should call:
89
+
90
+ ```ruby
91
+ PWN::AI::Agent::Mistakes.note_hint_outcome(
92
+ signature: signature, helped: true, session_id: verified_session_id
93
+ )
94
+ ```
95
+
96
+ Failed hints clear that stability evidence. `skills_consolidate` consumes
97
+ `verified_sessions` and `last_verified` keyed by the existing RL signature.
98
+ The parent loop must pass `session_id` only after real verification; callers
99
+ that omit it cannot promote notes merely by incrementing a counter.
100
+ Session-recall/index consumers can read the `pwn-skill-version` stamp and
101
+ prefer current installed content over historical SOP text. This module does
102
+ not modify central loop or session-recall selection behavior.
@@ -0,0 +1,31 @@
1
+ # Recon / Findings integration (P17 / P10)
2
+
3
+ ## Parent integration hooks
4
+
5
+ - Existing autoloaded `PWN::Plugins::Recon` now exposes `run(opts)`; no new plugin autoload needed. New agent registration file: `lib/pwn/ai/agent/tools/recon_run.rb` (normal Registry discovery).
6
+ - `finding_record` handler uses `PWN::Plugins::Findings.record_structured(opts)`. The loop can treat `ArgumentError` as incomplete finding input; **do not infer a working PoC from accepted input**. No Loop/config/CLI changes in this patch.
7
+ - P3 hook: `Recon.run(..., ingest: true)` calls `PWN::AI::Context.ingest(path, session_id: engagement_id, **ingest_options)` after atomic persistence and JSON readback. `ingestor: callable` supports an alternate implementation with the same signature. Ingestion failure is returned in `result[:ingestion]` without discarding recon evidence. Preserve the literal asset ID in ingested chunks. Plugin callers opt in; the agent tool defaults ingestion on.
8
+
9
+ ## Recon model
10
+
11
+ `Recon.run(target: '127.0.0.1', modules: %w[nmap banner tls], ports: [443], engagement_id: 'lab', ingest: true)`.
12
+
13
+ Single hostname/IP target (no arbitrary CLI switches, URL, CIDR, or shell command), explicit integer ports, per-operation timeout 15 seconds by default (maximum 300). `nmap` uses an unprivileged TCP connect scan, without sudo or NSE. Existing `NmapIt` API remains untouched. `banner` passively reads up to 4096 bytes; it does not send an HTTP request. `tls` collects the certificate with `trust_verified: false` (not a trust audit). Optional `subfinder` and `nuclei` run **only if explicitly named**, and unavailable tools return module status rather than fabricate results. Nuclei observations are not automatically promoted to findings.
14
+
15
+ Output: `{schema_version: 1, engagement_id:, assets: [{id:, address:, protocol:, port:, observations: [], evidence_paths: []}], modules: [], path:, ingestion: ...}`.
16
+
17
+ Stored at `~/.pwn/engagements/<engagement>/recon/assets.json`; `root:` overrides the engagements directory for isolated fixtures. Raw scan evidence is persisted beside it. IDs are `asset-` plus the first 24 hex SHA256 characters of lowercase address, protocol, port joined by `|`. Repeated same-identity observations merge across runs, not overwrite other assets. DNS names and resolved IPs are distinct identities unless the source explicitly supplies the same address; the pipeline does not silently infer aliases.
18
+
19
+ Use `affected_asset: asset[:id]` and `evidence_paths: asset[:evidence_paths]` in a structured finding. Stable IDs survive report generation and JSON ingestion unchanged.
20
+
21
+ ## Strict findings boundary and compatibility
22
+
23
+ `record_structured` requires title, CWE identifier (`CWE-<positive integer>`), complete CVSS 3.0/3.1 base vector and matching numeric base score, affected_asset, nonempty existing readable absolute evidence_paths, nonempty PoC command/code string, attack_chain_refs array, remediation string, and numeric confidence 0..1. Chain refs must already exist in the same engagement. Invalid input raises before appending. CVSS 2/4, temporal/environmental vectors are explicitly unsupported (rejected, not normalized).
24
+
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
+
27
+ 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
+
29
+ ## Verification boundaries
30
+
31
+ Specs use disposable localhost TCP/TLS fixtures and real nmap connect scans. Optional external scanner outputs are parser fixtures, not internet engagements. Context hook failures are visible; embeddings are never synthesized. No remote scanning is necessary to run these focused specs.
@@ -12,7 +12,7 @@ self-improvement loop covers the whole swarm.
12
12
 
13
13
  | Path | Contains |
14
14
  |---|---|
15
- | `~/.pwn/agents.yml` | Persona registry (name → role/engine/toolsets/max_iters) |
15
+ | `~/.pwn/agents.yml` | Persona registry (name → role/engine/model/toolsets/max_iters) |
16
16
  | `~/.pwn/swarm/<id>/bus.jsonl` | Append-only chat every persona reads/writes |
17
17
  | `~/.pwn/swarm/<id>/personas.json` | persona name → `PWN::Sessions` id |
18
18
 
@@ -21,6 +21,41 @@ a `PWN::Cron` job) calling `Swarm.ask` with the same `swarm_id`.
21
21
 
22
22
  ## Define personas
23
23
 
24
+ Each persona accepts optional `engine` and `model` keys. `model` is the exact
25
+ provider model identifier (case and punctuation are preserved). Omit `engine`
26
+ to inherit the active engine; omit `model` or leave it blank to use the selected
27
+ provider's configured default model. Existing engine-only entries still work.
28
+
29
+ For example, edit `~/.pwn/agents.yml`:
30
+
31
+ ```yaml
32
+ reviewer:
33
+ role: Review implementation and tests.
34
+ engine: openai
35
+ model: gpt-6-astra
36
+ toolsets: [terminal, pwn]
37
+ local_reviewer:
38
+ role: Independently review implementation and tests.
39
+ engine: ollama
40
+ model: your-installed-model:tag
41
+ toolsets: [terminal, pwn]
42
+ ```
43
+
44
+ Replace model identifiers with models available to your configured providers.
45
+ Personas can use different models on the same provider or different providers.
46
+ The selection applies to `ask`, `debate`, and `broadcast`; overrides are scoped
47
+ to each persona execution, restored after nested calls/errors, and do not rewrite
48
+ global provider defaults. An omitted model uses the provider default, not an
49
+ enclosing persona's model override. Provider authentication is unchanged.
50
+
51
+ `agent_spawn` also accepts `model:` and `agent_list` reports it.
52
+
53
+ On upgrade, `pwn setup --migrate` applies schema 3: existing persona entries
54
+ without a `model` key receive an unset YAML `model:` field (null, meaning the
55
+ selected provider's default). Existing model values, engine selections, and
56
+ custom persona fields are preserved. Re-running migration makes no further
57
+ changes. A missing `agents.yml` is not created by this migration.
58
+
24
59
  ```ruby
25
60
  agent_spawn(name: 'red_team',
26
61
  role: 'Offensive operator. Propose the most aggressive next step.',
@@ -82,8 +117,8 @@ agent_ask(name: 'red_team', swarm_id: tx[:swarm_id],
82
117
  request: 'blue_team raised WAF concerns - revise the payload.')
83
118
  ```
84
119
 
85
- Because each persona can pin a **different engine**, the debate is real model
86
- diversity, not one model role-playing.
120
+ Each persona can pin a **different engine and/or model**, rather than requiring
121
+ every persona to use one provider's default model.
87
122
 
88
123
  **See also:** [pwn-ai Agent](pwn-ai-Agent.md) ·
89
124
  [Agent Tool Registry](Agent-Tool-Registry.md) · [Sessions](Sessions.md)
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: pwn-ai-agent
3
+ description: Drive PWN::AI::Agent from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::AI::Agent
10
+ source: pwn/ai/agent/engagement_memory.rb
11
+ ---
12
+
13
+ # PWN::AI::Agent
14
+
15
+ Public API for PWN::AI::Agent.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::AI::Agent` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/ai/agent/engagement_memory.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::AI::Agent.help
31
+ PWN::AI::Agent.help(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `authors`
37
+ - `help`
38
+
39
+ ## Source
40
+
41
+ `pwn/ai/agent/engagement_memory.rb`
42
+
43
+ ## Verification
44
+
45
+ `PWN::AI::Agent.respond_to?(:authors)` after the
46
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: pwn-ai-agent-manifest
3
+ description: Drive PWN::AI::Agent::Manifest from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::AI::Agent::Manifest
10
+ source: pwn/ai/agent/manifest.rb
11
+ ---
12
+
13
+ # PWN::AI::Agent::Manifest
14
+
15
+ Trusted, on-disk tool declarations. Never load a manifest from tool args.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::AI::Agent::Manifest` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/ai/agent/manifest.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::AI::Agent::Manifest.help
31
+ PWN::AI::Agent::Manifest.load(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `load`
37
+ - `register`
38
+ - `check`
39
+ - `authors`
40
+ - `help`
41
+
42
+ ## Source
43
+
44
+ `pwn/ai/agent/manifest.rb`
45
+
46
+ ## Verification
47
+
48
+ `PWN::AI::Agent::Manifest.respond_to?(:load)` after the
49
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: pwn-ai-agent
3
+ description: Drive PWN::AI::Agent from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::AI::Agent
10
+ source: pwn/ai/agent/profiles.rb
11
+ ---
12
+
13
+ # PWN::AI::Agent
14
+
15
+ Public API for PWN::AI::Agent.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::AI::Agent` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/ai/agent/profiles.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::AI::Agent.help
31
+ PWN::AI::Agent.help(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `authors`
37
+ - `help`
38
+
39
+ ## Source
40
+
41
+ `pwn/ai/agent/profiles.rb`
42
+
43
+ ## Verification
44
+
45
+ `PWN::AI::Agent.respond_to?(:authors)` after the
46
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: pwn-ai-agent
3
+ description: Drive PWN::AI::Agent from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::AI::Agent
10
+ source: pwn/ai/agent/request_runtime.rb
11
+ ---
12
+
13
+ # PWN::AI::Agent
14
+
15
+ Public API for PWN::AI::Agent.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::AI::Agent` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/ai/agent/request_runtime.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::AI::Agent.help
31
+ PWN::AI::Agent.help(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `authors`
37
+ - `help`
38
+
39
+ ## Source
40
+
41
+ `pwn/ai/agent/request_runtime.rb`
42
+
43
+ ## Verification
44
+
45
+ `PWN::AI::Agent.respond_to?(:authors)` after the
46
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: pwn-ai-agent-skillconsolidation
3
+ description: Drive PWN::AI::Agent::SkillConsolidation from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::AI::Agent::SkillConsolidation
10
+ source: pwn/ai/agent/skill_consolidation.rb
11
+ ---
12
+
13
+ # PWN::AI::Agent::SkillConsolidation
14
+
15
+ Conservative, deterministic SOP hygiene. Content proposals are pure; writes require an explicit path and optimistic-concurrency digest.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::AI::Agent::SkillConsolidation` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/ai/agent/skill_consolidation.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::AI::Agent::SkillConsolidation.help
31
+ PWN::AI::Agent::SkillConsolidation.consolidate(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `consolidate`
37
+ - `consolidate_file`
38
+ - `authors`
39
+ - `help`
40
+
41
+ ## Source
42
+
43
+ `pwn/ai/agent/skill_consolidation.rb`
44
+
45
+ ## Verification
46
+
47
+ `PWN::AI::Agent::SkillConsolidation.respond_to?(:consolidate)` after the
48
+ module is loaded. Read the source for parameter names.
@@ -28,11 +28,12 @@ Class methods take `(opts = {})` and read `opts`.
28
28
 
29
29
  ```ruby
30
30
  PWN::AI::Agent::ToolGuard.help
31
- PWN::AI::Agent::ToolGuard.present(opts)
31
+ PWN::AI::Agent::ToolGuard.unwrap_payload(opts)
32
32
  ```
33
33
 
34
34
  ## Public methods
35
35
 
36
+ - `unwrap_payload`
36
37
  - `present`
37
38
  - `placeholder`
38
39
  - `bashism`
@@ -91,5 +92,5 @@ PWN::AI::Agent::ToolGuard.present(opts)
91
92
 
92
93
  ## Verification
93
94
 
94
- `PWN::AI::Agent::ToolGuard.respond_to?(:present)` after the
95
+ `PWN::AI::Agent::ToolGuard.respond_to?(:unwrap_payload)` after the
95
96
  module is loaded. Read the source for parameter names.