pwn 0.5.784 → 0.5.785

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +6 -6
  4. data/README.md +37 -0
  5. data/bin/pwn +2 -1
  6. data/bin/pwn_web_cache_deception +35 -7
  7. data/documentation/AI-Integration.md +7 -5
  8. data/documentation/Agent-Tool-Registry.md +2 -2
  9. data/documentation/Backup-Retention.md +70 -15
  10. data/documentation/Configuration.md +10 -6
  11. data/documentation/Contributing.md +1 -1
  12. data/documentation/Cron.md +2 -2
  13. data/documentation/Diagrams.md +2 -2
  14. data/documentation/General-PWN-Usage.md +4 -3
  15. data/documentation/Home.md +6 -5
  16. data/documentation/How-PWN-Works.md +6 -5
  17. data/documentation/Installation.md +12 -9
  18. data/documentation/Mission-Cancellation.md +134 -0
  19. data/documentation/Operability.md +17 -0
  20. data/documentation/Persistence.md +7 -5
  21. data/documentation/Plugin-Degradation.md +12 -0
  22. data/documentation/Plugins.md +3 -2
  23. data/documentation/Runtime-Source-Protection.md +162 -0
  24. data/documentation/Transparent-Browser.md +11 -0
  25. data/documentation/Troubleshooting.md +3 -2
  26. data/documentation/What-is-PWN.md +2 -2
  27. data/documentation/YubiKey-Vaults.md +183 -0
  28. data/documentation/YubiKey.md +339 -0
  29. data/documentation/diagrams/cron-scheduling.svg +130 -130
  30. data/documentation/diagrams/dot/cron-scheduling.dot +3 -3
  31. data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
  32. data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
  33. data/documentation/diagrams/persistence-filesystem.svg +172 -142
  34. data/documentation/diagrams/plugin-ecosystem.svg +64 -64
  35. data/documentation/pwn-ai-Agent.md +10 -4
  36. data/etc/default_cron/session_cleanup.rb +43 -27
  37. data/etc/default_skills/pwn/ai/agent/mission_namespace/SKILL.md +21 -0
  38. data/etc/default_skills/pwn/ai/agent/tools/browser_act/SKILL.md +45 -0
  39. data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +27 -26
  40. data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +25 -15
  41. data/etc/default_skills/pwn/ai/agent/tools/workspace_tool/SKILL.md +24 -0
  42. data/etc/default_skills/pwn/ai/server_tools/SKILL.md +25 -0
  43. data/etc/default_skills/pwn/backups/SKILL.md +54 -0
  44. data/etc/default_skills/pwn/plugins/recon/SKILL.md +12 -1
  45. data/etc/default_skills/pwn/plugins/yubikey/SKILL.md +157 -0
  46. data/etc/default_skills/pwn/plugins/yubikey/fido/SKILL.md +189 -0
  47. data/etc/default_skills/pwn/plugins/yubikey/hsmauth/SKILL.md +83 -0
  48. data/etc/default_skills/pwn/plugins/yubikey/management/SKILL.md +80 -0
  49. data/etc/default_skills/pwn/plugins/yubikey/oath/SKILL.md +115 -0
  50. data/etc/default_skills/pwn/plugins/yubikey/openpgp/SKILL.md +144 -0
  51. data/etc/default_skills/pwn/plugins/yubikey/otp/SKILL.md +97 -0
  52. data/etc/default_skills/pwn/plugins/yubikey/piv/SKILL.md +110 -0
  53. data/etc/default_skills/pwn/plugins/yubikey/protocol/SKILL.md +21 -0
  54. data/etc/default_skills/pwn/plugins/yubikey/security_domain/SKILL.md +75 -0
  55. data/etc/default_skills/pwn/plugins/yubikey/security_domain/scp/SKILL.md +46 -0
  56. data/etc/default_skills/pwn/plugins/yubikey/transport/SKILL.md +98 -0
  57. data/etc/default_skills/pwn/redaction/SKILL.md +13 -1
  58. data/lib/pwn/ai/agent/dispatch.rb +26 -15
  59. data/lib/pwn/ai/agent/learning.rb +14 -18
  60. data/lib/pwn/ai/agent/loop.rb +17 -1
  61. data/lib/pwn/ai/agent/mission_namespace.rb +331 -0
  62. data/lib/pwn/ai/agent/policy_evaluation.rb +8 -8
  63. data/lib/pwn/ai/agent/prompt_builder.rb +13 -2
  64. data/lib/pwn/ai/agent/request_runtime.rb +122 -18
  65. data/lib/pwn/ai/agent/reward.rb +5 -3
  66. data/lib/pwn/ai/agent/skill_consolidation.rb +3 -2
  67. data/lib/pwn/ai/agent/skill_review.rb +9 -5
  68. data/lib/pwn/ai/agent/swarm.rb +4 -0
  69. data/lib/pwn/ai/agent/tools/browser_act.rb +32 -0
  70. data/lib/pwn/ai/agent/tools/loot_query.rb +11 -1
  71. data/lib/pwn/ai/agent/tools/recon_run.rb +11 -1
  72. data/lib/pwn/ai/agent/tools/workspace_tool.rb +29 -0
  73. data/lib/pwn/ai/agent/turn_finalizer.rb +6 -2
  74. data/lib/pwn/ai/agent.rb +1 -0
  75. data/lib/pwn/ai/anthropic.rb +8 -33
  76. data/lib/pwn/ai/cli.rb +34 -5
  77. data/lib/pwn/ai/gemini.rb +1 -0
  78. data/lib/pwn/ai/grok.rb +39 -28
  79. data/lib/pwn/ai/ollama.rb +4 -2
  80. data/lib/pwn/ai/open_ai.rb +11 -32
  81. data/lib/pwn/ai/open_web_ui.rb +62 -4
  82. data/lib/pwn/ai/router.rb +8 -1
  83. data/lib/pwn/ai/server_tools.rb +231 -0
  84. data/lib/pwn/ai.rb +1 -0
  85. data/lib/pwn/backups.rb +347 -0
  86. data/lib/pwn/config.rb +70 -39
  87. data/lib/pwn/migrate.rb +85 -280
  88. data/lib/pwn/module_skills.rb +7 -0
  89. data/lib/pwn/plugins/ai_sandbox.rb +538 -1
  90. data/lib/pwn/plugins/authentication_helper.rb +74 -4
  91. data/lib/pwn/plugins/basic_auth.rb +2 -1
  92. data/lib/pwn/plugins/google_workspace.rb +4 -21
  93. data/lib/pwn/plugins/jobs.rb +1 -1
  94. data/lib/pwn/plugins/log.rb +3 -1
  95. data/lib/pwn/plugins/preflight_checker.rb +34 -9
  96. data/lib/pwn/plugins/recon.rb +4 -1
  97. data/lib/pwn/plugins/repl/ai.rb +293 -26
  98. data/lib/pwn/plugins/repl/ai_console.rb +268 -88
  99. data/lib/pwn/plugins/repl/ai_swarm.rb +177 -58
  100. data/lib/pwn/plugins/repl/mesh.rb +3 -16
  101. data/lib/pwn/plugins/repl/vault.rb +4 -15
  102. data/lib/pwn/plugins/repl.rb +1 -216
  103. data/lib/pwn/plugins/sandbox/driver.rb +4 -0
  104. data/lib/pwn/plugins/transparent_browser.rb +215 -0
  105. data/lib/pwn/plugins/vault.rb +412 -135
  106. data/lib/pwn/plugins/yubikey/fido.rb +1288 -0
  107. data/lib/pwn/plugins/yubikey/hsmauth.rb +439 -0
  108. data/lib/pwn/plugins/yubikey/management.rb +686 -0
  109. data/lib/pwn/plugins/yubikey/oath.rb +766 -0
  110. data/lib/pwn/plugins/yubikey/openpgp.rb +1063 -0
  111. data/lib/pwn/plugins/yubikey/otp.rb +897 -0
  112. data/lib/pwn/plugins/yubikey/piv.rb +1315 -0
  113. data/lib/pwn/plugins/yubikey/protocol.rb +341 -0
  114. data/lib/pwn/plugins/yubikey/security_domain/scp.rb +482 -0
  115. data/lib/pwn/plugins/yubikey/security_domain.rb +464 -0
  116. data/lib/pwn/plugins/yubikey/transport.rb +1017 -0
  117. data/lib/pwn/plugins/yubikey.rb +844 -0
  118. data/lib/pwn/plugins.rb +1 -0
  119. data/lib/pwn/redaction.rb +69 -12
  120. data/lib/pwn/session_trace.rb +4 -0
  121. data/lib/pwn/setup.rb +114 -24
  122. data/lib/pwn/version.rb +1 -1
  123. data/lib/pwn/www/app_cobalt_io.rb +16 -5
  124. data/lib/pwn/www/bug_crowd.rb +16 -5
  125. data/lib/pwn/www/coinbase_pro.rb +16 -5
  126. data/lib/pwn/www/github.rb +30 -14
  127. data/lib/pwn/www/paypal.rb +15 -4
  128. data/lib/pwn/www/synack.rb +16 -5
  129. data/lib/pwn/www/twitter.rb +17 -6
  130. data/lib/pwn.rb +1 -0
  131. data/spec/documentation/installation_md_spec.rb +1 -1
  132. data/spec/fixtures/yubikey/apdu_frames.json +162 -0
  133. data/spec/integration/authentication_helper_oath_spec.rb +117 -0
  134. data/spec/integration/controller_spill_spec.rb +48 -0
  135. data/spec/integration/harness_process_spec.rb +97 -0
  136. data/spec/integration/mission_cancel_spec.rb +79 -0
  137. data/spec/integration/mission_curses_pty_spec.rb +44 -0
  138. data/spec/integration/readme_security_spec.rb +47 -0
  139. data/spec/integration/web_cache_deception_yubikey_spec.rb +159 -0
  140. data/spec/lib/pwn/ai/agent/dispatch_spec.rb +6 -3
  141. data/spec/lib/pwn/ai/agent/dispatch_vault_safety_spec.rb +86 -0
  142. data/spec/lib/pwn/ai/agent/injection_guard_spec.rb +2 -0
  143. data/spec/lib/pwn/ai/agent/learning_spec.rb +4 -4
  144. data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +5 -0
  145. data/spec/lib/pwn/ai/agent/loop_spec.rb +104 -29
  146. data/spec/lib/pwn/ai/agent/mission_namespace_spec.rb +138 -0
  147. data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +48 -0
  148. data/spec/lib/pwn/ai/agent/shell_search_effect_spec.rb +44 -0
  149. data/spec/lib/pwn/ai/agent/solve_spec.rb +6 -4
  150. data/spec/lib/pwn/ai/agent/tools/browser_act_spec.rb +44 -0
  151. data/spec/lib/pwn/ai/agent/tools/loot_query_yubikey_spec.rb +55 -0
  152. data/spec/lib/pwn/ai/agent/tools/recon_run_yubikey_spec.rb +43 -0
  153. data/spec/lib/pwn/ai/agent/tools/workspace_tool_spec.rb +20 -0
  154. data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +7 -0
  155. data/spec/lib/pwn/ai/anthropic_vault_enrollment_spec.rb +37 -0
  156. data/spec/lib/pwn/ai/cli_spec.rb +6 -9
  157. data/spec/lib/pwn/ai/oauth_vault_persistence_spec.rb +195 -0
  158. data/spec/lib/pwn/ai/open_ai_spec.rb +2 -2
  159. data/spec/lib/pwn/ai/open_web_ui_workspace_spec.rb +36 -0
  160. data/spec/lib/pwn/ai/router_spec.rb +1 -1
  161. data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
  162. data/spec/lib/pwn/backups_spec.rb +114 -0
  163. data/spec/lib/pwn/config_redaction_yubikey_spec.rb +28 -0
  164. data/spec/lib/pwn/config_yubikey_spec.rb +92 -0
  165. data/spec/lib/pwn/migrate_spec.rb +29 -81
  166. data/spec/lib/pwn/migrate_yubikey_spec.rb +128 -0
  167. data/spec/lib/pwn/module_skills_spec.rb +8 -0
  168. data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +230 -0
  169. data/spec/lib/pwn/plugins/authentication_helper_native_yubikey_spec.rb +40 -0
  170. data/spec/lib/pwn/plugins/authentication_helper_yubikey_spec.rb +90 -0
  171. data/spec/lib/pwn/plugins/capability_broker/daemon_spec.rb +1 -1
  172. data/spec/lib/pwn/plugins/google_workspace_spec.rb +17 -35
  173. data/spec/lib/pwn/plugins/log_yubikey_spec.rb +47 -0
  174. data/spec/lib/pwn/plugins/preflight_checker_native_yubikey_spec.rb +19 -0
  175. data/spec/lib/pwn/plugins/preflight_checker_yubikey_spec.rb +55 -0
  176. data/spec/lib/pwn/plugins/recon_yubikey_spec.rb +43 -0
  177. data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +110 -75
  178. data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
  179. data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +19 -16
  180. data/spec/lib/pwn/plugins/repl/ai_vault_edit_yubikey_spec.rb +103 -0
  181. data/spec/lib/pwn/plugins/repl/ai_yubikey_spec.rb +48 -0
  182. data/spec/lib/pwn/plugins/repl/mesh_vault_persistence_spec.rb +149 -0
  183. data/spec/lib/pwn/plugins/repl/vault_yaml_serialization_spec.rb +11 -0
  184. data/spec/lib/pwn/plugins/repl/vault_yubikey_spec.rb +39 -0
  185. data/spec/lib/pwn/plugins/repl_spec.rb +56 -5
  186. data/spec/lib/pwn/plugins/transparent_browser_jev_spec.rb +46 -0
  187. data/spec/lib/pwn/plugins/vault_native_yubikey_spec.rb +29 -0
  188. data/spec/lib/pwn/plugins/vault_publication_safety_spec.rb +258 -0
  189. data/spec/lib/pwn/plugins/vault_store_safety_spec.rb +241 -0
  190. data/spec/lib/pwn/plugins/vault_yubikey_safety_spec.rb +168 -0
  191. data/spec/lib/pwn/plugins/vault_yubikey_spec.rb +128 -0
  192. data/spec/lib/pwn/plugins/vault_yubikey_vector_spec.rb +45 -0
  193. data/spec/lib/pwn/plugins/yubikey/fido_spec.rb +854 -0
  194. data/spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb +235 -0
  195. data/spec/lib/pwn/plugins/yubikey/management_spec.rb +277 -0
  196. data/spec/lib/pwn/plugins/yubikey/oath_spec.rb +349 -0
  197. data/spec/lib/pwn/plugins/yubikey/openpgp_spec.rb +810 -0
  198. data/spec/lib/pwn/plugins/yubikey/otp_spec.rb +337 -0
  199. data/spec/lib/pwn/plugins/yubikey/piv_spec.rb +646 -0
  200. data/spec/lib/pwn/plugins/yubikey/protocol_secure_spec.rb +28 -0
  201. data/spec/lib/pwn/plugins/yubikey/protocol_spec.rb +149 -0
  202. data/spec/lib/pwn/plugins/yubikey/protocol_vectors_spec.rb +18 -0
  203. data/spec/lib/pwn/plugins/yubikey/security_domain/scp_spec.rb +256 -0
  204. data/spec/lib/pwn/plugins/yubikey/security_domain/support.rb +153 -0
  205. data/spec/lib/pwn/plugins/yubikey/security_domain_spec.rb +296 -0
  206. data/spec/lib/pwn/plugins/yubikey/transport_spec.rb +760 -0
  207. data/spec/lib/pwn/plugins/yubikey_security_spec.rb +260 -0
  208. data/spec/lib/pwn/plugins/yubikey_spec.rb +209 -0
  209. data/spec/lib/pwn/plugins/yubikey_terminal_spec.rb +147 -0
  210. data/spec/lib/pwn/redaction_yubikey_spec.rb +61 -0
  211. data/spec/lib/pwn/session_cleanup_default_spec.rb +7 -5
  212. data/spec/lib/pwn/session_cleanup_payload_spec.rb +11 -11
  213. data/spec/lib/pwn/session_cleanup_recovery_spec.rb +34 -20
  214. data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
  215. data/spec/lib/pwn/setup_vault_yubikey_spec.rb +61 -0
  216. data/spec/lib/pwn/setup_yubikey_spec.rb +40 -0
  217. data/spec/lib/pwn/www/login_yubikey_spec.rb +273 -0
  218. data/spec/spec_helper.rb +15 -0
  219. data/spec/support/harness/cli_faults.rb +13 -9
  220. data/spec/support/harness/controller_spill.rb +26 -0
  221. data/spec/support/harness/controller_state.rb +54 -0
  222. data/spec/support/harness/evaluate.rb +12 -1
  223. data/spec/support/harness/experiments.rb +1 -1
  224. data/spec/support/harness/fake_provider.rb +22 -6
  225. data/spec/support/harness/fault_preload.rb +3 -1
  226. data/spec/support/harness/mission_console.rb +54 -0
  227. data/spec/support/harness/mission_curses_pty.rb +41 -0
  228. data/spec/support/harness/mission_dispatch.rb +30 -0
  229. data/spec/support/harness/mission_fixture_preload.rb +24 -0
  230. data/spec/support/harness/mission_job_lifetime.rb +25 -0
  231. data/spec/support/harness/mission_legacy.rb +25 -0
  232. data/spec/support/harness/mission_normal_exit.rb +34 -0
  233. data/spec/support/harness/mission_swarm.rb +23 -0
  234. data/spec/support/harness/mission_unsupported.rb +29 -0
  235. data/spec/support/harness/package_acceptance.rb +33 -5
  236. data/spec/support/harness/session_acceptance.rb +4 -4
  237. data/spec/support/harness/swarm_acceptance.rb +1 -1
  238. data/spec/support/harness/swarm_control.rb +44 -0
  239. data/spec/support/mission_namespace.rb +14 -0
  240. data/spec/support/mission_pipe_queue.rb +33 -0
  241. data/third_party/pwn_rdoc.jsonl +589 -19
  242. metadata +129 -13
