pwn 0.5.783 → 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 (264) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +8 -8
  4. data/README.md +37 -0
  5. data/Rakefile +19 -1
  6. data/bin/pwn +2 -1
  7. data/bin/pwn_setup +1 -1
  8. data/bin/pwn_web_cache_deception +35 -7
  9. data/build_gem.sh +1 -1
  10. data/documentation/AI-Integration.md +7 -5
  11. data/documentation/Agent-Tool-Registry.md +2 -2
  12. data/documentation/Backup-Retention.md +82 -0
  13. data/documentation/Configuration.md +10 -6
  14. data/documentation/Contributing.md +1 -1
  15. data/documentation/Cron.md +64 -35
  16. data/documentation/Diagrams.md +2 -2
  17. data/documentation/General-PWN-Usage.md +4 -3
  18. data/documentation/Home.md +6 -5
  19. data/documentation/How-PWN-Works.md +7 -6
  20. data/documentation/Installation.md +254 -10
  21. data/documentation/Mission-Cancellation.md +134 -0
  22. data/documentation/Operability.md +17 -0
  23. data/documentation/Persistence.md +8 -6
  24. data/documentation/Plugin-Degradation.md +12 -0
  25. data/documentation/Plugins.md +3 -2
  26. data/documentation/Reinforcement-Learning.md +5 -6
  27. data/documentation/Runtime-Source-Protection.md +162 -0
  28. data/documentation/Transparent-Browser.md +11 -0
  29. data/documentation/Troubleshooting.md +3 -2
  30. data/documentation/What-is-PWN.md +2 -2
  31. data/documentation/YubiKey-Vaults.md +183 -0
  32. data/documentation/YubiKey.md +339 -0
  33. data/documentation/diagrams/cron-scheduling.svg +130 -128
  34. data/documentation/diagrams/dot/cron-scheduling.dot +5 -5
  35. data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
  36. data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
  37. data/documentation/diagrams/persistence-filesystem.svg +172 -142
  38. data/documentation/diagrams/plugin-ecosystem.svg +64 -64
  39. data/documentation/pwn-ai-Agent.md +10 -4
  40. data/etc/default_cron/session_cleanup.rb +1245 -22
  41. data/etc/default_skills/pwn/ai/agent/mission_namespace/SKILL.md +21 -0
  42. data/etc/default_skills/pwn/ai/agent/tools/browser_act/SKILL.md +45 -0
  43. data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +27 -26
  44. data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +25 -15
  45. data/etc/default_skills/pwn/ai/agent/tools/workspace_tool/SKILL.md +24 -0
  46. data/etc/default_skills/pwn/ai/server_tools/SKILL.md +25 -0
  47. data/etc/default_skills/pwn/backups/SKILL.md +54 -0
  48. data/etc/default_skills/pwn/plugins/recon/SKILL.md +12 -1
  49. data/etc/default_skills/pwn/plugins/yubikey/SKILL.md +157 -0
  50. data/etc/default_skills/pwn/plugins/yubikey/fido/SKILL.md +189 -0
  51. data/etc/default_skills/pwn/plugins/yubikey/hsmauth/SKILL.md +83 -0
  52. data/etc/default_skills/pwn/plugins/yubikey/management/SKILL.md +80 -0
  53. data/etc/default_skills/pwn/plugins/yubikey/oath/SKILL.md +115 -0
  54. data/etc/default_skills/pwn/plugins/yubikey/openpgp/SKILL.md +144 -0
  55. data/etc/default_skills/pwn/plugins/yubikey/otp/SKILL.md +97 -0
  56. data/etc/default_skills/pwn/plugins/yubikey/piv/SKILL.md +110 -0
  57. data/etc/default_skills/pwn/plugins/yubikey/protocol/SKILL.md +21 -0
  58. data/etc/default_skills/pwn/plugins/yubikey/security_domain/SKILL.md +75 -0
  59. data/etc/default_skills/pwn/plugins/yubikey/security_domain/scp/SKILL.md +46 -0
  60. data/etc/default_skills/pwn/plugins/yubikey/transport/SKILL.md +98 -0
  61. data/etc/default_skills/pwn/redaction/SKILL.md +13 -1
  62. data/lib/pwn/ai/agent/dispatch.rb +26 -15
  63. data/lib/pwn/ai/agent/extrospection.rb +6 -7
  64. data/lib/pwn/ai/agent/learning.rb +62 -25
  65. data/lib/pwn/ai/agent/loop.rb +17 -1
  66. data/lib/pwn/ai/agent/mission_namespace.rb +331 -0
  67. data/lib/pwn/ai/agent/policy.rb +5 -0
  68. data/lib/pwn/ai/agent/policy_evaluation.rb +8 -8
  69. data/lib/pwn/ai/agent/prompt_builder.rb +13 -2
  70. data/lib/pwn/ai/agent/request_runtime.rb +122 -18
  71. data/lib/pwn/ai/agent/reward.rb +5 -3
  72. data/lib/pwn/ai/agent/skill_consolidation.rb +3 -2
  73. data/lib/pwn/ai/agent/skill_review.rb +9 -5
  74. data/lib/pwn/ai/agent/swarm.rb +35 -16
  75. data/lib/pwn/ai/agent/tools/browser_act.rb +32 -0
  76. data/lib/pwn/ai/agent/tools/loot_query.rb +11 -1
  77. data/lib/pwn/ai/agent/tools/recon_run.rb +11 -1
  78. data/lib/pwn/ai/agent/tools/workspace_tool.rb +29 -0
  79. data/lib/pwn/ai/agent/turn_finalizer.rb +6 -2
  80. data/lib/pwn/ai/agent.rb +1 -0
  81. data/lib/pwn/ai/anthropic.rb +8 -33
  82. data/lib/pwn/ai/cli.rb +34 -5
  83. data/lib/pwn/ai/gemini.rb +1 -0
  84. data/lib/pwn/ai/grok.rb +39 -28
  85. data/lib/pwn/ai/ollama.rb +4 -2
  86. data/lib/pwn/ai/open_ai.rb +11 -32
  87. data/lib/pwn/ai/open_web_ui.rb +62 -4
  88. data/lib/pwn/ai/router.rb +53 -3
  89. data/lib/pwn/ai/server_tools.rb +231 -0
  90. data/lib/pwn/ai.rb +1 -0
  91. data/lib/pwn/backups.rb +347 -0
  92. data/lib/pwn/config.rb +70 -39
  93. data/lib/pwn/cron.rb +120 -49
  94. data/lib/pwn/migrate.rb +150 -68
  95. data/lib/pwn/module_skills.rb +7 -0
  96. data/lib/pwn/plugins/ai_sandbox.rb +538 -1
  97. data/lib/pwn/plugins/authentication_helper.rb +74 -4
  98. data/lib/pwn/plugins/basic_auth.rb +2 -1
  99. data/lib/pwn/plugins/binary_parser.rb +5 -4
  100. data/lib/pwn/plugins/ghidra.rb +4 -3
  101. data/lib/pwn/plugins/google_workspace.rb +4 -21
  102. data/lib/pwn/plugins/jobs.rb +1 -1
  103. data/lib/pwn/plugins/log.rb +21 -3
  104. data/lib/pwn/plugins/preflight_checker.rb +34 -9
  105. data/lib/pwn/plugins/recon.rb +4 -1
  106. data/lib/pwn/plugins/repl/ai.rb +305 -27
  107. data/lib/pwn/plugins/repl/ai_console.rb +273 -89
  108. data/lib/pwn/plugins/repl/ai_swarm.rb +181 -62
  109. data/lib/pwn/plugins/repl/mesh.rb +3 -16
  110. data/lib/pwn/plugins/repl/vault.rb +4 -15
  111. data/lib/pwn/plugins/repl.rb +1 -216
  112. data/lib/pwn/plugins/sandbox/driver.rb +4 -0
  113. data/lib/pwn/plugins/transparent_browser.rb +215 -0
  114. data/lib/pwn/plugins/vault.rb +412 -135
  115. data/lib/pwn/plugins/yubikey/fido.rb +1288 -0
  116. data/lib/pwn/plugins/yubikey/hsmauth.rb +439 -0
  117. data/lib/pwn/plugins/yubikey/management.rb +686 -0
  118. data/lib/pwn/plugins/yubikey/oath.rb +766 -0
  119. data/lib/pwn/plugins/yubikey/openpgp.rb +1063 -0
  120. data/lib/pwn/plugins/yubikey/otp.rb +897 -0
  121. data/lib/pwn/plugins/yubikey/piv.rb +1315 -0
  122. data/lib/pwn/plugins/yubikey/protocol.rb +341 -0
  123. data/lib/pwn/plugins/yubikey/security_domain/scp.rb +482 -0
  124. data/lib/pwn/plugins/yubikey/security_domain.rb +464 -0
  125. data/lib/pwn/plugins/yubikey/transport.rb +1017 -0
  126. data/lib/pwn/plugins/yubikey.rb +844 -0
  127. data/lib/pwn/plugins.rb +1 -0
  128. data/lib/pwn/redaction.rb +69 -12
  129. data/lib/pwn/session_trace.rb +4 -0
  130. data/lib/pwn/sessions.rb +58 -16
  131. data/lib/pwn/setup.rb +123 -41
  132. data/lib/pwn/version.rb +1 -1
  133. data/lib/pwn/www/app_cobalt_io.rb +16 -5
  134. data/lib/pwn/www/bug_crowd.rb +16 -5
  135. data/lib/pwn/www/coinbase_pro.rb +16 -5
  136. data/lib/pwn/www/github.rb +30 -14
  137. data/lib/pwn/www/paypal.rb +15 -4
  138. data/lib/pwn/www/synack.rb +16 -5
  139. data/lib/pwn/www/twitter.rb +17 -6
  140. data/lib/pwn.rb +1 -0
  141. data/scripts/build_gate.rb +58 -0
  142. data/spec/documentation/installation_md_spec.rb +1 -1
  143. data/spec/fixtures/yubikey/apdu_frames.json +162 -0
  144. data/spec/integration/authentication_helper_oath_spec.rb +117 -0
  145. data/spec/integration/build_gate_spec.rb +73 -0
  146. data/spec/integration/controller_spill_spec.rb +48 -0
  147. data/spec/integration/harness_process_spec.rb +97 -0
  148. data/spec/integration/mission_cancel_spec.rb +79 -0
  149. data/spec/integration/mission_curses_pty_spec.rb +44 -0
  150. data/spec/integration/persistence_roundtrip_spec.rb +1 -7
  151. data/spec/integration/readme_security_spec.rb +47 -0
  152. data/spec/integration/web_cache_deception_yubikey_spec.rb +159 -0
  153. data/spec/lib/pwn/ai/agent/dispatch_spec.rb +6 -3
  154. data/spec/lib/pwn/ai/agent/dispatch_vault_safety_spec.rb +86 -0
  155. data/spec/lib/pwn/ai/agent/injection_guard_spec.rb +2 -0
  156. data/spec/lib/pwn/ai/agent/learning_spec.rb +4 -4
  157. data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +5 -0
  158. data/spec/lib/pwn/ai/agent/loop_spec.rb +104 -29
  159. data/spec/lib/pwn/ai/agent/mission_namespace_spec.rb +138 -0
  160. data/spec/lib/pwn/ai/agent/policy_spec.rb +27 -0
  161. data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +48 -0
  162. data/spec/lib/pwn/ai/agent/shell_search_effect_spec.rb +44 -0
  163. data/spec/lib/pwn/ai/agent/solve_spec.rb +6 -4
  164. data/spec/lib/pwn/ai/agent/tools/browser_act_spec.rb +44 -0
  165. data/spec/lib/pwn/ai/agent/tools/loot_query_yubikey_spec.rb +55 -0
  166. data/spec/lib/pwn/ai/agent/tools/recon_run_yubikey_spec.rb +43 -0
  167. data/spec/lib/pwn/ai/agent/tools/workspace_tool_spec.rb +20 -0
  168. data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +7 -0
  169. data/spec/lib/pwn/ai/anthropic_vault_enrollment_spec.rb +37 -0
  170. data/spec/lib/pwn/ai/cli_spec.rb +6 -9
  171. data/spec/lib/pwn/ai/oauth_vault_persistence_spec.rb +195 -0
  172. data/spec/lib/pwn/ai/open_ai_spec.rb +2 -2
  173. data/spec/lib/pwn/ai/open_web_ui_workspace_spec.rb +36 -0
  174. data/spec/lib/pwn/ai/router_spec.rb +31 -3
  175. data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
  176. data/spec/lib/pwn/backups_spec.rb +114 -0
  177. data/spec/lib/pwn/config_redaction_yubikey_spec.rb +28 -0
  178. data/spec/lib/pwn/config_yubikey_spec.rb +92 -0
  179. data/spec/lib/pwn/cron_spec.rb +113 -6
  180. data/spec/lib/pwn/migrate_spec.rb +108 -5
  181. data/spec/lib/pwn/migrate_yubikey_spec.rb +128 -0
  182. data/spec/lib/pwn/module_skills_spec.rb +8 -0
  183. data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +230 -0
  184. data/spec/lib/pwn/plugins/authentication_helper_native_yubikey_spec.rb +40 -0
  185. data/spec/lib/pwn/plugins/authentication_helper_yubikey_spec.rb +90 -0
  186. data/spec/lib/pwn/plugins/capability_broker/daemon_spec.rb +1 -1
  187. data/spec/lib/pwn/plugins/google_workspace_spec.rb +17 -35
  188. data/spec/lib/pwn/plugins/log_spec.rb +7 -0
  189. data/spec/lib/pwn/plugins/log_yubikey_spec.rb +47 -0
  190. data/spec/lib/pwn/plugins/preflight_checker_native_yubikey_spec.rb +19 -0
  191. data/spec/lib/pwn/plugins/preflight_checker_yubikey_spec.rb +55 -0
  192. data/spec/lib/pwn/plugins/recon_yubikey_spec.rb +43 -0
  193. data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +125 -78
  194. data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
  195. data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +30 -16
  196. data/spec/lib/pwn/plugins/repl/ai_vault_edit_yubikey_spec.rb +103 -0
  197. data/spec/lib/pwn/plugins/repl/ai_yubikey_spec.rb +48 -0
  198. data/spec/lib/pwn/plugins/repl/mesh_vault_persistence_spec.rb +149 -0
  199. data/spec/lib/pwn/plugins/repl/vault_yaml_serialization_spec.rb +11 -0
  200. data/spec/lib/pwn/plugins/repl/vault_yubikey_spec.rb +39 -0
  201. data/spec/lib/pwn/plugins/repl_spec.rb +56 -5
  202. data/spec/lib/pwn/plugins/transparent_browser_jev_spec.rb +46 -0
  203. data/spec/lib/pwn/plugins/vault_native_yubikey_spec.rb +29 -0
  204. data/spec/lib/pwn/plugins/vault_publication_safety_spec.rb +258 -0
  205. data/spec/lib/pwn/plugins/vault_store_safety_spec.rb +241 -0
  206. data/spec/lib/pwn/plugins/vault_yubikey_safety_spec.rb +168 -0
  207. data/spec/lib/pwn/plugins/vault_yubikey_spec.rb +128 -0
  208. data/spec/lib/pwn/plugins/vault_yubikey_vector_spec.rb +45 -0
  209. data/spec/lib/pwn/plugins/yubikey/fido_spec.rb +854 -0
  210. data/spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb +235 -0
  211. data/spec/lib/pwn/plugins/yubikey/management_spec.rb +277 -0
  212. data/spec/lib/pwn/plugins/yubikey/oath_spec.rb +349 -0
  213. data/spec/lib/pwn/plugins/yubikey/openpgp_spec.rb +810 -0
  214. data/spec/lib/pwn/plugins/yubikey/otp_spec.rb +337 -0
  215. data/spec/lib/pwn/plugins/yubikey/piv_spec.rb +646 -0
  216. data/spec/lib/pwn/plugins/yubikey/protocol_secure_spec.rb +28 -0
  217. data/spec/lib/pwn/plugins/yubikey/protocol_spec.rb +149 -0
  218. data/spec/lib/pwn/plugins/yubikey/protocol_vectors_spec.rb +18 -0
  219. data/spec/lib/pwn/plugins/yubikey/security_domain/scp_spec.rb +256 -0
  220. data/spec/lib/pwn/plugins/yubikey/security_domain/support.rb +153 -0
  221. data/spec/lib/pwn/plugins/yubikey/security_domain_spec.rb +296 -0
  222. data/spec/lib/pwn/plugins/yubikey/transport_spec.rb +760 -0
  223. data/spec/lib/pwn/plugins/yubikey_security_spec.rb +260 -0
  224. data/spec/lib/pwn/plugins/yubikey_spec.rb +209 -0
  225. data/spec/lib/pwn/plugins/yubikey_terminal_spec.rb +147 -0
  226. data/spec/lib/pwn/redaction_spec.rb +13 -10
  227. data/spec/lib/pwn/redaction_yubikey_spec.rb +61 -0
  228. data/spec/lib/pwn/session_cleanup_concurrency_spec.rb +201 -0
  229. data/spec/lib/pwn/session_cleanup_default_spec.rb +167 -2
  230. data/spec/lib/pwn/session_cleanup_legacy_spec.rb +101 -0
  231. data/spec/lib/pwn/session_cleanup_payload_spec.rb +335 -2
  232. data/spec/lib/pwn/session_cleanup_recovery_spec.rb +403 -0
  233. data/spec/lib/pwn/sessions_spec.rb +112 -0
  234. data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
  235. data/spec/lib/pwn/setup_spec.rb +3 -1
  236. data/spec/lib/pwn/setup_vault_yubikey_spec.rb +61 -0
  237. data/spec/lib/pwn/setup_yubikey_spec.rb +40 -0
  238. data/spec/lib/pwn/www/login_yubikey_spec.rb +273 -0
  239. data/spec/spec_helper.rb +15 -0
  240. data/spec/support/gate_failure_formatter.rb +14 -0
  241. data/spec/support/harness/cli_faults.rb +13 -9
  242. data/spec/support/harness/controller_spill.rb +26 -0
  243. data/spec/support/harness/controller_state.rb +54 -0
  244. data/spec/support/harness/evaluate.rb +12 -1
  245. data/spec/support/harness/experiments.rb +1 -1
  246. data/spec/support/harness/fake_provider.rb +22 -6
  247. data/spec/support/harness/fault_preload.rb +3 -1
  248. data/spec/support/harness/mission_console.rb +54 -0
  249. data/spec/support/harness/mission_curses_pty.rb +41 -0
  250. data/spec/support/harness/mission_dispatch.rb +30 -0
  251. data/spec/support/harness/mission_fixture_preload.rb +24 -0
  252. data/spec/support/harness/mission_job_lifetime.rb +25 -0
  253. data/spec/support/harness/mission_legacy.rb +25 -0
  254. data/spec/support/harness/mission_normal_exit.rb +34 -0
  255. data/spec/support/harness/mission_swarm.rb +23 -0
  256. data/spec/support/harness/mission_unsupported.rb +29 -0
  257. data/spec/support/harness/package_acceptance.rb +33 -5
  258. data/spec/support/harness/session_acceptance.rb +4 -4
  259. data/spec/support/harness/swarm_acceptance.rb +1 -1
  260. data/spec/support/harness/swarm_control.rb +44 -0
  261. data/spec/support/mission_namespace.rb +14 -0
  262. data/spec/support/mission_pipe_queue.rb +33 -0
  263. data/third_party/pwn_rdoc.jsonl +599 -15
  264. metadata +140 -17
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.783'
4
+ VERSION = '0.5.785'
5
5
  end
