pwn 0.5.668 → 0.5.669

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +15 -0
  3. data/README.md +4 -4
  4. data/documentation/AI-Integration.md +5 -3
  5. data/documentation/Agent-Tool-Registry.md +18 -14
  6. data/documentation/Configuration.md +2 -0
  7. data/documentation/Contributing.md +13 -0
  8. data/documentation/Diagrams.md +1 -1
  9. data/documentation/Home.md +4 -4
  10. data/documentation/How-PWN-Works.md +6 -6
  11. data/documentation/Persistence.md +17 -0
  12. data/documentation/Reinforcement-Learning.md +39 -39
  13. data/documentation/SDR.md +1 -1
  14. data/documentation/Sessions.md +1 -0
  15. data/documentation/Skills-Memory-Learning.md +2 -0
  16. data/documentation/What-is-PWN.md +3 -3
  17. data/documentation/diagrams/agent-tool-registry.svg +43 -43
  18. data/documentation/diagrams/ai-integration-tool-calling.svg +112 -85
  19. data/documentation/diagrams/dot/agent-tool-registry.dot +4 -4
  20. data/documentation/diagrams/dot/ai-integration-tool-calling.dot +7 -3
  21. data/documentation/diagrams/dot/memory-skills-detailed.dot +2 -2
  22. data/documentation/diagrams/dot/overall-pwn-architecture.dot +6 -4
  23. data/documentation/diagrams/dot/persistence-filesystem.dot +1 -1
  24. data/documentation/diagrams/dot/sdr-radio-flow.dot +1 -1
  25. data/documentation/diagrams/memory-skills-detailed.svg +182 -182
  26. data/documentation/diagrams/overall-pwn-architecture.svg +154 -146
  27. data/documentation/diagrams/persistence-filesystem.svg +6 -6
  28. data/documentation/diagrams/sdr-radio-flow.svg +1 -1
  29. data/documentation/pwn-ai-Agent.md +29 -13
  30. data/lib/pwn/ai/agent/loop.rb +189 -1
  31. data/lib/pwn/ai/agent/prompt_builder.rb +24 -5
  32. data/lib/pwn/ai/agent/task_summarizer.rb +40 -3
  33. data/lib/pwn/ai/agent/tools/shell.rb +32 -0
  34. data/lib/pwn/memory_index.rb +17 -2
  35. data/lib/pwn/plugins/repl.rb +36 -7
  36. data/lib/pwn/version.rb +1 -1
  37. data/spec/lib/pwn/ai/agent/loop_spec.rb +31 -0
  38. data/spec/lib/pwn/ai/agent/task_summarizer_spec.rb +10 -0
  39. data/spec/lib/pwn/ai/agent/tools/shell_spec.rb +21 -0
  40. data/spec/lib/pwn/plugins/repl_pwn_vault_spec.rb +134 -0
  41. data/third_party/pwn_rdoc.jsonl +5 -0
  42. metadata +2 -1
@@ -360,6 +360,16 @@ describe PWN::AI::Agent::TaskSummarizer do
360
360
  expect(second).not_to include(subnet_req)
361
361
  end
362
362
 
363
+ it 'how-to goals stay explanation-only without rubocop verify padding' do
364
+ allow(PWN::Env).to receive(:dig).and_call_original
365
+ allow(PWN::Env).to receive(:dig).with(:ai, :agent, :task_summary_llm).and_return(false)
366
+ tasks = described_class.plan(request: 'how to do a ping sweep of a subnet using hping3?')
367
+ joined = tasks.join(' | ').downcase
368
+ expect(joined).to match(/explain|example|present|how/)
369
+ expect(joined).not_to match(/rubocop|live hosts|sweep the subnet|discover/)
370
+ expect(tasks.length).to be <= 3
371
+ end
372
+
363
373
  it 'falls back generically when LLM is disabled (no static domain scripts)' do
364
374
  allow(PWN::Env).to receive(:dig).and_call_original
365
375
  allow(PWN::Env).to receive(:dig).with(:ai, :agent, :task_summary_llm).and_return(false)
@@ -45,4 +45,25 @@ describe 'PWN::AI::Agent::Tools shell' do
45
45
  expect(r[:exit]).to eq(0)
46
46
  expect(r[:stdout].to_s).to include('holdout_ok_30e55')
47
47
  end