@@ -0,0 +1,134 @@
1
+ # Mission process lifetime and cancellation
2
+
3
+ ## Implemented boundary
4
+
5
+ The curses ordinary-request path, local slash-command workers, each curses
6
+ swarm job, and the non-curses `pwn_ai_run_steerable` entrypoint execute provider,
7
+ controller, and tool work
8
+ in a mission-owned Linux PID namespace. CTRL+C kills
9
+ namespace PID 1 through a pidfd, waits for its supervisor to reap it, joins the
10
+ IPC reader, and only then releases the UI's running state. A process group is
11
+ not the ownership boundary: setsid, double forks, and nested child namespaces
12
+ remain descendants of the mission namespace. Other sessions and unrelated
13
+ processes are not signalled.
14
+
15
+ There is no systemd, cgroup, application compiler, or PGID fallback.
16
+ `PWN::AI::Agent::MissionNamespace` is Ruby code using stdlib Fiddle for Linux pidfds and
17
+ process-death signals. Its runtime prerequisite is **util-linux `unshare`**
18
+ (`pwn setup --profile ai`). Linux user/PID namespaces and pidfds must be enabled;
19
+ unsupported kernels or local policy fail closed with setup guidance.
20
+
21
+ Ruby recreates its timer thread after fork, so creating a user namespace from a
22
+ Ruby fork is not supported. For ordinary launches the controller **execs** an
23
+ `unshare --user --map-current-user --keep-caps` supervisor. That fresh Ruby
24
+ supervisor creates a PID namespace and forks its init. It passes a pidfd back
25
+ through a private Unix socket; the init waits for acknowledgement before running
26
+ any request. Killing the supervisor also kills its init through PDEATHSIG.
27
+ The controller, its cwd, and unrelated embedded PWN REPL modes are unchanged.
28
+
29
+ Curses/native, legacy, slash, swarm and one-shot CLI requests use named,
30
+ serializable descriptors, not serialized Ruby closures. Configuration, loaded
31
+ skill metadata and routing context travel as typed JSON over inherited private
32
+ sockets, never in argv or plaintext staging files. RequestRuntime's tool-worker
33
+ snapshot excludes locks and caller cancellation callbacks; the enclosing owner
34
+ provides cancellation. Replies and events remain JSON with generation fencing.
35
+ Loaded Ruby skill scripts are required in the worker. Embedders with additional
36
+ trusted plugin initializers can explicitly set
37
+ `PWN::AI::Agent::MissionNamespace.bootstrap_requires = ['/absolute/plugin/bootstrap.rb']`.
38
+ Arbitrary monkey patches, IO objects and Proc values are not serializable.
39
+
40
+ For applications explicitly launched in a single-identity user namespace,
41
+ MissionNamespace can fork its PID supervisor directly. This preserves trusted
42
+ in-memory Ruby closures without calling unshare(NEWUSER) from Ruby. The caller
43
+ must bootstrap explicitly with `unshare --user --map-current-user --keep-caps`;
44
+ there is no automatic re-exec of an embedded application. The unit-test runner
45
+ uses this real-kernel mode for in-memory mocks; exec/PTy fixtures explicitly
46
+ force the ordinary descriptor path. Both paths create a real per-mission PID
47
+ namespace and reject unavailable containment.
48
+
49
+ Ordinary-request events carry a generation; swarm child messages carry job
50
+ IDs, and console swarm callbacks carry an epoch invalidated by CTRL+C.
51
+ Provider calls, including ordinary-request title refresh, remain in the child.
52
+ Steering uses a separate JSON control pipe. The UI never deserializes Ruby
53
+ objects from the child.
54
+
55
+ ## Detached jobs
56
+
57
+ Detached jobs started by a tool inside a mission inherit that mission's
58
+ namespace. They can continue after the individual tool returns, but **not**
59
+ after mission completion, cancellation, or namespace-owner death. Normal
60
+ mission completion also tears down remaining detached descendants. This is
61
+ intentionally different from cross-session durable jobs. A standalone
62
+ `AISandbox.protect_call` without an enclosing mission creates its own namespace
63
+ and ends that lifetime when the call ends.
64
+
65
+ Existing files and already-submitted remote operations are not rolled back.
66
+ The local process guarantee does not revoke work accepted by an external
67
+ service or remote host. Kernel-blocked uninterruptible I/O may delay reaping;
68
+ the UI must remain busy rather than claim readiness early.
69
+
70
+ ## Controller-state separation
71
+
72
+ Tool workers are denied writes to their RequestRuntime artifact/checkpoint
73
+ root, including its spill directories and inherited descriptors. The actual
74
+ worker-symlink -> parent-spill reproducer now fails at the worker's symlink
75
+ with permission denied, and the synthetic source directory remains unchanged.
76
+ The audited controller stores (sessions/traces, transcript artifacts,
77
+ learning/policy/metrics, skills, logs, vault/configuration and mission state) are
78
+ also excluded from worker grants. See the finite writer checklist in
79
+ [Runtime-Source-Protection](Runtime-Source-Protection.md). Unspecified tool
80
+ writes stay in `~/.pwn/workspaces/runtime`; another path is allowed only when
81
+ the original operator request names it. Naming a missing file grants its parent,
82
+ including when that parent contains unrelated unreadable directories, so the
83
+ named file can be created. Controller state and tool output
84
+ workspaces must be disjoint: a parent directory containing protected state
85
+ cannot safely receive a broad Landlock write grant.
86
+
87
+ ## Verification and remaining scope
88
+
89
+ RSpec owns the subprocess fixtures under `spec/support/harness/mission_*.rb`.
90
+ `spec/integration/mission_cancel_spec.rb` exercises ordinary console next-request
91
+ readiness and generation fencing, blocked-provider descendants, setsid escape,
92
+ normal-exit double forks, unrelated-process preservation, mission-lived tool
93
+ jobs, swarm cancellation, and fail-closed bootstrap. The namespace-denial
94
+ fixture injects EPERM; namespace creation/teardown tests use the real kernel.
95
+ `spec/integration/controller_spill_spec.rb` exercises actual RequestRuntime,
96
+ Dispatch, sandbox restriction, and parent spill persistence with synthetic paths.
97
+
98
+ Local slash-command maintenance/model/MCP workers now use the same hard process
99
+ ownership. Their completion and UI-state messages are generation-fenced JSON;
100
+ CTRL+C reaps the owner before returning ready. Maintenance already committed
101
+ before cancellation is not rolled back. Model persistence decrypts and encrypts
102
+ in memory; its staging file contains only ciphertext (or is empty). An offline
103
+ SIGKILL regression interrupts encryption startup and verifies unchanged original
104
+ ciphertext and absence of plaintext in the abandoned stage. Native curses PTY
105
+ coverage includes cancellation during a blocked local command and a successful
106
+ next mission.
107
+
108
+ The legacy regex-ReAct fallback now runs its provider/parser/tool loop in the
109
+ same MissionProcess owner. Completed history is transferred by JSON; native
110
+ containment failures are terminal rather than an excuse to retry uncontained.
111
+ Its actual after_read hook and PTY fixture cover detached Ruby/bash descendants,
112
+ CTRL+C, no late writes and a successful next request. Workspace policy
113
+ clarification does not block these offline gates.
114
+
115
+ Native PTY regressions drive actual terminal CTRL+C through both the curses
116
+ console and `pwn_ai_run_steerable`, assert no late descendant writes, then complete
117
+ a second mission. Compiler-free package acceptance builds and installs into a
118
+ private scratch GEM_HOME with compiler/make commands replaced by failing shims,
119
+ activates that exact specification, checks an empty extensions list and no
120
+ mission shared object, then executes a descriptor. Root and nonroot acceptance
121
+ must be reported separately; these are offline checks, not provider or hardware
122
+ evidence.
123
+
124
+ Root loses host-wide DAC override when it enters a user namespace. A host-root
125
+ mission therefore cannot traverse another user's mode-0700 home merely because
126
+ its parent could. Test roots and deployment workspaces must be traversable by
127
+ the mapped host identity. Do not chmod the operator's home to make a fixture
128
+ pass. Filesystem fixtures beneath a private nonroot HOME must use that identity,
129
+ including when their outer test runner is root. The integrated root fixture uses
130
+ an isolated mount namespace: only its task-owned scratch layout is bound at a
131
+ root-traversable path, with HOME, TMPDIR and inherited stdout/stderr logs opened
132
+ inside that layout. Inherited logs opened below the inaccessible real home would
133
+ correctly fail the protected-descriptor preflight. Neither the host mount table
134
+ nor the operator's directory permissions are changed.
@@ -57,6 +57,23 @@ PKI DMs remain unsupported. Topic `region` is an opaque prefix, not the radio's
57
57
  regulatory-band setting; `radio_index` is distinct from legacy `channel_num`.