@@ -5,6 +5,8 @@ require 'yaml'
5
5
  module PWN
6
6
  module WWW
7
7
  # This plugin supports app.cobalt.io actions.
8
+ # YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
9
+ # and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
8
10
  module AppCobaltIO
9
11
  # Supported Method Parameters::
10
12
  # browser_obj = PWN::WWW::AppCobaltIO.open(
@@ -28,10 +30,13 @@ module PWN
28
30
  # browser_obj: 'required - browser_obj returned from #open method',
29
31
  # username: 'required - username',
30
32
  # password: 'optional - passwd (will prompt if blank)',
31
- # mfa: 'optional - if true prompt for mfa token (defaults to false)'
33
+ # mfa: 'optional - if true prompt for mfa token (defaults to false)',
34
+ # yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
32
35
  # )
33
36
 
34
37
  public_class_method def self.login(opts = {})
38
+ PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
39
+ yubikey = opts[:yubikey]
35
40
  browser_obj = opts[:browser_obj]
36
41
  username = opts[:username].to_s.scrub.strip.chomp
37
42
  password = opts[:password]
@@ -54,9 +59,9 @@ module PWN
54
59
  # name: 'commit' doesn't work
55
60
  browser.button(index: 6).click! # no name or id in button element
56
61
 
57
- if mfa
62
+ if mfa || !yubikey.nil?
58
63
  until browser.url == 'https://app.cobalt.io/dashboard'
