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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +15 -0
- data/README.md +4 -4
- data/documentation/AI-Integration.md +5 -3
- data/documentation/Agent-Tool-Registry.md +18 -14
- data/documentation/Configuration.md +2 -0
- data/documentation/Contributing.md +13 -0
- data/documentation/Diagrams.md +1 -1
- data/documentation/Home.md +4 -4
- data/documentation/How-PWN-Works.md +6 -6
- data/documentation/Persistence.md +17 -0
- data/documentation/Reinforcement-Learning.md +39 -39
- data/documentation/SDR.md +1 -1
- data/documentation/Sessions.md +1 -0
- data/documentation/Skills-Memory-Learning.md +2 -0
- data/documentation/What-is-PWN.md +3 -3
- data/documentation/diagrams/agent-tool-registry.svg +43 -43
- data/documentation/diagrams/ai-integration-tool-calling.svg +112 -85
- data/documentation/diagrams/dot/agent-tool-registry.dot +4 -4
- data/documentation/diagrams/dot/ai-integration-tool-calling.dot +7 -3
- data/documentation/diagrams/dot/memory-skills-detailed.dot +2 -2
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +6 -4
- data/documentation/diagrams/dot/persistence-filesystem.dot +1 -1
- data/documentation/diagrams/dot/sdr-radio-flow.dot +1 -1
- data/documentation/diagrams/memory-skills-detailed.svg +182 -182
- data/documentation/diagrams/overall-pwn-architecture.svg +154 -146
- data/documentation/diagrams/persistence-filesystem.svg +6 -6
- data/documentation/diagrams/sdr-radio-flow.svg +1 -1
- data/documentation/pwn-ai-Agent.md +29 -13
- data/lib/pwn/ai/agent/loop.rb +189 -1
- data/lib/pwn/ai/agent/prompt_builder.rb +24 -5
- data/lib/pwn/ai/agent/task_summarizer.rb +40 -3
- data/lib/pwn/ai/agent/tools/shell.rb +32 -0
- data/lib/pwn/memory_index.rb +17 -2
- data/lib/pwn/plugins/repl.rb +36 -7
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +31 -0
- data/spec/lib/pwn/ai/agent/task_summarizer_spec.rb +10 -0
- data/spec/lib/pwn/ai/agent/tools/shell_spec.rb +21 -0
- data/spec/lib/pwn/plugins/repl_pwn_vault_spec.rb +134 -0
- data/third_party/pwn_rdoc.jsonl +5 -0
- 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
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -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.
|
|
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
|