58
58
  See [General Usage](General-PWN-Usage.md) and [REPL](pwn-REPL.md).
59
59
 
60
+ ## Current runtime contract
61
+
62
+ These are source contracts. A checkout edit does not change an installed gem or
63
+ a process that is already running. Restart after installing the matching package.
64
+
65
+ | Contract | Current behavior |
66
+ | --- | --- |
67
+ | Tool writes | Unspecified `pwn-ai` writes and `TMPDIR` use `~/.pwn/workspaces/runtime`. Another path is writable only when the original operator request names it, including `mv`/`to`. A missing file grants its parent so the file can be created; that parent is still granted when it contains unrelated unreadable directories. `/opt/pwn` and the loaded package stay read-only. |
68
+ | Mission lifetime | CTRL+C kills the mission through `PWN::AI::Agent::MissionNamespace`. That owner is Ruby plus util-linux `unshare` and Linux pidfds. There is no compiled `pwn_mission` extension and no systemd fallback. |
69
+ | Recovery copies | Every PWN recovery producer uses `PWN::Backups` and `~/.pwn/backup/`, seven generation folders total. Adjacent `.bak` files are not the current producer. |
70
+ | Housekeeping | A fresh install seeds one internal `session_cleanup` coordinator. Curriculum jobs are not seeded. Housekeeping never uses systemd. |
71
+
72
+ Details: [Runtime Source Protection](Runtime-Source-Protection.md),
73
+ [Mission Cancellation](Mission-Cancellation.md),
74
+ [Backup Retention](Backup-Retention.md), [Cron](Cron.md),
75
+ [Persistence](Persistence.md).
76
+
60
77
  ## Deployment and safe verification