59
- browser.text_field(id: 'code').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
64
+ browser.text_field(id: 'code').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
60
65
  browser.button(name: 'commit').click!
61
66
  sleep 3
62
67
  end
@@ -117,12 +122,18 @@ module PWN
117
122
  proxy: 'optional - scheme://proxy_host:port || tor'
118
123
  )
119
124
 
120
- # Run login and return its result
125
+ # Log in with manual MFA or explicitly selected YubiKey OATH.
126
+ # YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
127
+ # Configure the service for an authenticator challenge, not SMS.
128
+ # Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
129
+ # Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
130
+ # Reject conflicting explicit tokens before any login action.
121
131
  #{self}.login(
122
132
  browser_obj: 'required - browser_obj returned from #open method',
123
133
  username: 'required - username value consumed by #login',
124
134
  password: 'optional - passwd (will prompt if blank)',
125
- mfa: 'optional - if true prompt for mfa token (defaults to false)'
135
+ mfa: 'optional - if true prompt for mfa token (defaults to false)',
136
+ yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
126
137
  )
127
138
 
128
139
  # Run logout and return its result
@@ -5,6 +5,8 @@ require 'yaml'
5
5
  module PWN
6
6
  module WWW
7
7
  # This plugin supports bugcrowd.com actions.
8
+ # YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
9
+ # and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
8
10
  module BugCrowd