48
+
49
+ it 'blocks unauthorized hping3 subnet sweep style commands' do
50
+ Thread.current[:pwn_recon_authorized] = false
51
+ allow(PWN::Env).to receive(:dig).and_call_original
52
+ allow(PWN::Env).to receive(:dig).with(:ai, :agent, :recon_authorized).and_return(nil)
53
+ r = handler.call(command: 'hping3 -1 -c 1 192.168.1.1')
54
+ expect(r[:error]).to eq('unauthorized_recon_blocked')
55
+ expect(r[:exit]).to eq(126)
56
+ expect(r[:stderr].to_s).to match(/blocked unauthorized recon/)
57
+ ensure
58
+ Thread.current[:pwn_recon_authorized] = nil
59
+ end
60
+
61
+ it 'allows commands when recon_authorized thread flag is set' do
62
+ Thread.current[:pwn_recon_authorized] = true
63
+ r = handler.call(command: 'echo allowed_hping_guard')
64
+ expect(r[:exit]).to eq(0)
65
+ expect(r[:stdout].to_s).to include('allowed_hping_guard')
66
+ ensure
67
+ Thread.current[:pwn_recon_authorized] = nil
68
+ end
48
69
  end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'stringio'
5
+
6
+ describe 'pwn-vault Config RuntimeError retry' do
7
+ # Source/behavior contract: after Vault.edit, Config.refresh_env must run
8
+ # inside the command so RuntimeError from invalid pwn.yaml prints the error,
9
+ # waits on "Press Enter to Resolve" via $stdin.gets, then re-opens the editor.
10
+ # Validation must NOT be deferred solely to the PS1 hook.
11
+ let(:repl_src) { File.read(PWN::Plugins::REPL.method(:add_commands).source_location.first) }
12
+ let(:vault_src) { File.read(PWN::Plugins::Vault.method(:edit).source_location.first) }
13
+ let(:block) do
14
+ start = repl_src.index("Pry::Commands.create_command 'pwn-vault'")
15
+ stop = repl_src.index("Pry::Commands.create_command 'toggle-pwn-ai-debug'")
16
+ repl_src[start...stop]
17
+ end
18
+
19
+ it 'validates via PWN::Config.refresh_env inside the pwn-vault command' do
20
+ expect(block).to include('PWN::Config.refresh_env')
21
+ expect(block).to include('PWN::Plugins::Vault.edit')
22
+ expect(block).to match(/loop do/)
23
+ expect(block).to match(/rescue RuntimeError/)
24
+ end
25
+
26
+ it 'prints the error, prompts, "Press ENTER to resolve...", and waits on $stdin.gets' do
27
+ expect(block).to include('puts e.message')
28
+ expect(block).to include("print 'Press ENTER to resolve...'")
29
+ expect(block).to include('$stdin.gets')
30
+ end
31
+
32
+ it 'clears refresh_pwn_env on Config RuntimeError so PS1 does not re-raise' do
33
+ expect(block).to include('Pry.config.refresh_pwn_env = false')
34
+ end
35
+
36
+ it 'defaults decryptor path to <pwn.yaml>.decryptor (Config canonical)' do
37
+ # Ruby source uses "#{pwn_env_path}.decryptor" — match that expression.
38
+ expect(block).to include('pwn_env_path}.decryptor')
39
+ expect(block).not_to include('pwn.decryptor.yaml')
40
+ end
41
+
42
+ it 'Vault.edit alone still only flags refresh (validation stays in command)' do
43
+ expect(vault_src).to include('Pry.config.refresh_pwn_env = true')
44
+ # edit method should not call refresh_env itself
45
+ edit_start = vault_src.index('def self.edit')
46
+ edit_chunk = vault_src[edit_start...(edit_start + 1200)]
47
+ expect(edit_chunk).not_to include('refresh_env')
48
+ end
49
+
50
+ describe 'retry loop behavioral double' do
51
+ it 'prints error + resolve prompt, waits on $stdin.gets, re-invokes edit until refresh_env succeeds' do
52
+ edit_calls = 0
53
+ refresh_calls = 0
54
+ gets_calls = 0
55
+ stdout = StringIO.new
56
+
57
+ vault = Module.new do
58
+ define_singleton_method(:edit) do |**_opts|
59
+ edit_calls += 1
60
+ true
61
+ end
62
+ end
63
+
64
+ config = Module.new do
65
+ define_singleton_method(:refresh_env) do |**_opts|
66
+ refresh_calls += 1
67
+ raise 'ERROR: Unsupported AI Engine: bogon' if refresh_calls < 3
68
+
69
+ true
70
+ end
71
+ end
72
+
73
+ # Mirror the production control flow without vim/Pry command plumbing.
74
+ # Use $stdin.gets (not Kernel#gets) so ARGF/ARGV from rspec/rake cannot
75
+ # steal the read the way bare gets does under `rake`.
76
+ original_stdout = $stdout
77
+ original_stdin = $stdin
78
+ begin
79
+ $stdout = stdout
80
+ $stdin = StringIO.new("\n\n")
81
+ loop do
82
+ vault.edit(file: 'x', key: 'k', iv: 'i')
83
+ begin
84
+ config.refresh_env(pwn_env_path: 'x', pwn_dec_path: 'y', key: 'k', iv: 'i')
85
+ break
86
+ rescue RuntimeError => e
87
+ puts e
88
+ puts 'Press Enter to Resolve'
89
+ $stdin.gets
90
+ gets_calls += 1
91
+ end
92
+ end
93
+ ensure
94
+ $stdout = original_stdout
95
+ $stdin = original_stdin
96
+ end
97
+
98
+ expect(edit_calls).to eq(3)
99
+ expect(refresh_calls).to eq(3)
100
+ expect(gets_calls).to eq(2)
101
+ out = stdout.string
102
+ expect(out).to include('ERROR: Unsupported AI Engine: bogon')
103
+ expect(out.scan('Press Enter to Resolve').size).to eq(2)
104
+ end
105
+
106
+ it 'does not retry when refresh_env succeeds on first try' do
107
+ edit_calls = 0
108
+ refresh_calls = 0
109
+ vault = Module.new do
110
+ define_singleton_method(:edit) do |**_|
111
+ edit_calls += 1
112
+ end
113
+ end
114
+ config = Module.new do
115
+ define_singleton_method(:refresh_env) do |**_|
116
+ refresh_calls += 1
117
+ end
118
+ end
119
+
120
+ loop do
121
+ vault.edit
122
+ begin
123
+ config.refresh_env
124
+ break
125
+ rescue RuntimeError
126
+ raise 'unexpected retry'
127
+ end
128
+ end
129
+
130
+ expect(edit_calls).to eq(1)
131
+ expect(refresh_calls).to eq(1)
132
+ end
133
+ end
134
+ end
@@ -303,6 +303,7 @@
303
303
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Learning.verdict_for_score Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Learning.verdict_for_score`: "}]}
304
304
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.active_engine Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.active_engine`: "}]}
305
305
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.agent_flag Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.agent_flag`: "}]}
306
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.answer_howto Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.answer_howto`: "}]}
306
307
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.append_session Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.append_session`: "}]}
307
308
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.attribute_cause Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.attribute_cause`: "}]}
308
309
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
@@ -332,7 +333,10 @@
332
333
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.parse_tool_arguments Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.parse_tool_arguments`: "}]}
333
334
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.plan_first Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.plan_first`: "}]}
334
335
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.publish_usage Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.publish_usage`: "}]}
336
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.recon_authorized? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.recon_authorized?`: "}]}
335
337
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.record_metrics Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.record_metrics`: "}]}
338
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.refuse_unauthorized_recon Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.refuse_unauthorized_recon`: "}]}
339
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.request_intent Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.request_intent`: "}]}
336
340
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.run Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.run`: Supported Method Parameters\n\nfinal = PWN::AI::Agent::Loop.run(\n\nrequest: 'required - what the human typed',\nsession_id: 'optional - PWN::Sessions id (transcript is appended to it)',\nenabled_toolsets: 'optional - subset of Registry.toolsets, or nil for all',\non_tool: 'optional - ->(name, args, result) callback for live UI',\nsystem_role_content: 'optional - override default system prompt (built from session_id if not provided)'\n\n)\n"}]}
337
341
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.should_auto_introspect? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.should_auto_introspect?`: "}]}
338
342
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.stringify_tool_arguments Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.stringify_tool_arguments`: "}]}
@@ -520,6 +524,7 @@
520
524
  {"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.fresh Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.fresh`: Supported Method Parameters\n\nstate = PWN::AI::Agent::TaskSummarizer.fresh(\n\nrequest: 'optional - original user goal string'\n\n)\n"}]}
521
525
  {"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.help`: "}]}
522
526
  {"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.heuristic_decompose Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.heuristic_decompose`: Supported Method Parameters\n\ntasks = PWN::AI::Agent::TaskSummarizer.heuristic_decompose(\n\ngoal: 'required - user goal string'\n\n)\n"}]}
527
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.howto_goal? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.howto_goal?`: "}]}
523
528
  {"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.intent_phrase Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.intent_phrase`: "}]}
524
529
  {"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.interval_s Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.interval_s`: "}]}
525
530
  {"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.llm_decompose Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.llm_decompose`: "}]}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.668
4
+ version: 0.5.669
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -2532,6 +2532,7 @@ files:
2532
2532
  - spec/lib/pwn/plugins/ps_spec.rb
2533
2533
  - spec/lib/pwn/plugins/pwn_logger_spec.rb
2534
2534
  - spec/lib/pwn/plugins/rabbit_mq_spec.rb
2535
+ - spec/lib/pwn/plugins/repl_pwn_vault_spec.rb
2535
2536
  - spec/lib/pwn/plugins/repl_spec.rb
2536
2537
  - spec/lib/pwn/plugins/scannable_codes_spec.rb
2537
2538
  - spec/lib/pwn/plugins/serial_spec.rb