61
78
 
62
79
  1. Inspect the intended checkout and preserve existing changes before upgrading.
@@ -7,8 +7,8 @@ external services and environment overrides can place evidence elsewhere.
7
7
 
8
8
  | Path | Owner | Format | Reset tool | Purpose |
9
9
  |---|---|---|---|---|
10
- | `pwn.yaml` | `PWN::Config` · `PWN::Plugins::Vault` | AES-encrypted YAML | `pwn-vault` | engines, keys, agent options |
11
- | `pwn.yaml.decryptor` | `PWN::Plugins::Vault` | key/IV | - | decrypts `pwn.yaml` (or set `PWN_DECRYPTOR_KEY`/`_IV`) |
10
+ | `pwn.yaml` | `PWN::Config` · `PWN::Plugins::Vault` | Legacy AES-CBC or authenticated YubiKey-derived AES-GCM | `pwn-vault` | engines, keys, agent options; see [hardware vaults](YubiKey-Vaults.md) |
11
+ | `pwn.yaml.decryptor` | `PWN::Plugins::Vault` | Legacy key/IV | - | decrypts legacy `pwn.yaml` (or set `PWN_DECRYPTOR_KEY`/`_IV`); absent for hardware-only vaults |
12
12
  | **`.schema`** | **`PWN::Migrate`** | JSON `{schema:,pwn_version:,at:}` | `pwn setup --migrate` | **schema stamp - drives the one-line drift warning on launch** |
13
13
  | `memory.json` | `PWN::Memory` | JSON array | `memory_clear` | facts · prefs · lessons · env - injected into every prompt |
14
14
  | `memory.idx` | `PWN::MemoryIndex` | JSON `{key → {sha,vec}}` | `PWN::MemoryIndex.reset` | local embedding index over `memory.json` - powers **relevance-ranked** MEMORY injection (incremental; only re-embeds changed entries) |
@@ -34,7 +34,8 @@ external services and environment overrides can place evidence elsewhere.
34
34
  | `curriculum/` | `PWN::AI::Agent::Curriculum` | JSONL | rm -rf | self-play reproducers · train/gate A/B eval sets |
35
35
  | `curriculum_kpi.jsonl` | `PWN::AI::Agent::Curriculum.practice_kpi` | JSONL | rm | outer curriculum KPI snapshots (repeating-mistake trend) |
36
36
  | `finetune/*.jsonl` | `Learning.export_finetune` · `Reward.export_dpo` | ShareGPT / OpenAI / DPO JSONL | `rm` | supervised + preference datasets - feed to a LoRA over the local model |
37
- | `backup/<ts>/` | `PWN::Migrate` | tree copy | rm -rf | timestamped snapshot taken before every `--migrate --fix` |
37
+ | `workspaces/runtime/` | `PWN::Plugins::AISandbox` · `SessionCleanup.temporary` | files | operator-managed | Unspecified `pwn-ai` writes, `TMPDIR`, and registered disposable inputs such as Festival speech. Another path is writable only when the original request names it. `/opt/pwn` stays read-only |
38
+ | `backup/<generation>/` | `PWN::Backups` | shared generations | see [Backup Retention](Backup-Retention.md) | One store, seven generation folders total, for every recovery producer including migrate |
38
39
  | `quarantine/` | `PWN::Migrate` | quarantined originals | rm -rf | corrupt/incompatible state files moved aside so the owner re-seeds |
39
40
  | `pwn_history` | Pry / pwn-ai | text | operator-managed | Shared durable input history at `~/.pwn/pwn_history` |