9
11
  # Supported Method Parameters::
10
12
  # browser_obj = PWN::WWW::BugCrowd.open(
@@ -28,10 +30,13 @@ module PWN
28
30
  # browser_obj: 'required - browser_obj returned from #open method',
29
31
  # username: 'required - username',
30
32
  # password: 'optional - passwd (will prompt if blank)',
31
- # mfa: 'optional - if true prompt for mfa token (defaults to false)'
33
+ # mfa: 'optional - if true prompt for mfa token (defaults to false)',
34
+ # yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
32
35
  # )
33
36
 
34
37
  public_class_method def self.login(opts = {})
38
+ PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
39
+ yubikey = opts[:yubikey]
35
40
  browser_obj = opts[:browser_obj]
36
41
  username = opts[:username].to_s.scrub.strip.chomp
37
42
  password = opts[:password]
@@ -51,9 +56,9 @@ module PWN
51
56
  browser.text_field(id: 'user_password').wait_until(&:present?).set(password)
52
57
  browser.button(name: 'button').click!
53
58
 
54
- if mfa
59
+ if mfa || !yubikey.nil?
55
60
  until browser.url == 'https://bugcrowd.com/programs'
56
- browser.text_field(name: 'otp_attempt').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
61
+ browser.text_field(name: 'otp_attempt').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
57
62
  browser.button(name: 'commit').click!
58
63
  sleep 3
59
64
  end
@@ -114,12 +119,18 @@ module PWN
114
119
  proxy: 'optional - scheme://proxy_host:port || tor'
115
120
  )
116
121
 
117
- # Run login and return its result
122
+ # Log in with manual MFA or explicitly selected YubiKey OATH.
123
+ # YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
124
+ # Configure the service for an authenticator challenge, not SMS.
125
+ # Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
126
+ # Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
127
+ # Reject conflicting explicit tokens before any login action.
118
128
  #{self}.login(
119
129
  browser_obj: 'required - browser_obj returned from #open method',
120
130
  username: 'required - username value consumed by #login',
121
131
  password: 'optional - passwd (will prompt if blank)',
122
- mfa: 'optional - if true prompt for mfa token (defaults to false)'
132
+ mfa: 'optional - if true prompt for mfa token (defaults to false)',
133
+ yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
123
134
  )
124
135
 
125
136
  # Run logout and return its result
@@ -5,6 +5,8 @@ require 'yaml'
5
5
  module PWN
6
6
  module WWW
7
7
  # This plugin supports tradingview.com actions.
8
+ # YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
9
+ # and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
8
10
  module CoinbasePro
9
11
  # Supported Method Parameters::
10
12
  # browser_obj = PWN::WWW::CoinbasePro.open(
@@ -28,10 +30,13 @@ module PWN
28
30
  # browser_obj: 'required - browser_obj returned from #open method',
29
31
  # username: 'required - username',
30
32
  # password: 'optional - passwd (will prompt if blank)',
31
- # mfa: 'optional - if true prompt for mfa token (defaults to false)'
33
+ # mfa: 'optional - if true prompt for mfa token (defaults to false)',
34
+ # yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
32
35
  # )
33
36
 
34
37
  public_class_method def self.login(opts = {})
38
+ PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
39
+ yubikey = opts[:yubikey]
35
40
  browser_obj = opts[:browser_obj]
36
41
  username = opts[:username].to_s.scrub.strip.chomp
37
42
  password = opts[:password]
@@ -52,9 +57,9 @@ module PWN
52
57
  browser.text_field(name: 'password').wait_until(&:present?).set(password)
53
58
  browser.button(text: 'Sign In').click!
54
59
 
55
- if mfa
60
+ if mfa || !yubikey.nil?
56
61
  until browser.url.include?('https://pro.coinbase.com')
57
- browser.text_field(name: 'token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
62
+ browser.text_field(name: 'token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
58
63
  browser.button(text: 'Verify').click!
59
64
  sleep 3
60
65
  end
@@ -114,12 +119,18 @@ module PWN
114
119
  proxy: 'optional - scheme://proxy_host:port || tor'
115
120
  )
116
121
 
117
- # Run login and return its result
122
+ # Log in with manual MFA or explicitly selected YubiKey OATH.
123
+ # YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
124
+ # Configure the service for an authenticator challenge, not SMS.
125
+ # Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
126
+ # Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
127
+ # Reject conflicting explicit tokens before any login action.
118
128
  #{self}.login(
119
129
  browser_obj: 'required - browser_obj returned from #open method',
120
130
  username: 'required - username value consumed by #login',
121
131
  password: 'optional - passwd (will prompt if blank)',
122
- mfa: 'optional - if true prompt for mfa token (defaults to false)'
132
+ mfa: 'optional - if true prompt for mfa token (defaults to false)',
133
+ yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
123
134
  )
124
135
 
125
136
  # Run logout and return its result
@@ -5,6 +5,8 @@ require 'yaml'
5
5
  module PWN
6
6
  module WWW
7
7
  # This plugin supports github.com actions.
8
+ # YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
9
+ # and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
8
10
  module GitHub
9
11
  # Supported Method Parameters::
10
12
  # browser_obj = PWN::WWW::GitHub.open(
@@ -29,10 +31,12 @@ module PWN
29
31
  # username: 'required - username',
30
32
  # password: 'optional - passwd (will prompt if blank)',
31
33
  # mfa: 'optional - MFA / TOTP token string, or true to prompt (defaults to false)',
32
- # mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state'
34
+ # mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state',
35
+ # yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
33
36
  # )
34
37
 
35
38
  public_class_method def self.login(opts = {})
39
+ PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
36
40
  browser_obj = opts[:browser_obj]
37
41
  username = opts[:username].to_s.scrub.strip.chomp
38
42
  password = opts[:password]
@@ -45,6 +49,7 @@ module PWN
45
49
  password = opts[:password].to_s.scrub.strip.chomp