40
41
 
@@ -69,8 +70,9 @@ for the full `PWN::Migrate` API.
69
70
  tar czf pwn-state-$(date +%F).tgz -C "$HOME" .pwn
70
71
  ```
71
72
 
72
- `pwn setup --migrate --fix` also writes one automatically to
73
- `~/.pwn/backup/<timestamp>/` before touching anything.
73
+ `pwn setup --migrate --fix` admits one generation through `PWN::Backups` under
74
+ `~/.pwn/backup/` before it changes state. That store holds seven generations
75
+ total, not a new unbounded tree per run. See [Backup Retention](Backup-Retention.md).
74
76
 
75
77
  ## Start fresh for a new engagement
76
78
 
@@ -75,6 +75,18 @@ Required: `searchsploit`. Fallback: `intel_lookup`.
75
75
 
76
76
  Required: `subfinder`, `httpx`. Fallback: `NmapIt.scan` + `Corpus.get`.
77
77
 
78
+ ## pwn-plugins-yubikey
79
+
80
+ Required: `ykman` (YubiKey Manager CLI). Preflight checks executable presence
81
+ only; it never opens a device, requests a PIN, or performs a challenge. An `ok`
82
+ status does not establish device availability, provisioning, or authorization.
83
+ Hardware-backed vault operations require the selected YubiKey and its existing
84
+ HMAC-SHA1 OTP slot; missing devices, denied touch, or authentication failures
85
+ raise without falling back to a disk master key or software MFA. Restore device
86
+ access before retrying. Dispatch blocks the handler when vault expansion fails;
87
+ if output redaction fails after execution, it withholds the result and reports
88
+ that the handler executed, so verify its effects before retrying.
89
+
78
90
  ## pwn-plugins-credentialattack
79
91
 
80
92
  Required: `hydra`, `john`, `hashcat`. Fallback: `pwn_eval` + `Corpus`.
@@ -1,4 +1,4 @@
1
- # `PWN::Plugins` - All 107 Modules
1
+ # `PWN::Plugins` - All 108 Modules
2
2
 
3
3
  Every plugin is a plain Ruby module of `public_class_method def self.x(opts = {})`
4
4
  methods with self-documenting `.help`. Source: `lib/pwn/plugins/*.rb`.
@@ -64,6 +64,7 @@ show-source PWN::Plugins::Fuzz.http
64
64
  |---|---|
65
65
  | `OAuth2` · `BasicAuth` · `AuthenticationHelper` | credential flows |
66
66
  | `Vault` | Local encrypted configuration and engagement loot helpers; not a HashiCorp Vault client |
67
+ | [`YubiKey`](YubiKey.md) | Guarded ykman operations, HMAC-backed authenticated vaults and OATH codes |
67
68
 
68
69
  ### Hardware / Physical
69
70
  | Module | Purpose | Doc |
@@ -97,6 +98,6 @@ show-source PWN::Plugins::Fuzz.http
97
98
 
98
99
  ## Full alphabetical list
99
100
 
100
- `AFLplusplus AISandbox Android ArtifactRegistry Assembly AuthenticationHelper BareSIP BasicAuth BeEF BinaryAnalysis BinaryParser BinWorkspace BlackDuckBinaryAnalysis BurpSuite BusPirate Capability CapabilityBroker Char CredentialAttack CreditCard DAOLDAP DAOMongo DAOPostgres DAOSQLite3 Debugger DefectDojo DetectOS Detonate EIN Emulator ExploitDB ExploitDev FileFu Findings Frida Fuzz GDB GDBMI GDBMi Ghidra GhidraHeadless Git Github GoogleWorkspace HackerOne Handler Httpx Hunter IPInfo IRC Jenkins JiraDataCenter Jobs JSONPathify K8s Log MailAgent Metasploit MethodCatalog MitmProxy MonkeyPatch MSR206 NessusCloud NexposeVulnScan NmapIt Nuclei OAuth2 OCR OpenAPI OpenVAS Packet PDFParse Pony PreflightChecker ProcessTube PS PWNLogger RabbitMQ Radare2 Recon REDB REPL ROP Sandbox SBOM ScannableCodes Semgrep Serial Shodan SlackClient Sock Spider Sqlmap SSN ThreadPool Tor TransparentBrowser TTYSpinner TwitterAPI URIScheme Vault VIN Voice Volatility Vsphere XXD Zaproxy`
101
+ `AFLplusplus AISandbox Android ArtifactRegistry Assembly AuthenticationHelper BareSIP BasicAuth BeEF BinaryAnalysis BinaryParser BinWorkspace BlackDuckBinaryAnalysis BurpSuite BusPirate Capability CapabilityBroker Char CredentialAttack CreditCard DAOLDAP DAOMongo DAOPostgres DAOSQLite3 Debugger DefectDojo DetectOS Detonate EIN Emulator ExploitDB ExploitDev FileFu Findings Frida Fuzz GDB GDBMI GDBMi Ghidra GhidraHeadless Git Github GoogleWorkspace HackerOne Handler Httpx Hunter IPInfo IRC Jenkins JiraDataCenter Jobs JSONPathify K8s Log MailAgent Metasploit MethodCatalog MitmProxy MonkeyPatch MSR206 NessusCloud NexposeVulnScan NmapIt Nuclei OAuth2 OCR OpenAPI OpenVAS Packet PDFParse Pony PreflightChecker ProcessTube PS PWNLogger RabbitMQ Radare2 Recon REDB REPL ROP Sandbox SBOM ScannableCodes Semgrep Serial Shodan SlackClient Sock Spider Sqlmap SSN ThreadPool Tor TransparentBrowser TTYSpinner TwitterAPI URIScheme Vault VIN Voice Volatility Vsphere XXD YubiKey Zaproxy`
101
102
 
102
103
  [← Home](Home.md) · [Diagrams](Diagrams.md)
@@ -0,0 +1,162 @@
1
+ # Runtime source protection
2
+
3
+ > The mandatory worker boundary protects source and separates the finite
4
+ > controller-state inventory below from tool writes. Checkpoint persistence also
5
+ > uses pinned fd-relative no-follow directory operations. Process containment
6
+ > owns detached tool jobs until mission completion/cancellation; it is not a
7
+ > substitute for filesystem protection. Unspecified tool writes use
8
+ > `~/.pwn/workspaces/runtime`. A path is writable outside that directory only
9
+ > when the original operator request names it. An explicit destination grants
10
+ > its parent even when that parent contains unrelated unreadable directories,
11
+ > so a new file can be created there. Source checkouts stay read-only
12
+ > even if named. See [Mission-Cancellation](Mission-Cancellation.md).
13
+
14
+ A pwn-ai RequestRuntime dispatch executes its registered handler in an AISandbox
15
+ worker. This boundary is mandatory even when the optional `ai_sandbox` profile
16
+ is `off`. It covers shell, Ruby evaluation, other registered handlers, nested
17
+ Dispatch calls, and fork/exec descendants including durable Jobs supervisors.
18
+ Direct library calls made by an operator outside a RequestRuntime are not AI
19
+ runtime tool calls and are unchanged. Legacy regex-ReAct Ruby and shell blocks
20
+ also use the mandatory worker boundary (inside their enclosing mission); they
21
+ no longer evaluate directly in the mission controller. The source-denial
22
+ regression drives both languages through the actual Pry after_read hook.
23
+
24
+ The host protects `/opt/pwn` when present and the real source root containing the
25
+ loaded package. Linux Landlock ABI 3 or newer denies content writes, truncation,
26
+ creation, removal, and linking under those roots. It resolves symlinks and `..`
27
+ in the kernel: scripts are not rewritten. Repo reads remain available. The UI
28
+ process does not change cwd. Relative reads use the task directory when it is
29
+ outside protected source; otherwise they use `~/.pwn/workspaces/runtime`.
30
+ Unnamed new files and temporary files go to that runtime directory. `/tmp`, the
31
+ launch directory, and the rest of `~/.pwn` are not default write grants. A path
32
+ named by the original operator request is created and granted; naming a file
33
+ grants its parent so the file can be created. That parent grant is a directory
34
+ rule, so a new file can still be created when the parent contains unrelated
35
+ unreadable directories. Do not create the missing destination as a directory.
36
+ Protected source is never granted.
37
+ Standard null/zero/random devices are available. Other filesystem writes fail
38
+ closed. This bounded write set permits hardlink-alias exclusion without scanning
39
+ all mounted filesystems or changing checkout permissions.
40
+
41
+ ## Fail-closed requirements and restrictions
42
+
43
+ - Supported syscall ABIs are Linux x86_64 and aarch64; libseccomp is required.
44
+ Missing enforcement prevents handler execution, rather than falling back to
45
+ regex checks or unsandboxed execution.
46
+ - Landlock does not mediate file metadata changes. The worker therefore denies
47
+ chmod/chown, timestamp and extended-attribute mutation system-wide, including
48
+ outside source. Ordinary outside content writes and file creation still work.
49
+ Tools that need those metadata operations require a different supported
50
+ execution environment, not an exception to source protection.
51
+ - Mount/namespace manipulation, ptrace/process-memory writes, and io_uring setup
52
+ are denied. Existing descriptors into protected roots are closed in the child.
53
+ - Host preflight inventories multiply linked protected file identities and
54
+ excludes matching aliases in every writable tree. Aliases elsewhere receive
55
+ no write grant. Alias inventories are cached against source inode/link-count/
56
+ ctime fingerprints and writable-root identity; source link changes invalidate
57
+ that cache. Unreadable subtrees encountered during alias scanning are excluded.
58
+ This includes generated rdoc assets linked to installed gem assets; no source
59
+ files are chmodded, deleted, or automatically rewritten.
60
+ - Protected root ancestors cannot grant inherited write permission without also
61
+ granting it to source. Creating a new sibling directly in such an ancestor is
62
+ consequently denied; use an existing writable outside directory.
63
+ - This is a local process/filesystem effect boundary, not isolation of remote
64
+ services or already-running privileged daemons commanded over IPC/network.
65
+ It does not defend against a concurrent external actor replacing policy paths
66
+ or introducing new hardlink aliases after preflight. Do not run untrusted
67
+ external writers against the protected installation.
68
+ - Handler-local Ruby variables and in-memory plugin state do not propagate back
69
+ to the UI. Persistent outputs and durable job state do. A handler that requires
70
+ process-local state across calls must not assume the old in-process evaluator.
71
+
72
+ ## Sandboxed swarm control
73
+
74
+ `agent_spawn` and `agent_ask` handlers still run inside the mandatory worker.
75
+ Their Swarm calls use a private socket to request only the controller's typed
76
+ `spawn`/`ask` operations. The controller checks request tool permissions, rejects
77
+ unknown fields/operations and traversal identifiers, and never invokes a
78
+ worker-selected Registry handler, callback, Ruby expression, or filesystem path.
79
+ Nested persona tools pass through Dispatch and get their own mandatory workers.
80
+
81
+ Before launch the controller provisions the fixed swarm directory, persona file,
82
+ and continuation lock with no-follow ancestry and regular-file/single-link
83
+ checks. Workers cannot write the persona file, swarm subtree, or request
84
+ checkpoint tree, nor rename their ancestors. No parent-directory write grant is
85
+ added. Child Loop receipts remain controller-observed and are rechecked when
86
+ merged into the parent. This preserves sandboxed spawn/ask state creation without
87
+ allowing arbitrary controller callbacks.
88
+
89
+ ## Finite parent-writer audit
90
+
91
+ The worker deny set now includes these controller-owned stores, even before a
92
+ store exists. Missing paths still split the ancestor's Landlock grant, preventing
93
+ first-write symlink planting. Existing inherited descriptors and hardlink aliases
94
+ are covered by the same preflight as source. Store constants are resolved in the
95
+ controller, not from model/tool output. Both lexical paths and canonical store
96
+ aliases are denied, so a configured symlink cannot expose controller state
97
+ through another otherwise writable directory.
98
+
99
+ | Parent writer | Protected state |
100
+ | --- | --- |
101
+ | RequestRuntime checkpoints and EngagementMemory | Request-specific artifact root; checkpoint lock/temp/final persistence is also fd-relative/no-follow |
102
+ | Sessions and SessionTrace | Sessions root, including support, leases, traces and requests |
103
+ | Loop transcript compaction, ArtifactRegistry, swarm bus spills | Default artifacts root and configured ArtifactRegistry root |
104
+ | Learning, Policy, Metrics, Reward, Mistakes, ToolGuard | Declared files/directories, learning and metrics subtrees; absent sibling staging paths cannot be planted |
105
+ | Memory and MemoryIndex | Memory, archive and index files |
106
+ | OpenGoal, Mission and TaskDAG | Goal file, missions and runs roots |
107
+ | SkillReview and Curriculum | Ledger, curriculum/finetune roots, default/configured skill directories and loaded skill parents |
108
+ | Provider credential persistence and local model configuration | Default vault/decryptor and configured driver paths |
109
+ | Session redaction's Vault persistence | Engagements tree, legacy vault file and master-key path |
110
+ | Log and Extrospection | Default logs and declared Extrospection stores |
111
+ | Typed swarm controller | Swarm subtree, personas and continuation lock |
112
+
113
+ The regression first reproduced an actual worker redirect of the transcript
114
+ spill directory: after the worker returned, Loop created a file in synthetic
115
+ protected source. It now receives permission denied, as does the earlier
116
+ RequestRuntime spill reproducer. An independent fixture attempts 29 state
117
+ mutations across existing directories/files and an absent staging path in both
118
+ ordinary and symlinked-store layouts, while also creating a legitimate output in
119
+ a disjoint workspace.
120
+
121
+ Tools that directly mutate these controller stores are deliberately denied;
122
+ only spawn/ask currently have the typed broker exception. This is not a promise
123
+ that arbitrary in-process stateful plugins retain their old behavior. New parent
124
+ writers or custom stores require an inventory update. The guarantee assumes a
125
+ trusted initial controller layout and excludes concurrent external filesystem
126
+ writers as described above; it is not a general host sandbox.
127
+
128
+ ## Completion evidence and uncertainty
129
+
130
+ Required artifact names come from the original operator request (including
131
+ explicit amendments), not cached model guesses or tool transcripts. Unquoted
132
+ prose such as `i.e.` does not create a required file named `i.e`; explicitly
133
+ quoted filenames are still honored.
134
+
135
+ RequestRuntime snapshots required artifacts before dispatch and, on success,
136
+ reads their stat, bounded head/tail and SHA-256 from the host. Successful actual
137
+ file deltas count even if payload classification called the tool `eval` rather
138
+ than `write`. Receipts accumulate across separate calls/iterations and survive
139
+ transcript compaction. A failed tool, unchanged stale file, or tool stdout claim
140
+ cannot create a receipt. Current bytes/identity must still match at completion.
141
+ A combined write/read call does not require a ritual second call.
142
+
143
+ Unknown side effects remain replay-fenced. A timeout/interruption or failed
144
+ mutation is not erased by unrelated successful artifacts. On an attempted final
145
+ answer the loop returns a structured `blocked` outcome with action ID and payload
146
+ hash, instead of endlessly asking for rewrites. Exact-target operator-approved
147
+ reconciliation is still required for genuine uncertainty; the existing HTTP
148
+ reconciliation API is unchanged. Kernel/preflight failure before the handler
149
+ starts is recorded as `rejected`, not falsely labeled an uncertain mutation.
150
+
151
+ ## Verification and deployment
152
+
153
+ Offline specs exercise actual kernel workers, symlink/parent traversal denial,
154
+ metadata denial, nested Dispatch, a detached Jobs worker, fail-closed unsupported
155
+ protection/hardlinks, and the Loop → Dispatch → cumulative host receipt path with
156
+ an actual generated PDF and separate text output. Provider messages in that
157
+ replay are fixtures, not a live remote-model test.
158
+
159
+ Source changes do not update an installed gem or a running pwn-ai process.
160
+ Deploy the matching source/package files and restart the remote process through
161
+ the operator's normal deployment procedure. Inspect the reported root/hardlink
162
+ and kernel requirements before claiming that host is operational.
@@ -52,5 +52,16 @@ All four honor `proxy:` (Burp / `tor`) so attribution stays controlled, reuse
52
52
  **one** browser handle across anchors, and `close` in an `ensure`. See
53
53
  [Extrospection](Extrospection.md) for the full loop.
54
54
 
55
+ ## Jev action loop
56
+
57
+ `act` and `browser_act` run only when Open WebUI is configured and its model
58
+ list contains a Jev model. Jev receives the visible control table and returns
59
+ one operation plus one index: `CLICK`, `TYPE_TEXT`, `SELECT`, `SCROLL_UP`,
60
+ `SCROLL_DOWN`, `WAIT`, `DONE`, or `BLOCKED`. It does not receive screenshots
61
+ and it does not emit selectors. If the active pwn-ai model is not Jev, that
62
+ model still writes the field text for `TYPE_TEXT`. Other pwn-ai work stays on
63
+ the active model. This is the indexed-action idea from
64
+ [jev-ultrafast](https://github.com/browser-use/jev-ultrafast), not TypeSafe's
65
+ multi-head API and not a claim of its measured latency.
55
66
 
56
67
  [← Home](Home.md) · [BurpSuite](BurpSuite.md) · [WWW](WWW.md) · [Extrospection](Extrospection.md)
@@ -32,8 +32,9 @@ $ pwn setup --migrate --dry-run # report only
32
32
  ```
33
33
 
34
34
  Corrupt / incompatible files are moved to `~/.pwn/quarantine/` (never
35
- deleted); a full timestamped backup is written to `~/.pwn/backup/<ts>/`
36
- first. Missing config keys added by the new release are deep-merged into
35
+ deleted). Recovery copies go to one shared generation under `~/.pwn/backup/`
36
+ first; the store keeps seven generations total. See
37
+ [Backup Retention](Backup-Retention.md). Missing config keys added by the new release are deep-merged into
37
38
  your encrypted `~/.pwn/pwn.yaml` **under** your existing values. See
38
39
  [Installation § Upgrading](Installation.md#upgrading--pwn-state-migration-pwnmigrate).
39
40
 
@@ -12,7 +12,7 @@ with a **tool-calling AI agent** on top that can run the same methods.
12
12
 
13
13
  | Namespace | Count | What it is |
14
14
  |---|---|---|
15
- | `PWN::Plugins::*` | **107** | Wrappers for external and native tooling (Burp, Nmap, Metasploit, Shodan, browsers, serial, ...) |
15
+ | `PWN::Plugins::*` | **108** | Wrappers for external and native tooling (Burp, Nmap, Metasploit, Shodan, browsers, serial, YubiKey, ...) |
16
16
  | `PWN::SAST::*` | **48** | Static-analysis rules across C/Java/Go/Python/Ruby/Scala/PHP/TS |
17
17
  | `PWN::AWS::*` | **90** | One module per AWS service for cloud enumeration |
18
18
  | `PWN::WWW::*` | **22** | Site-specific browser automations (HackerOne, BugCrowd, GitHub, Google, LinkedIn, ...) |
@@ -20,7 +20,7 @@ with a **tool-calling AI agent** on top that can run the same methods.
20
20
  | `PWN::FFI::*` | **11** | Native bindings (including DSPNative, Capstone and Keystone): Volk · Liquid · FFTW · RTLSdr · HackRF · AdalmPluto · SoapySDR · Stdio |
21
21
  | `PWN::AI::*` | **6** engines | OpenAI, Anthropic, Grok (OAuth device-flow), Gemini, Ollama, Open WebUI |
22
22
  | `bin/` (including `pwn-ai` and `pwn-capd`) | **56** | Headless CLI executables for CI/CD |
23
- | Agent toolsets | **16** · **150 tools** | terminal · pwn · memory · skills · sessions · learning · metrics · policy · extrospection · cron · swarm · reward · curriculum · http · mcp · manifest |
23
+ | Agent toolsets | **16** · **152 tools** | terminal · pwn · memory · skills · sessions · learning · metrics · policy · extrospection · cron · swarm · reward · curriculum · http · mcp · manifest |
24
24
 
25
25
  ## Three ways to use it
26
26
 
@@ -0,0 +1,183 @@
1
+ # YubiKey-backed vaults and authentication
2
+
3
+ See [YubiKey device operations](YubiKey.md) for the `ykman` command families,
4
+ provisioning safeguards and supported hardware/firmware boundaries.
5
+
6
+ ## Encryption model
7
+
8
+ This is **hardware-derived key encryption**, not bulk encryption inside the
9
+ YubiKey. An already provisioned OTP HMAC-SHA1 slot answers a fresh random
10
+ 32-byte challenge. Ruby derives a 32-byte encryption key using HKDF-SHA256 with
11
+ a fresh 16-byte salt and a domain-specific context, then uses AES-256-GCM with
12
+ a fresh nonce and a full 16-byte tag. The HMAC response and derived key exist
13
+ transiently in the Ruby process; the slot secret is not exported. Ruby does not
14
+ promise zeroization of copied strings.
15
+
16
+ The versioned `PWN-YUBIKEY-VAULT:1` envelope authenticates its metadata as AAD.
17
+ The public challenge, salt, slot and optional serial are stored with ciphertext,
18
+ not in a secret sidecar. Executable paths and timeout overrides are supplied
19
+ by the caller, never loaded from ciphertext. PINs, OTP codes and serial numbers
20
+ are not encryption keys. HMAC-SHA1 is used as the device PRF, not as an
21
+ unauthenticated document hash; it provides at most the slot/response security,
22
+ not 256 bits of entropy merely because AES-256 is used.
23
+
24
+ Existing Base64 AES-256-CBC files and software loot remain readable. New
25
+ hardware protection is opt-in; no existing configuration is migrated merely by
26
+ loading PWN. A hardware envelope never falls back to a software master key,
27
+ passphrase or unauthenticated plaintext when a device is missing or a tag fails.
28
+ Do not combine a software key/IV or passphrase/keyring with hardware selection.
29
+
30
+ ## Use an existing HMAC slot
31
+
32
+ Use a key model supporting OTP HMAC challenge-response. FIDO-only Security Keys
33
+ do not support this vault backend. Set up the slot explicitly using the device
34
+ management interface; prefer its physical-touch policy. PWN never provisions,
35
+ replaces, unlocks by guessing, or resets a slot in order to open a vault.
36
+
37
+ ```ruby
38
+ # The named file must already contain the plaintext you intend to encrypt.
39
+ PWN::Plugins::Vault.create(file: '/path/to/private.yaml', yubikey: { slot: 2 })
40
+
41
+ # With multiple devices, explicitly select the intended device serial.
42
+ # Serial is a placeholder; replace locally, never paste secrets into a transcript.
43
+ selector = { slot: 2, serial: '12345678' }
44
+ PWN::Plugins::Vault.encrypt(file: '/path/to/another.yaml', yubikey: selector)
45
+
46
+ # Read-only: decrypted data exists in memory, not an intermediate file.
47
+ config = PWN::Plugins::Vault.dump(file: '/path/to/private.yaml'); nil
48
+
49
+ # Mutate YAML in memory and atomically publish ciphertext, preserving the backend.
50
+ PWN::Plugins::Vault.update(file: '/path/to/private.yaml') do |document|
51
+ document[:setting] = 'replacement'
52
+ end
53
+ ```
54
+
55
+ `yubikey` is a Hash accepting `slot` (integer 1 or 2; default 2), optional
56
+ `serial`, optional local `ykman` executable path and bounded `timeout` seconds.
57
+ `dump`, `decrypt`, `open_credentials` and hardware loot retrieval otherwise use
58
+ the slot/serial stored in their envelope. Selecting another serial permits an
59
+ explicitly provisioned recovery key with the same slot secret; the authentication
60
+ tag still has to verify. A different slot requires explicit rewrapping.
61
+
62
+ `encrypt_data` / `decrypt_data` are in-memory String APIs, including arbitrary
63
+ binary data and empty plaintext. `seal_credentials` / `open_credentials` work
64
+ on JSON-compatible credential values. Neither API opens the live configuration
65
+ or writes keys. `file_encrypted?` / `hardware_encrypted?` inspect format only;
66
+ they do not prove the device is present or that a ciphertext authenticates.
67
+
68
+ `decrypt(file:)` explicitly replaces ciphertext with plaintext. Use `dump`
69
+ instead when plaintext-on-disk is unnecessary. `edit` explicitly gives a private
70
+ 0600 temporary copy to the chosen editor under `~/.pwn/workspaces/runtime`, then
71
+ removes its private working directory. The original stays encrypted until a
72
+ successful re-encryption and atomic replacement. Editors are trusted programs:
73
+ their external backups, OS swap, crash dumps and an abruptly killed process are
74
+ not a secure-erasure guarantee. The curses `/vault edit` editor works in memory
75
+ and writes only ciphertext. Built-in file writers share its parent-directory
76
+ advisory lock across the final baseline check and atomic rename. A stale edit
77
+ raises rather than overwriting a newer publication. This is not a filesystem
78
+ compare-and-swap guarantee against external writers that ignore the lock.
79
+
80
+ ## Conversion and recovery
81
+
82
+ Before converting a legacy vault, retain its verified encrypted backup and
83
+ legacy decryptor somewhere secure. Conversion is explicit:
84
+
85
+ ```ruby
86
+ PWN::Plugins::Vault.refresh_encryption_secrets(
87
+ file: '/path/to/legacy.yaml',
88
+ key: legacy_key, iv: legacy_iv,
89
+ new_yubikey: { slot: 2 }
90
+ )
91
+ ```
92
+
93
+ No temporary plaintext is published during conversion. On an existing hardware
94
+ vault, `refresh_encryption_secrets(file:)` rewraps with fresh challenge/salt/nonce;
95
+ it does **not** change the device secret. Supply `new_yubikey:` to move to another
96
+ already provisioned slot/device while the old one is available. An explicit
97
+ `new_yubikey:` must be a valid selector Hash; values such as `false` are rejected
98
+ before opening the source vault, not treated as a request for software rotation.
99
+ Hardware access or encryption failure leaves the original ciphertext intact. Old software
100
+ sidecars and backups are not silently deleted; retire them deliberately after
101
+ verifying recovery.
102
+
103
+ Losing or reprogramming the only usable slot loses access. A ciphertext backup
104
+ alone is not an independent recovery key. Establish and test an appropriate
105
+ recovery device/secret before relying on hardware-only storage. Removing a
106
+ YubiKey does not erase an already unlocked `PWN::Env` or revoke remote tokens;
107
+ the application retains its in-memory configuration until exit/refresh.
108
+
109
+ ## Integration coverage
110
+
111
+ | Entry point | Hardware behavior |
112
+ |---|---|
113
+ | `Vault.create/encrypt/decrypt/dump/edit/refresh_encryption_secrets` | Explicit creation/conversion, automatic envelope detection on read, backend-preserving edits |
114
+ | `Vault.seal_credentials/open_credentials` | Authenticated in-memory hardware envelopes alongside existing passphrase/keyring formats |
115
+ | `Vault.store/fetch/offer/ingest/expand/redact` | Optional hardware-protected engagement records; metadata-only `query` does not touch hardware |
116
+ | `BasicAuth.encode(host:)` | Resolves matching protected loot through Vault, not a separate crypto implementation |
117
+ | `Redaction.capture` and `Recon` loot ingestion | Explicit hardware selector for new captures; locked-store failure never creates a software-key fallback |
118
+ | `Config.default_env/refresh_env` | Explicit hardware creation; hardware loading without a legacy decryptor sidecar |
119
+ | `Migrate.vault_drift/backfill_vault` | Read/merge through Vault, preserving hardware format and user settings |
120
+ | `pwn-vault`, `/vault edit`, `/model`, `/system-role`, mesh settings | Backend-aware encrypted persistence; no duplicate CBC-only write path |
121
+ | OpenAI, Anthropic, Grok and Google Workspace OAuth persistence | In-memory configuration merge, ciphertext-only atomic save; hardware unavailability reports persistence failure |
122
+ | `AuthenticationHelper.mfa` and supported WWW login methods | Explicit exact-account OATH code lookup, separate from ordinary manual MFA |
123
+
124
+ Hardware-protected loot reads may require a touch, including explicit expansion
125
+ and secret-value redaction. Do not enable it for unattended workflows expecting
126
+ unbounded automatic access. Background metadata discovery and help do not unlock
127
+ hardware. Existing unrelated records are not implicitly re-encrypted. Missing
128
+ loot stores are empty; an existing unreadable store, malformed JSON, or invalid
129
+ record/envelope shape raises `IOError` on reads and writes, including metadata
130
+ queries. PWN does not infer a software backend from corrupt records, generate a
131
+ replacement master key, or overwrite the damaged store. Restore a verified backup
132
+ before retrying.
133
+
134
+ ## Explicit OATH authentication
135
+
136
+ Select an exact account and authorize its effects explicitly:
137
+
138
+ ```ruby
139
+ code = PWN::Plugins::AuthenticationHelper.mfa(
140
+ yubikey: { account: 'Example:alice', serial: '12345678', confirm: true }
141
+ ); nil
142
+ ```
143
+
144
+ The same `yubikey:` Hash is accepted by GitHub, Synack, Twitter, BugCrowd,
145
+ CoinbasePro and AppCobaltIO login methods. Lookup occurs at the authenticator
146
+ form, not during option validation. Missing/nonliteral consent is rejected before
147
+ password prompts or navigation. Device errors do not fall back to manual MFA;
148
+ explicit manual tokens cannot be combined with the hardware selector. PayPal's
149
+ current SMS-only adapter rejects OATH even with consent, and GitHub's SMS-only
150
+ challenge does not accept an authenticator code.
151
+
152
+ For `pwn_web_cache_deception`, add `--yubikey-account='Example:alice'`, optionally
153
+ `--yubikey-serial=12345678`, and the required `--yubikey-confirm` to the normal
154
+ arguments for a compatible login module. The flag authorizes HOTP counter
155
+ advancement, remembered-key authentication and deletion of invalid cached keys
156
+ during account listing, and upstream `ykman` cleanup that may delete a corrupted
157
+ OATH credential. It is required for TOTP too; PWN never silently supplies consent.
158
+ Confirmation/serial flags without an account are rejected before login.
159
+
160
+ ## Scope boundaries
161
+
162
+ YubiKey FIDO management is not a WebAuthn relying-party implementation. Website
163
+ passkey login still requires the browser's genuine security-key ceremony and
164
+ site enrollment; a generated OATH code must never be submitted to an SMS-only
165
+ challenge. PIV/OpenPGP key management does not export non-exportable keys or
166
+ magically replace TLS, SSH-agent or GPG-provider integrations.
167
+
168
+ Radio protocol keys, packet AES-CCM, TLS inspection certificates, OAuth PKCE,
169
+ artifact hashes, cloud KMS/HSM API clients and Jenkins private-key uploads are
170
+ not rewritten to use hardware-derived bytes. Their stored credentials can be
171
+ protected through Vault where appropriate.
172
+
173
+ Hardware-backed loot does not encrypt Recon's raw evidence/asset files, generic
174
+ tool-result artifacts, exports, or the filesystem as a whole. Those retain their
175
+ existing evidence-storage behavior and need separate access/at-rest protection.
176
+ `recon_run` advertises the same nonsecret hardware selector as `loot_query` for
177
+ harvested loot; metadata-only Vault queries do not unlock a device, but Dispatch's
178
+ separate output-redaction step can still require access to protected secrets.
179
+
180
+ Verification uses fake device processes and synthetic HMAC responses. Tests
181
+ exercise real Ruby encryption, authentication failures, selector validation,
182
+ subprocess cleanup and persistence entry points, but are not evidence of a
183
+ physical YubiKey, live website authentication or every firmware variant.