46
50
  end
47
51
  mfa = opts[:mfa]
52
+ yubikey = opts[:yubikey]
48
53
  mfa_token = opts[:mfa_token].to_s.scrub.strip.chomp
49
54
  mfa_token = mfa.to_s.scrub.strip.chomp if mfa_token.empty? && mfa.is_a?(String)
50
55
 
@@ -54,7 +59,7 @@ module PWN
54
59
  browser.text_field(name: 'password').wait_until(&:present?).set(password)
55
60
  browser.input(name: 'commit').click!
56
61
 
57
- if mfa || !mfa_token.empty?
62
+ if mfa || !mfa_token.empty? || !yubikey.nil?
58
63
  until browser.url == 'https://github.com/' ||
59
64
  browser.url.include?('https://github.com/dashboard') ||
60
65
  (
@@ -63,23 +68,28 @@ module PWN
63
68
  !browser.url.include?('/session')
64
69
  )
65
70
 
71
+ mfa_field = if browser.text_field(name: 'app_otp').exist?
72
+ browser.text_field(name: 'app_otp')
73
+ elsif browser.text_field(id: 'app_totp').exist?
74
+ browser.text_field(id: 'app_totp')
75
+ elsif browser.text_field(name: 'otp').exist?
76
+ browser.text_field(name: 'otp')
77
+ else
78
+ raise ArgumentError, 'YubiKey OATH requires an authenticator challenge, not SMS' unless yubikey.nil?
79
+
80
+ browser.text_field(name: 'sms_otp')
81
+ end
82
+ mfa_field.wait_until(&:present?)
83
+
66
84
  token = if mfa_token.empty?
67
- PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token')
85
+ PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey)
68
86
  else
69
87
  consumed = mfa_token
70
88
  mfa_token = ''
71
89
  consumed
72
90
  end
73
91
 
74
- if browser.text_field(name: 'app_otp').exist?
75
- browser.text_field(name: 'app_otp').wait_until(&:present?).set(token)
76
- elsif browser.text_field(id: 'app_totp').exist?
77
- browser.text_field(id: 'app_totp').wait_until(&:present?).set(token)
78
- elsif browser.text_field(name: 'otp').exist?
79
- browser.text_field(name: 'otp').wait_until(&:present?).set(token)
80
- else
81
- browser.text_field(name: 'sms_otp').wait_until(&:present?).set(token)
82
- end
92
+ mfa_field.set(token)
83
93
 
84
94
  if browser.input(name: 'commit').exist?
85
95
  browser.input(name: 'commit').click!
@@ -151,13 +161,19 @@ module PWN
151
161
  proxy: 'optional - scheme://proxy_host:port || tor'
152
162
  )
153
163
 
154
- # Run login and return its result
164
+ # Log in with manual MFA or explicitly selected YubiKey OATH.
165
+ # YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
166
+ # Configure the service for an authenticator challenge, not SMS.
167
+ # Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
168
+ # Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
169
+ # Reject conflicting explicit tokens before any login action.
155
170
  #{self}.login(
156
171
  browser_obj: 'required - browser_obj returned from #open method',
157
172
  username: 'required - username value consumed by #login',
158
173
  password: 'optional - passwd (will prompt if blank)',
159
174
  mfa: 'optional - MFA / TOTP token string, or true to prompt (defaults to false)',
160
- mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state'
175
+ mfa_token: 'optional - MFA / TOTP token used to reach a post-authenticated state',
176
+ yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
161
177
  )
162
178
 
163
179
  # Run logout and return its result
@@ -5,6 +5,9 @@ require 'yaml'
5
5
  module PWN
6
6
  module WWW
7
7
  # This plugin supports paypal.com actions.
8
+ # Its legacy login adapter selects SMS MFA; YubiKey OATH is unsupported even
9
+ # with confirm: true; it is rejected before login rather than submitting
10
+ # an authenticator code as SMS.
8
11
  module Paypal
9
12
  # Supported Method Parameters::
10
13
  # browser_obj = PWN::WWW::Paypal.open(
@@ -86,10 +89,14 @@ module PWN
86
89
  # browser_obj: 'required - browser_obj returned from #open method',
87
90
  # username: 'required - username (email address)',
88
91
  # password: 'optional - passwd (will prompt if blank)',
89
- # mfa: 'optional - if true prompt for mfa token (defaults to false)'
92
+ # mfa: 'optional - if true prompt for mfa token (defaults to false)',
93
+ # yubikey: 'optional - rejected: exact OATH account Hash with :serial, :reader, :device_path, :connection, :timeout is unsupported even with confirm: true; this adapter is SMS-only; explicit mfa/mfa_token values conflict; omit to use manual SMS'
90
94
  # )
91
95
 
92
96
  public_class_method def self.login(opts = {})
97
+ PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
98
+ raise ArgumentError, 'PayPal YubiKey OATH login is unsupported by this SMS-only browser adapter' unless opts[:yubikey].nil?
99
+
93
100
  browser_obj = opts[:browser_obj]
94
101
  username = opts[:username].to_s.scrub.strip.chomp
95
102
  password = opts[:password]
@@ -110,7 +117,7 @@ module PWN
110
117
  browser.button(id: 'btnLogin').click!
111
118
 
112
119
  if mfa
113
- # Send code to SMS
120
+ # Send code to SMS; this legacy adapter does not select an OATH authenticator.
114
121
  browser.button(id: 'btnSelectSoftToken').wait_until(&:present?).click!
115
122
  until browser.url == 'https://www.paypal.com/myaccount/home'
116
123
  browser.text_field(id: 'security-code').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
@@ -188,12 +195,16 @@ module PWN
188
195
  mfa: 'optional - mfa value consumed by #signup'
189
196
  )
190
197
 
191
- # Run login and return its result
198
+ # Log in using the existing manual SMS challenge.
199
+ # YubiKey OATH is unsupported even with confirm: true; this SMS-only browser adapter rejects it before login.
200
+ # Service-level authenticator availability does not establish a compatible adapter flow.
201
+ # Validate options and reject conflicting explicit tokens before any login action.
192
202
  #{self}.login(
193
203
  browser_obj: 'required - browser_obj returned from #open method',
194
204
  username: 'required - username (email address)',
195
205
  password: 'optional - passwd (will prompt if blank)',
196
- mfa: 'optional - if true prompt for mfa token (defaults to false)'
206
+ mfa: 'optional - if true prompt for mfa token (defaults to false)',
207
+ yubikey: 'optional - rejected: exact OATH account Hash with :serial, :reader, :device_path, :connection, :timeout is unsupported even with confirm: true; this adapter is SMS-only; explicit mfa/mfa_token values conflict; omit to use manual SMS'
197
208
  )
198
209
 
199
210
  # Run logout and return its result
@@ -5,6 +5,8 @@ require 'yaml'
5
5
  module PWN
6
6
  module WWW
7
7
  # This plugin supports platform.synack.com actions.
8
+ # YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
9
+ # and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
8
10
  module Synack
9
11
  # Supported Method Parameters::
10
12
  # browser_obj = PWN::WWW::Synack.open(
@@ -28,10 +30,13 @@ module PWN
28
30
  # browser_obj: 'required - browser_obj returned from #open method',
29
31
  # username: 'required - username',
30
32
  # password: 'optional - passwd (will prompt if blank)',
31
- # mfa: 'optional - if true prompt for mfa token (defaults to false)'
33
+ # mfa: 'optional - if true prompt for mfa token (defaults to false)',
34
+ # yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
32
35
  # )
33
36
 
34
37
  public_class_method def self.login(opts = {})
38
+ PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
39
+ yubikey = opts[:yubikey]
35
40
  browser_obj = opts[:browser_obj]
36
41
  username = opts[:username].to_s.scrub.strip.chomp
37
42
  password = opts[:password]
@@ -51,9 +56,9 @@ module PWN
51
56
  browser.text_field(name: 'password').wait_until(&:present?).set(password)
52
57
  browser.button(class: 'btn').click!
53
58
 
54
- if mfa
59
+ if mfa || !yubikey.nil?
55
60
  until browser.url.include?('https://platform.synack.com')
56
- browser.text_field(name: 'authy_token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
61
+ browser.text_field(name: 'authy_token').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
57
62
  browser.button(class: 'btn').click!
58
63
  sleep 3
59
64
  end
@@ -114,12 +119,18 @@ module PWN
114
119
  proxy: 'optional - scheme://proxy_host:port || tor'
115
120
  )
116
121
 
117
- # Run login and return its result
122
+ # Log in with manual MFA or explicitly selected YubiKey OATH.
123
+ # YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
124
+ # Configure the service for an authenticator challenge, not SMS.
125
+ # Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
126
+ # Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
127
+ # Reject conflicting explicit tokens before any login action.
118
128
  #{self}.login(
119
129
  browser_obj: 'required - browser_obj returned from #open method',
120
130
  username: 'required - username value consumed by #login',
121
131
  password: 'optional - passwd (will prompt if blank)',
122
- mfa: 'optional - if true prompt for mfa token (defaults to false)'
132
+ mfa: 'optional - if true prompt for mfa token (defaults to false)',
133
+ yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
123
134
  )
124
135
 
125
136
  # Run logout and return its result
@@ -5,6 +5,8 @@ require 'yaml'
5
5
  module PWN
6
6
  module WWW
7
7
  # This plugin supports twitter.com actions.
8
+ # YubiKey OATH MFA requires explicit confirm: true because HOTP advances counters
9
+ # and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
8
10
  module Twitter
9
11
  # Supported Method Parameters::
10
12
  # browser_obj = PWN::WWW::Twitter.open(
@@ -28,10 +30,13 @@ module PWN
28
30
  # browser_obj: 'required - browser_obj returned from #open method',
29
31
  # username: 'required - username',
30
32
  # password: 'optional - passwd (will prompt if blank)',
31
- # mfa: 'optional - if true prompt for mfa token (defaults to false)'
33
+ # mfa: 'optional - if true prompt for mfa token (defaults to false)',
34
+ # yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
32
35
  # )
33
36
 
34
37
  public_class_method def self.login(opts = {})
38
+ PWN::Plugins::AuthenticationHelper.validate_mfa_options(opts)
39
+ yubikey = opts[:yubikey]
35
40
  browser_obj = opts[:browser_obj]
36
41
  username = opts[:username].to_s.scrub.strip.chomp
37
42
  password = opts[:password]
@@ -45,17 +50,17 @@ module PWN
45
50
  end
46
51
  mfa = opts[:mfa]
47
52
 
48
- browser_obj.goto('https://twitter.com/login')
53
+ browser.goto('https://twitter.com/login')
49
54
 
50
55
  browser.text_field(index: 2).wait_until(&:present?).set(username)
51
56
  browser.text_field(index: 3).wait_until(&:present?).set(password)
52
57
  browser.button(index: 1).click!
53
58
 
54
- if mfa
59
+ if mfa || !yubikey.nil?
55
60
  until browser.url == 'https://twitter.com/' ||
56
61
  browser.url == 'https://twitter.com/home'
57
62
 
58
- browser.text_field(id: 'challenge_response').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token'))
63
+ browser.text_field(id: 'challenge_response').wait_until(&:present?).set(PWN::Plugins::AuthenticationHelper.mfa(prompt: 'enter mfa token', yubikey: yubikey))
59
64
  browser.button(id: 'email_challenge_submit').click!
60
65
  sleep 3
61
66
  end
@@ -116,12 +121,18 @@ module PWN
116
121
  proxy: 'optional - scheme://proxy_host:port || tor'
117
122
  )
118
123
 
119
- # Run login and return its result
124
+ # Log in with manual MFA or explicitly selected YubiKey OATH.
125
+ # YubiKey enables MFA even when mfa is false; failures never fall back to a prompt.
126
+ # Configure the service for an authenticator challenge, not SMS.
127
+ # Example yubikey: { account: 'EXACT_OATH_NAME', confirm: true, serial: '123456', timeout: 30 }
128
+ # Explicit consent is required: HOTP advances counters and protected OATH accounts may require authentication; no automatic repair or deletion occurs.
129
+ # Reject conflicting explicit tokens before any login action.
120
130
  #{self}.login(
121
131
  browser_obj: 'required - browser_obj returned from #open method',
122
132
  username: 'required - username value consumed by #login',
123
133
  password: 'optional - passwd (will prompt if blank)',
124
- mfa: 'optional - if true prompt for mfa token (defaults to false)'
134
+ mfa: 'optional - if true prompt for mfa token (defaults to false)',
135
+ yubikey: 'optional - Hash selecting an exact OATH account (required :account and literal :confirm true); optional :serial exact decimal String, :reader or :device_path nonempty control-free String, :connection native exchange object, :timeout finite positive seconds (default 30), in-memory :password or callable :secret_provider; serial/reader and reader/device_path are mutually exclusive; no OTP slot; explicit mfa/mfa_token values conflict'
125
136
  )
126
137
 
127
138
  # Run logout and return its result
data/lib/pwn.rb CHANGED
@@ -24,6 +24,7 @@ module PWN
24
24
  autoload :Memory, 'pwn/memory'
25
25
  autoload :MemoryIndex, 'pwn/memory_index'
26
26
  autoload :Migrate, 'pwn/migrate'
27
+ autoload :Backups, 'pwn/backups'
27
28
  autoload :ModuleSkills, 'pwn/module_skills'
28
29
  autoload :Sessions, 'pwn/sessions'
29
30
  autoload :SessionTrace, 'pwn/session_trace'
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Run only the validation gate, never installation. Keep one bounded, private
4
+ # log even when a terminal disconnects or the child exits without a summary.
5
+ require 'fileutils'
6
+ require 'open3'
7
+
8
+ abort 'Usage: ruby scripts/build_gate.rb command [args...]' if ARGV.empty?
9
+
10
+ limit = 1_048_576
11
+ path = File.join(Dir.home, '.pwn', 'logs', 'build-gate.log')
12
+ FileUtils.mkdir_p(File.dirname(path), mode: 0o700)
13
+ File.open(path, File::RDWR | File::CREAT | File::NOFOLLOW, 0o600) do |log|
14
+ abort "Build gate already running; log: #{path}" unless log.flock(File::LOCK_EX | File::LOCK_NB)
15
+
16
+ log.chmod(0o600)
17
+ log.truncate(0)
18
+ log.sync = true
19
+ $stdout.sync = true
20
+ append = lambda do |bytes|
21
+ if log.size + bytes.bytesize > limit
22
+ log.rewind
23
+ retained = (log.read + bytes).byteslice(-limit, limit)
24
+ log.rewind
25
+ log.truncate(0)
26
+ log.write(retained)
27
+ else
28
+ log.seek(0, IO::SEEK_END)
29
+ log.write(bytes)
30
+ end
31
+ end
32
+ emit = lambda do |bytes|
33
+ append.call(bytes)
34
+ begin
35
+ $stdout.write(bytes)
36
+ rescue Errno::EPIPE, IOError
37
+ # Continue draining the child into the durable log after stream loss.
38
+ nil
39
+ end
40
+ end
41
+ emit.call("Build gate log: #{path} (last #{limit} bytes; replaced on next run)\n")
42
+ emit.call("Gate launcher: uid=#{Process.uid} ruby=#{RUBY_VERSION} cwd=#{Dir.pwd} HOME=#{Dir.home}\n")
43
+ status = nil
44
+ Open3.popen2e(*ARGV) do |input, output, waiter|
45
+ input.close
46
+ loop { emit.call(output.readpartial(16_384)) }
47
+ rescue EOFError
48
+ status = waiter.value
49
+ end
50
+ result = status.signaled? ? "signal=#{Signal.signame(status.termsig)}" : "exit=#{status.exitstatus}"
51
+ emit.call("\nBuild gate #{result}\n")
52
+ unless status.success?
53
+ log.rewind
54
+ tail = log.read.byteslice(-16_384, 16_384) || log.tap(&:rewind).read
55
+ warn "\nBuild gate failed (#{result}). Build gate log: #{path}\n#{tail}"
56
+ end
57
+ exit(status.exitstatus || (128 + status.termsig))
58
+ end
@@ -66,7 +66,7 @@ RSpec.describe 'documentation/Installation.md' do
66
66
  it 'programmatic API section is truthful' do
67
67
  expect(PWN::Setup).to respond_to(:check, :deps, :list_profiles, :pkg_manager, :migrate)
68
68
  expect(PWN::Setup.check(io: StringIO.new).keys)
69
- .to eq(%i[ok native_gems_missing toolchain_missing state pkg_manager os arch])
69
+ .to eq(%i[ok native_gems_missing toolchain_missing state pkg_manager os arch native_libraries_missing])
70
70
  expect(PWN::Setup.pkg_manager.keys).to eq(%i[key install sudo])
71
71
  expect(PWN::Setup.constants).to include(:NATIVE_GEMS, :TOOLCHAIN, :PROFILES)
72
72
  end