pwn 0.5.696 → 0.5.697
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/etc/default_skills/pwn/ai/agent/tool_guard/SKILL.md +5 -0
- data/etc/default_skills/pwn/plugins/repl/SKILL.md +7 -0
- data/lib/pwn/ai/agent/loop.rb +18 -1
- data/lib/pwn/ai/agent/prompt_builder.rb +4 -3
- data/lib/pwn/ai/agent/tool_guard.rb +129 -33
- data/lib/pwn/ai/agent/tools/ruby_eval.rb +9 -11
- data/lib/pwn/ai/agent/tools/shell.rb +6 -3
- data/lib/pwn/plugins/nmap_it.rb +47 -4
- data/lib/pwn/plugins/repl.rb +183 -1
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +4 -3
- data/spec/lib/pwn/ai/agent/tool_guard_spec.rb +80 -19
- data/spec/lib/pwn/ai/agent/tools/ruby_eval_spec.rb +2 -2
- data/spec/lib/pwn/ai/agent/tools/shell_spec.rb +3 -3
- data/spec/lib/pwn/plugins/nmap_it_spec.rb +7 -0
- data/spec/lib/pwn/plugins/repl_spec.rb +37 -1
- data/third_party/pwn_rdoc.jsonl +23 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4218feff0995079766c3b31b6beec85c9b45b1f971ac0bc78a9794c67ea8016e
|
|
4
|
+
data.tar.gz: 5a47207b366764a60b8c560bdcf77c5b12288d7fdbeb3e2d74c655a8e92e12e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 326c061669305b7069c4a66e68d958b6935ef3a9c3f90e02ee1242ad6bedfed9e5e31170fd4ae8b7b9570ab7c63fb1e7198c0cc97a5a8e3eeb96400bc329c611
|
|
7
|
+
data.tar.gz: ed8cdb221e58fdb00ae799d902121e0f2b6f629871ceb458a54ef35ee5e2ad326070c431d9c17346db442e0d62374f90f85cba074a00967d3193d41753a091a7
|
|
@@ -42,6 +42,11 @@ PWN::AI::Agent::ToolGuard.present(opts)
|
|
|
42
42
|
- `invalid_payload`
|
|
43
43
|
- `host_load`
|
|
44
44
|
- `deadline_s`
|
|
45
|
+
- `reset_timeout_budget`
|
|
46
|
+
- `mutation_count`
|
|
47
|
+
- `payload_spent`
|
|
48
|
+
- `note_timeout`
|
|
49
|
+
- `next_timeout`
|
|
45
50
|
- `timeout_lesson`
|
|
46
51
|
- `timeout_result`
|
|
47
52
|
- `timeout_prior_count`
|
|
@@ -46,6 +46,13 @@ PWN::Plugins::REPL.ready_tty(opts)
|
|
|
46
46
|
- `install_pwn_ai_completer`
|
|
47
47
|
- `restore_pwn_ai_completer`
|
|
48
48
|
- `pwn_ai_dispatch_slash`
|
|
49
|
+
- `pwn_ai_engines`
|
|
50
|
+
- `pwn_ai_provider_class`
|
|
51
|
+
- `pwn_ai_model_ids`
|
|
52
|
+
- `pwn_ai_list_llms`
|
|
53
|
+
- `pwn_ai_engine_model`
|
|
54
|
+
- `pwn_ai_run_model`
|
|
55
|
+
- `persist_ai_selection`
|
|
49
56
|
- `pwn_ai_run_cron`
|
|
50
57
|
- `pwn_ai_run_sessions`
|
|
51
58
|
- `pwn_ai_run_memory`
|
data/lib/pwn/ai/agent/loop.rb
CHANGED
|
@@ -822,7 +822,8 @@ module PWN
|
|
|
822
822
|
lesson = ToolGuard.timeout_lesson(
|
|
823
823
|
tool: name,
|
|
824
824
|
payload: opts[:args].to_s,
|
|
825
|
-
timeout: err.to_s[/timeout after (\d+)/, 1].to_i
|
|
825
|
+
timeout: err.to_s[/timeout after (\d+)/, 1].to_i,
|
|
826
|
+
task: timeout_task(opts)
|
|
826
827
|
)
|
|
827
828
|
err = lesson[:error] if lesson[:error].to_s.strip.length.positive?
|
|
828
829
|
shape = :timeout
|
|
@@ -835,6 +836,21 @@ module PWN
|
|
|
835
836
|
{ ok: true, err: nil, mistake: nil }
|
|
836
837
|
end
|
|
837
838
|
|
|
839
|
+
private_class_method def self.timeout_task(opts = {})
|
|
840
|
+
st = opts[:ts_state]
|
|
841
|
+
if st.respond_to?(:[])
|
|
842
|
+
idx = st[:plan_idx] || st['plan_idx']
|
|
843
|
+
return "task-#{idx}" unless idx.nil?
|
|
844
|
+
end
|
|
845
|
+
if st.respond_to?(:plan_idx)
|
|
846
|
+
idx = st.plan_idx
|
|
847
|
+
return "task-#{idx}" unless idx.nil?
|
|
848
|
+
end
|
|
849
|
+
opts[:session_id].to_s
|
|
850
|
+
rescue StandardError
|
|
851
|
+
opts[:session_id].to_s
|
|
852
|
+
end
|
|
853
|
+
|
|
838
854
|
# E1 — did the environment change under this tool? If Metrics CUSUM
|
|
839
855
|
# tripped for it in the last hour AND Extrospection.drift shows a
|
|
840
856
|
# toolchain/net/repo change, blame the WORLD not the AGENT.
|
|
@@ -2271,6 +2287,7 @@ module PWN
|
|
|
2271
2287
|
start_debug_session(opts)
|
|
2272
2288
|
loud_debug_tui!(debug: opts[:debug])
|
|
2273
2289
|
debug_progress(msg: "Loop.run start request=#{request[0, 240]}", debug: opts[:debug])
|
|
2290
|
+
ToolGuard.reset_timeout_budget! if defined?(ToolGuard) && ToolGuard.respond_to?(:reset_timeout_budget!)
|
|
2274
2291
|
nested = defined?(TurnFinalizer) && TurnFinalizer.user_path?
|
|
2275
2292
|
TurnFinalizer.enter_user_path! if defined?(TurnFinalizer)
|
|
2276
2293
|
engine = active_engine
|
|
@@ -100,9 +100,10 @@ module PWN
|
|
|
100
100
|
|
|
101
101
|
HOST LOAD
|
|
102
102
|
#{host_load_block}
|
|
103
|
-
On pwn_eval/shell timeout:
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
On pwn_eval/shell timeout: keep the same ruby/command and retry
|
|
104
|
+
with timeout += 180 until it finishes or the 3-hour budget is
|
|
105
|
+
gone. Only then rewrite the payload for the same goal (max 10
|
|
106
|
+
mutations per task). Any payload.
|
|
106
107
|
Record a mistake either way.
|
|
107
108
|
|
|
108
109
|
AUTONOMY
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
3
5
|
module PWN
|
|
4
6
|
module AI
|
|
5
7
|
module Agent
|
|
@@ -115,82 +117,176 @@ module PWN
|
|
|
115
117
|
{ ncpu: 1, load1: 0.0, mem_avail_mb: 0 }
|
|
116
118
|
end
|
|
117
119
|
|
|
120
|
+
TIMEOUT_STEP_S = 180
|
|
121
|
+
TIMEOUT_MAX_S = 10_800
|
|
122
|
+
MUTATION_MAX = 10
|
|
123
|
+
|
|
118
124
|
# Conservative wall-clock seconds for shell / pwn_eval.
|
|
119
|
-
# Explicit
|
|
120
|
-
# default from loadavg / ncpu / MemAvailable
|
|
121
|
-
#
|
|
125
|
+
# Explicit timeout is honored for any payload (1..TIMEOUT_MAX_S).
|
|
126
|
+
# Omit → host-derived default from loadavg / ncpu / MemAvailable.
|
|
127
|
+
# No tool-name sniffing: a 65k scan and `ls` use the same math.
|
|
122
128
|
public_class_method def self.deadline_s(opts = {})
|
|
123
129
|
kind = opts[:kind].to_s.to_sym
|
|
124
|
-
max = kind == :shell ? 180 : 90
|
|
125
130
|
asked = opts[:timeout] || opts[:timeout_s]
|
|
126
131
|
asked_i = asked.to_i
|
|
127
|
-
if asked_i.positive?
|
|
128
|
-
lo = 1
|
|
129
|
-
hi = max
|
|
130
|
-
return asked_i.clamp(lo, hi)
|
|
131
|
-
end
|
|
132
|
+
return asked_i.clamp(1, TIMEOUT_MAX_S) if asked_i.positive?
|
|
132
133
|
|
|
133
134
|
snap = host_load
|
|
134
135
|
ncpu = [snap[:ncpu].to_i, 1].max
|
|
135
136
|
load1 = snap[:load1].to_f
|
|
136
137
|
mem = snap[:mem_avail_mb].to_i
|
|
138
|
+
default_max = kind == :shell ? 180 : 90
|
|
137
139
|
base = kind == :shell ? 30 : 20
|
|
138
140
|
base += 15 if load1 > ncpu
|
|
139
141
|
base += 10 if load1 > (ncpu * 1.5)
|
|
140
142
|
base += 10 if mem.positive? && mem < 512
|
|
141
|
-
base.clamp(8,
|
|
143
|
+
base.clamp(8, default_max)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
public_class_method def self.reset_timeout_budget(opts = {})
|
|
147
|
+
return :noop unless opts.is_a?(Hash)
|
|
148
|
+
|
|
149
|
+
@timeout_spent = {}
|
|
150
|
+
@timeout_mutations = {}
|
|
151
|
+
@timeout_mutated = {}
|
|
152
|
+
:reset
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
public_class_method def self.reset_timeout_budget!
|
|
156
|
+
reset_timeout_budget
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
public_class_method def self.mutation_count(opts = {})
|
|
160
|
+
return 0 unless opts.is_a?(Hash)
|
|
161
|
+
|
|
162
|
+
timeout_mutations[task_key(opts)].to_i
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
public_class_method def self.payload_spent(opts = {})
|
|
166
|
+
return 0 unless opts.is_a?(Hash)
|
|
167
|
+
|
|
168
|
+
timeout_spent[payload_key(opts)].to_i
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
public_class_method def self.note_timeout!(opts = {})
|
|
172
|
+
return 0 unless opts.is_a?(Hash)
|
|
173
|
+
|
|
174
|
+
timeout = opts[:timeout].to_i
|
|
175
|
+
timeout = 1 if timeout < 1
|
|
176
|
+
key = payload_key(opts)
|
|
177
|
+
timeout_spent[key] = timeout_spent[key].to_i + timeout
|
|
178
|
+
if budget_exhausted?(opts.merge(spent: timeout_spent[key])) && !timeout_mutated[key]
|
|
179
|
+
timeout_mutated[key] = true
|
|
180
|
+
tkey = task_key(opts)
|
|
181
|
+
timeout_mutations[tkey] = timeout_mutations[tkey].to_i + 1
|
|
182
|
+
end
|
|
183
|
+
timeout_spent[key]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
public_class_method def self.next_timeout(opts = {})
|
|
187
|
+
base = opts[:timeout].to_i
|
|
188
|
+
base = 1 if base < 1
|
|
189
|
+
spent = opts.key?(:spent) ? opts[:spent].to_i : payload_spent(opts)
|
|
190
|
+
remaining = TIMEOUT_MAX_S - spent
|
|
191
|
+
remaining = 0 if remaining.negative?
|
|
192
|
+
[base + TIMEOUT_STEP_S, remaining, TIMEOUT_MAX_S].min
|
|
142
193
|
end
|
|
143
194
|
|
|
144
|
-
#
|
|
145
|
-
# 1.
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
195
|
+
# Timeout policy (loop-law, not a skill):
|
|
196
|
+
# 1. Same payload: timeout += 180 until the 3-hour budget is gone.
|
|
197
|
+
# 2. At the 3-hour cap: rewrite ruby/command for the same goal
|
|
198
|
+
# (one mutation). Max MUTATION_MAX mutations per task.
|
|
199
|
+
# 3. After MUTATION_MAX mutations: stop (exhausted).
|
|
149
200
|
public_class_method def self.timeout_lesson(opts = {})
|
|
150
201
|
return { scenario: :construction, error: '', hint: '' } unless opts.is_a?(Hash)
|
|
151
202
|
|
|
152
203
|
tool = opts[:tool].to_s
|
|
153
204
|
timeout = opts[:timeout].to_i
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
205
|
+
spent = payload_spent(opts)
|
|
206
|
+
spent_after = spent >= timeout && timeout.positive? ? spent : spent + [timeout, 1].max
|
|
207
|
+
nxt = next_timeout(timeout: timeout, spent: spent_after)
|
|
208
|
+
mutations = mutation_count(opts)
|
|
209
|
+
if budget_exhausted?(opts.merge(timeout: timeout, spent: spent_after))
|
|
210
|
+
if mutations >= MUTATION_MAX
|
|
211
|
+
{
|
|
212
|
+
scenario: :exhausted,
|
|
213
|
+
error: "#{tool} timeout: #{MUTATION_MAX} mutations exhausted for this task",
|
|
214
|
+
hint: "This task hit the mutation cap (#{MUTATION_MAX} rewrites after " \
|
|
215
|
+
'3-hour budgets). Do not retry the same payload. Report what ' \
|
|
216
|
+
'was tried and what remains blocked.'
|
|
217
|
+
}
|
|
218
|
+
else
|
|
219
|
+
{
|
|
220
|
+
scenario: :construction,
|
|
221
|
+
error: "#{tool} timeout: 3-hour budget exhausted; reconstruct payload to same goal",
|
|
222
|
+
hint: "The #{tool} payload used its 3-hour budget. Generate different " \
|
|
223
|
+
'ruby/command for the same goal. Mutation ' \
|
|
224
|
+
"#{[mutations, 1].max}/#{MUTATION_MAX}."
|
|
225
|
+
}
|
|
226
|
+
end
|
|
164
227
|
else
|
|
165
228
|
{
|
|
166
|
-
scenario: :
|
|
167
|
-
error: "#{tool} timeout:
|
|
168
|
-
hint: "
|
|
169
|
-
|
|
170
|
-
'Do not first raise timeout. Record this so it does not recur.'
|
|
229
|
+
scenario: :deadline,
|
|
230
|
+
error: "#{tool} timeout: deadline too short; retry with timeout += 180",
|
|
231
|
+
hint: "Keep the same #{tool} payload. This timeout (#{timeout}s) was too " \
|
|
232
|
+
"short. Retry with timeout += 180 (next_timeout=#{nxt})."
|
|
171
233
|
}
|
|
172
234
|
end
|
|
173
235
|
end
|
|
174
236
|
|
|
175
237
|
public_class_method def self.timeout_result(opts = {})
|
|
176
|
-
return { stdout: '', stderr: '', exit: nil, error: 'timeout', scenario: :
|
|
238
|
+
return { stdout: '', stderr: '', exit: nil, error: 'timeout', scenario: :deadline, hint: '', next_timeout: TIMEOUT_STEP_S, shell: shell_name } unless opts.is_a?(Hash)
|
|
177
239
|
|
|
240
|
+
timeout = opts[:timeout].to_i
|
|
241
|
+
note_timeout!(opts)
|
|
178
242
|
lesson = timeout_lesson(
|
|
179
243
|
tool: opts[:tool],
|
|
180
244
|
payload: opts[:payload],
|
|
181
|
-
timeout:
|
|
245
|
+
timeout: timeout,
|
|
246
|
+
task: opts[:task]
|
|
182
247
|
)
|
|
183
248
|
{
|
|
184
249
|
stdout: opts[:stdout].to_s,
|
|
185
250
|
stderr: opts[:stderr].to_s,
|
|
186
251
|
exit: nil,
|
|
187
|
-
error: "timeout after #{
|
|
252
|
+
error: "timeout after #{timeout}s",
|
|
188
253
|
scenario: lesson[:scenario],
|
|
189
254
|
hint: lesson[:hint],
|
|
255
|
+
next_timeout: next_timeout(timeout: timeout, spent: payload_spent(opts)),
|
|
256
|
+
mutations: mutation_count(opts),
|
|
190
257
|
shell: opts[:shell] || shell_name
|
|
191
258
|
}
|
|
192
259
|
end
|
|
193
260
|
|
|
261
|
+
private_class_method def self.timeout_spent
|
|
262
|
+
@timeout_spent ||= {}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private_class_method def self.timeout_mutations
|
|
266
|
+
@timeout_mutations ||= {}
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
private_class_method def self.timeout_mutated
|
|
270
|
+
@timeout_mutated ||= {}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
private_class_method def self.task_key(opts = {})
|
|
274
|
+
t = opts[:task]
|
|
275
|
+
t = opts[:session_id] if t.to_s.strip.empty?
|
|
276
|
+
t.to_s.strip.empty? ? 'default' : t.to_s
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private_class_method def self.payload_key(opts = {})
|
|
280
|
+
payload = opts[:payload].to_s
|
|
281
|
+
"#{task_key(opts)}:#{Digest::SHA256.hexdigest(payload)}"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
private_class_method def self.budget_exhausted?(opts = {})
|
|
285
|
+
spent = opts[:spent]
|
|
286
|
+
spent = payload_spent(opts) if spent.nil?
|
|
287
|
+
opts[:timeout].to_i >= TIMEOUT_MAX_S || spent.to_i >= TIMEOUT_MAX_S
|
|
288
|
+
end
|
|
289
|
+
|
|
194
290
|
public_class_method def self.timeout_prior_count(opts = {})
|
|
195
291
|
return 0 unless opts.is_a?(Hash)
|
|
196
292
|
return 0 unless defined?(PWN::AI::Agent::Mistakes)
|
|
@@ -24,7 +24,10 @@ PWN::AI::Agent::Registry.register(
|
|
|
24
24
|
'browser. Do not open a second browser. Close once with ' \
|
|
25
25
|
'Close once with TransparentBrowser.close(browser_obj: browser_obj). ' \
|
|
26
26
|
'Pass timeout as a conservative integer seconds estimate given HOST ' \
|
|
27
|
-
'LOAD (loadavg, ncpu, mem). Omit for a host-derived default
|
|
27
|
+
'LOAD (loadavg, ncpu, mem). Omit for a host-derived default. ' \
|
|
28
|
+
'Explicit timeout is honored up to 10800s (3 hours) for any payload. On ' \
|
|
29
|
+
'timeout, keep the same payload and retry with timeout += 180 ' \
|
|
30
|
+
'until the 3-hour budget is gone; then rewrite (max 10 mutations/task). ' \
|
|
28
31
|
'Returns captured stdout plus the inspected value of the last expression.',
|
|
29
32
|
parameters: {
|
|
30
33
|
type: 'object',
|
|
@@ -32,7 +35,7 @@ PWN::AI::Agent::Registry.register(
|
|
|
32
35
|
code: { type: 'string', description: 'Ruby source to evaluate.' },
|
|
33
36
|
timeout: {
|
|
34
37
|
type: 'integer',
|
|
35
|
-
description: 'Conservative seconds this eval should take given HOST LOAD. Omit for host-derived default.
|
|
38
|
+
description: 'Conservative seconds this eval should take given HOST LOAD. Omit for a host-derived default. Explicit values honored 1..10800 (3 hours). On timeout keep the same payload and timeout += 180; rewrite only after the 3-hour budget (max 10 mutations/task).'
|
|
36
39
|
}
|
|
37
40
|
},
|
|
38
41
|
required: %w[code]
|
|
@@ -61,7 +64,7 @@ PWN::AI::Agent::Registry.register(
|
|
|
61
64
|
old_stdout = $stdout
|
|
62
65
|
buf = StringIO.new
|
|
63
66
|
$stdout = buf
|
|
64
|
-
timeout = PWN::AI::Agent::ToolGuard.deadline_s(timeout: args[:timeout], kind: :eval)
|
|
67
|
+
timeout = PWN::AI::Agent::ToolGuard.deadline_s(timeout: args[:timeout], kind: :eval, payload: code)
|
|
65
68
|
begin
|
|
66
69
|
# rubocop:disable Security/Eval
|
|
67
70
|
# INTENTIONAL: this IS the pwn-ai → PWN bridge
|
|
@@ -89,17 +92,12 @@ PWN::AI::Agent::Registry.register(
|
|
|
89
92
|
# rubocop:enable Security/Eval
|
|
90
93
|
{ stdout: buf.string, value: val.inspect, timeout: timeout }
|
|
91
94
|
rescue Timeout::Error
|
|
92
|
-
|
|
95
|
+
PWN::AI::Agent::ToolGuard.timeout_result(
|
|
93
96
|
tool: 'pwn_eval',
|
|
94
97
|
payload: code,
|
|
95
|
-
timeout: timeout
|
|
98
|
+
timeout: timeout,
|
|
99
|
+
stdout: buf.string
|
|
96
100
|
)
|
|
97
|
-
{
|
|
98
|
-
stdout: buf.string,
|
|
99
|
-
error: "timeout after #{timeout}s",
|
|
100
|
-
scenario: lesson[:scenario],
|
|
101
|
-
hint: lesson[:hint]
|
|
102
|
-
}
|
|
103
101
|
rescue ScriptError, StandardError => e
|
|
104
102
|
{
|
|
105
103
|
stdout: buf.string,
|
|
@@ -23,14 +23,17 @@ PWN::AI::Agent::Registry.register(
|
|
|
23
23
|
'stdout/stderr/exit code. Use for OS-level work: nmap, curl, ' \
|
|
24
24
|
'ls, git, file inspection, anything not in the PWN:: namespace. ' \
|
|
25
25
|
'Pass timeout as a conservative integer seconds estimate given ' \
|
|
26
|
-
'HOST LOAD (loadavg, ncpu, mem). Omit for a host-derived default
|
|
26
|
+
'HOST LOAD (loadavg, ncpu, mem). Omit for a host-derived default. ' \
|
|
27
|
+
'Explicit timeout is honored up to 10800s (3 hours) for any payload. On ' \
|
|
28
|
+
'timeout, keep the same payload and retry with timeout += 180 ' \
|
|
29
|
+
'until the 3-hour budget is gone; then rewrite (max 10 mutations/task).',
|
|
27
30
|
parameters: {
|
|
28
31
|
type: 'object',
|
|
29
32
|
properties: {
|
|
30
33
|
command: { type: 'string', description: 'The exact shell command to run.' },
|
|
31
34
|
timeout: {
|
|
32
35
|
type: 'integer',
|
|
33
|
-
description: 'Conservative seconds this command should take given HOST LOAD. Omit for host-derived default.
|
|
36
|
+
description: 'Conservative seconds this command should take given HOST LOAD. Omit for a host-derived default. Explicit values honored 1..10800 (3 hours). On timeout keep the same payload and timeout += 180; rewrite only after the 3-hour budget (max 10 mutations/task).'
|
|
34
37
|
}
|
|
35
38
|
},
|
|
36
39
|
required: %w[command]
|
|
@@ -52,7 +55,7 @@ PWN::AI::Agent::Registry.register(
|
|
|
52
55
|
.gsub(/\\\r?\n/, ' ')
|
|
53
56
|
.gsub(/\\+\s*\z/, '')
|
|
54
57
|
.strip
|
|
55
|
-
timeout = PWN::AI::Agent::ToolGuard.deadline_s(timeout: args[:timeout], kind: :shell)
|
|
58
|
+
timeout = PWN::AI::Agent::ToolGuard.deadline_s(timeout: args[:timeout], kind: :shell, payload: cmd)
|
|
56
59
|
if cmd.empty? || PWN::AI::Agent::ToolGuard.placeholder?(text: cmd)
|
|
57
60
|
return PWN::AI::Agent::ToolGuard.invalid_payload(
|
|
58
61
|
hint: 'command is required (string). Do not send ..., {...}, {…}, or empty. ' \
|
data/lib/pwn/plugins/nmap_it.rb
CHANGED
|
@@ -14,19 +14,53 @@ module PWN
|
|
|
14
14
|
# nmap.connect_scan = true
|
|
15
15
|
# nmap.service_scan = true
|
|
16
16
|
# nmap.verbose = true
|
|
17
|
-
# nmap.ports = [1..1024,1337]
|
|
17
|
+
# nmap.ports = [1..1024, 1337]
|
|
18
18
|
# nmap.targets = '127.0.0.1'
|
|
19
|
-
# nmap.xml = '/tmp/nmap_port_scan_res.xml'
|
|
19
|
+
# nmap.xml = '/tmp/nmap_port_scan_res.xml' # alias of output_xml
|
|
20
20
|
# end
|
|
21
|
+
#
|
|
22
|
+
# PWN::Plugins::NmapIt.port_scan(
|
|
23
|
+
# targets: '127.0.0.1',
|
|
24
|
+
# ports: '1-65535',
|
|
25
|
+
# connect_scan: true,
|
|
26
|
+
# service_scan: true,
|
|
27
|
+
# script: 'vuln,safe',
|
|
28
|
+
# xml: '/tmp/nmap_port_scan_res.xml'
|
|
29
|
+
# )
|
|
21
30
|
|
|
22
|
-
public_class_method def self.port_scan
|
|
31
|
+
public_class_method def self.port_scan(opts = {})
|
|
23
32
|
Nmap::Command.sudo do |nmap|
|
|
24
|
-
|
|
33
|
+
apply_port_scan_opts(nmap: nmap, opts: opts)
|
|
34
|
+
yield(nmap_compat(nmap: nmap)) if block_given?
|
|
25
35
|
end
|
|
26
36
|
rescue StandardError => e
|
|
27
37
|
raise e
|
|
28
38
|
end
|
|
29
39
|
|
|
40
|
+
private_class_method def self.apply_port_scan_opts(opts = {})
|
|
41
|
+
nmap = opts[:nmap]
|
|
42
|
+
args = opts[:opts]
|
|
43
|
+
return nmap unless args.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
nmap.targets = args[:targets] || args[:target] if args[:targets] || args[:target]
|
|
46
|
+
nmap.ports = args[:ports] if args.key?(:ports)
|
|
47
|
+
xml = args[:xml] || args[:output_xml]
|
|
48
|
+
nmap.output_xml = xml if xml
|
|
49
|
+
nmap.connect_scan = args[:connect_scan] if args.key?(:connect_scan)
|
|
50
|
+
nmap.service_scan = args[:service_scan] || args[:service_detection] if args.key?(:service_scan) || args.key?(:service_detection)
|
|
51
|
+
nmap.verbose = args[:verbose] if args.key?(:verbose)
|
|
52
|
+
scripts = args[:script] || args[:scripts] || args[:vuln_scripts]
|
|
53
|
+
nmap.script = scripts if scripts
|
|
54
|
+
nmap
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private_class_method def self.nmap_compat(opts = {})
|
|
58
|
+
nmap = opts[:nmap]
|
|
59
|
+
nmap.define_singleton_method(:xml=) { |path| self.output_xml = path }
|
|
60
|
+
nmap.define_singleton_method(:xml) { output_xml }
|
|
61
|
+
nmap
|
|
62
|
+
end
|
|
63
|
+
|
|
30
64
|
# Supported Method Parameters::
|
|
31
65
|
# PWN::Plugins::NmapIt.parse_xml_results(:xml_file => 'required - path to nmap xml results') do |xml|
|
|
32
66
|
# puts xml.public_methods
|
|
@@ -103,6 +137,15 @@ module PWN
|
|
|
103
137
|
nmap.xml = '/tmp/nmap_port_scan_res.xml'
|
|
104
138
|
end
|
|
105
139
|
|
|
140
|
+
#{self}.port_scan(
|
|
141
|
+
targets: '127.0.0.1',
|
|
142
|
+
ports: '1-65535',
|
|
143
|
+
connect_scan: true,
|
|
144
|
+
service_scan: true,
|
|
145
|
+
script: 'vuln,safe',
|
|
146
|
+
xml: '/tmp/nmap_port_scan_res.xml'
|
|
147
|
+
)
|
|
148
|
+
|
|
106
149
|
#{self}.parse_xml_results(:xml_file => 'required - path to nmap xml results') do |xml|
|
|
107
150
|
xml.each_host do |host|
|
|
108
151
|
puts host.ip
|
data/lib/pwn/plugins/repl.rb
CHANGED
|
@@ -1557,7 +1557,7 @@ module PWN
|
|
|
1557
1557
|
end
|
|
1558
1558
|
|
|
1559
1559
|
PWN_AI_SLASH_COMMANDS = %w[
|
|
1560
|
-
/back /cron /debug /delegate /help /memory /sessions /skills
|
|
1560
|
+
/back /cron /debug /delegate /help /memory /model /sessions /skills
|
|
1561
1561
|
].freeze
|
|
1562
1562
|
|
|
1563
1563
|
PWN_AI_SLASH_SUBCOMMANDS = {
|
|
@@ -1566,6 +1566,7 @@ module PWN
|
|
|
1566
1566
|
'/delegate' => [],
|
|
1567
1567
|
'/help' => [],
|
|
1568
1568
|
'/memory' => %w[list recall remember forget clear],
|
|
1569
|
+
'/model' => %w[list],
|
|
1569
1570
|
'/sessions' => %w[list resume delete stats],
|
|
1570
1571
|
'/skills' => %w[list recall]
|
|
1571
1572
|
}.freeze
|
|
@@ -1618,6 +1619,20 @@ module PWN
|
|
|
1618
1619
|
|
|
1619
1620
|
cmd = tokens.first
|
|
1620
1621
|
sub_prefix = tokens.last.to_s
|
|
1622
|
+
if cmd == '/model'
|
|
1623
|
+
engines = pwn_ai_engines
|
|
1624
|
+
if tokens.length == 2
|
|
1625
|
+
pool = (%w[list] + engines)
|
|
1626
|
+
return pool.select { |s| sub_prefix.empty? || s.start_with?(sub_prefix) }
|
|
1627
|
+
end
|
|
1628
|
+
return %w[llms].select { |s| sub_prefix.empty? || s.start_with?(sub_prefix) } if tokens.length == 3 && tokens[1] == 'list'
|
|
1629
|
+
|
|
1630
|
+
if tokens.length >= 3
|
|
1631
|
+
current = pwn_ai_engine_model(engine: tokens[1]).to_s
|
|
1632
|
+
hits = [current].reject(&:empty?).select { |s| sub_prefix.empty? || s.start_with?(sub_prefix) }
|
|
1633
|
+
return hits unless hits.empty?
|
|
1634
|
+
end
|
|
1635
|
+
end
|
|
1621
1636
|
subs = Array(PWN_AI_SLASH_SUBCOMMANDS[cmd])
|
|
1622
1637
|
hits = subs.select { |s| sub_prefix.empty? || s.start_with?(sub_prefix) }
|
|
1623
1638
|
hits = [target] if hits.empty? && !target.empty?
|
|
@@ -1735,6 +1750,8 @@ module PWN
|
|
|
1735
1750
|
when '/delegate'
|
|
1736
1751
|
puts "[*] Delegating: #{args.join(' ')}"
|
|
1737
1752
|
puts ' Use agent_list / agent_debate from pwn-ai, or pwn-ai-delegate in the pwn REPL.'
|
|
1753
|
+
when '/model'
|
|
1754
|
+
pwn_ai_run_model(args: args)
|
|
1738
1755
|
end
|
|
1739
1756
|
true
|
|
1740
1757
|
rescue StandardError => e
|
|
@@ -1742,6 +1759,171 @@ module PWN
|
|
|
1742
1759
|
true
|
|
1743
1760
|
end
|
|
1744
1761
|
|
|
1762
|
+
public_class_method def self.pwn_ai_engines(opts = {})
|
|
1763
|
+
return [] unless opts.is_a?(Hash)
|
|
1764
|
+
|
|
1765
|
+
tmpl = {}
|
|
1766
|
+
tmpl = PWN::Config.env_template[:ai] if defined?(PWN::Config) && PWN::Config.respond_to?(:env_template)
|
|
1767
|
+
keys = tmpl.select { |_k, v| v.is_a?(Hash) && v.key?(:model) }.keys.map(&:to_s)
|
|
1768
|
+
if defined?(PWN::Env) && PWN::Env.is_a?(Hash) && PWN::Env[:ai].is_a?(Hash)
|
|
1769
|
+
PWN::Env[:ai].each do |k, v|
|
|
1770
|
+
next unless v.is_a?(Hash)
|
|
1771
|
+
next if %i[agent driver_opts].include?(k.to_sym)
|
|
1772
|
+
|
|
1773
|
+
keys << k.to_s if v.key?(:model) || v.key?(:key) || v.key?(:base_uri)
|
|
1774
|
+
end
|
|
1775
|
+
end
|
|
1776
|
+
keys.uniq.sort
|
|
1777
|
+
end
|
|
1778
|
+
|
|
1779
|
+
public_class_method def self.pwn_ai_provider_class(opts = {})
|
|
1780
|
+
engine = opts[:engine].to_s.downcase
|
|
1781
|
+
map = {
|
|
1782
|
+
'anthropic' => 'Anthropic',
|
|
1783
|
+
'gemini' => 'Gemini',
|
|
1784
|
+
'grok' => 'Grok',
|
|
1785
|
+
'ollama' => 'Ollama',
|
|
1786
|
+
'openai' => 'OpenAI',
|
|
1787
|
+
'openwebui' => 'OpenWebUI'
|
|
1788
|
+
}
|
|
1789
|
+
name = map[engine]
|
|
1790
|
+
return nil if name.nil? || !defined?(PWN::AI) || !PWN::AI.const_defined?(name)
|
|
1791
|
+
|
|
1792
|
+
PWN::AI.const_get(name)
|
|
1793
|
+
end
|
|
1794
|
+
|
|
1795
|
+
public_class_method def self.pwn_ai_model_ids(opts = {})
|
|
1796
|
+
raw = opts[:models]
|
|
1797
|
+
rows = case raw
|
|
1798
|
+
when Array then raw
|
|
1799
|
+
when Hash then raw[:data] || raw[:models] || raw['data'] || raw['models'] || []
|
|
1800
|
+
else []
|
|
1801
|
+
end
|
|
1802
|
+
Array(rows).filter_map do |row|
|
|
1803
|
+
if row.is_a?(Hash)
|
|
1804
|
+
row[:id] || row['id'] || row[:name] || row['name'] || row[:model] || row['model']
|
|
1805
|
+
else
|
|
1806
|
+
row.to_s
|
|
1807
|
+
end
|
|
1808
|
+
end.map(&:to_s).reject(&:empty?).uniq
|
|
1809
|
+
end
|
|
1810
|
+
|
|
1811
|
+
public_class_method def self.pwn_ai_list_llms(opts = {})
|
|
1812
|
+
engine = opts[:engine].to_s
|
|
1813
|
+
engine = PWN::Env.dig(:ai, :active).to_s if engine.empty? && defined?(PWN::Env)
|
|
1814
|
+
raise 'no active engine — /model <engine> first' if engine.empty?
|
|
1815
|
+
|
|
1816
|
+
klass = pwn_ai_provider_class(engine: engine)
|
|
1817
|
+
raise "#{engine} has no PWN::AI provider with get_models" unless klass.respond_to?(:get_models)
|
|
1818
|
+
|
|
1819
|
+
ids = pwn_ai_model_ids(models: klass.get_models)
|
|
1820
|
+
puts "[*] #{engine} llms (#{ids.length})"
|
|
1821
|
+
ids.each { |id| puts id }
|
|
1822
|
+
ids
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
public_class_method def self.pwn_ai_engine_model(opts = {})
|
|
1826
|
+
engine = opts[:engine].to_s.downcase.to_sym
|
|
1827
|
+
return '' if engine.empty?
|
|
1828
|
+
return '' unless defined?(PWN::Env) && PWN::Env.is_a?(Hash)
|
|
1829
|
+
|
|
1830
|
+
PWN::Env.dig(:ai, engine, :model).to_s
|
|
1831
|
+
end
|
|
1832
|
+
|
|
1833
|
+
public_class_method def self.pwn_ai_run_model(opts = {})
|
|
1834
|
+
args = Array(opts[:args]).map(&:to_s)
|
|
1835
|
+
engines = pwn_ai_engines
|
|
1836
|
+
current = defined?(PWN::Env) && PWN::Env.is_a?(Hash) ? PWN::Env.dig(:ai, :active).to_s : ''
|
|
1837
|
+
current_model = pwn_ai_engine_model(engine: current)
|
|
1838
|
+
sub = args[0].to_s
|
|
1839
|
+
if sub.empty? || %w[show status].include?(sub)
|
|
1840
|
+
msg = "active=#{current.empty? ? '(none)' : current} model=#{current_model.empty? ? '(unset)' : current_model}"
|
|
1841
|
+
puts "[*] #{msg}"
|
|
1842
|
+
return msg
|
|
1843
|
+
end
|
|
1844
|
+
if %w[list help].include?(sub)
|
|
1845
|
+
return pwn_ai_list_llms(engine: current) if args[1].to_s == 'llms'
|
|
1846
|
+
|
|
1847
|
+
puts 'pwn-ai /model — switch provider and model in this session'
|
|
1848
|
+
puts " current: #{current} #{current_model}"
|
|
1849
|
+
puts ' usage: /model [list] | /model list llms | /model <engine> [model] | /model <model>'
|
|
1850
|
+
engines.each do |eng|
|
|
1851
|
+
mark = eng == current ? '*' : ' '
|
|
1852
|
+
puts " #{mark} #{eng} #{pwn_ai_engine_model(engine: eng)}"
|
|
1853
|
+
end
|
|
1854
|
+
return engines
|
|
1855
|
+
end
|
|
1856
|
+
|
|
1857
|
+
engine = nil
|
|
1858
|
+
model = nil
|
|
1859
|
+
if engines.include?(sub)
|
|
1860
|
+
engine = sub
|
|
1861
|
+
model = args[1..].join(' ')
|
|
1862
|
+
model = nil if model.strip.empty?
|
|
1863
|
+
else
|
|
1864
|
+
engine = current
|
|
1865
|
+
model = args.join(' ')
|
|
1866
|
+
end
|
|
1867
|
+
raise "no active engine — /model <engine> first (#{engines.join(', ')})" if engine.to_s.empty?
|
|
1868
|
+
raise "unknown engine #{engine.inspect} — try: #{engines.join(', ')}" unless engines.include?(engine.to_s)
|
|
1869
|
+
|
|
1870
|
+
PWN::Env[:ai] ||= {}
|
|
1871
|
+
PWN::Env[:ai][engine.to_sym] ||= {}
|
|
1872
|
+
PWN::Env[:ai][:active] = engine.to_s
|
|
1873
|
+
PWN::Env[:ai][engine.to_sym][:model] = model unless model.to_s.strip.empty?
|
|
1874
|
+
persisted = persist_ai_selection(engine: engine, model: PWN::Env[:ai][engine.to_sym][:model])
|
|
1875
|
+
shown = PWN::Env[:ai][engine.to_sym][:model]
|
|
1876
|
+
msg = "active=#{engine} model=#{shown.to_s.empty? ? '(unset)' : shown}"
|
|
1877
|
+
msg = "#{msg} (session only)" unless persisted
|
|
1878
|
+
puts "[*] #{msg}"
|
|
1879
|
+
msg
|
|
1880
|
+
end
|
|
1881
|
+
|
|
1882
|
+
public_class_method def self.persist_ai_selection(opts = {})
|
|
1883
|
+
engine = opts[:engine].to_s
|
|
1884
|
+
model = opts[:model]
|
|
1885
|
+
return false if engine.empty?
|
|
1886
|
+
|
|
1887
|
+
env_path = nil
|
|
1888
|
+
dec_path = nil
|
|
1889
|
+
if defined?(PWN::Env) && PWN::Env.is_a?(Hash)
|
|
1890
|
+
env_path = PWN::Env.dig(:driver_opts, :pwn_env_path)
|
|
1891
|
+
dec_path = PWN::Env.dig(:driver_opts, :pwn_dec_path)
|
|
1892
|
+
end
|
|
1893
|
+
env_path = env_path.to_s.strip
|
|
1894
|
+
env_path = File.join(Dir.home, '.pwn', 'pwn.yaml') if env_path.empty?
|
|
1895
|
+
dec_path = dec_path.to_s.strip
|
|
1896
|
+
dec_path = "#{env_path}.decryptor" if dec_path.empty?
|
|
1897
|
+
return false unless File.exist?(env_path) && File.exist?(dec_path) && File.readable?(dec_path)
|
|
1898
|
+
|
|
1899
|
+
decryptor = YAML.load_file(dec_path, symbolize_names: true)
|
|
1900
|
+
key = decryptor.is_a?(Hash) ? decryptor[:key] : nil
|
|
1901
|
+
iv = decryptor.is_a?(Hash) ? decryptor[:iv] : nil
|
|
1902
|
+
return false if key.to_s.strip.empty? || iv.to_s.strip.empty?
|
|
1903
|
+
|
|
1904
|
+
PWN::Plugins::Vault.decrypt(file: env_path, key: key, iv: iv)
|
|
1905
|
+
begin
|
|
1906
|
+
cfg = YAML.load_file(env_path, symbolize_names: true)
|
|
1907
|
+
cfg = {} unless cfg.is_a?(Hash)
|
|
1908
|
+
cfg[:ai] = {} unless cfg[:ai].is_a?(Hash)
|
|
1909
|
+
cfg[:ai][:active] = engine
|
|
1910
|
+
unless model.to_s.strip.empty?
|
|
1911
|
+
slot = engine.to_sym
|
|
1912
|
+
cfg[:ai][slot] = {} unless cfg[:ai][slot].is_a?(Hash)
|
|
1913
|
+
cfg[:ai][slot][:model] = model
|
|
1914
|
+
end
|
|
1915
|
+
yaml_env = YAML.dump(cfg).gsub(/^(\s*):/, '\1')
|
|
1916
|
+
File.write(env_path, yaml_env)
|
|
1917
|
+
File.chmod(0o600, env_path)
|
|
1918
|
+
ensure
|
|
1919
|
+
PWN::Plugins::Vault.encrypt(file: env_path, key: key, iv: iv)
|
|
1920
|
+
end
|
|
1921
|
+
true
|
|
1922
|
+
rescue StandardError => e
|
|
1923
|
+
warn "[pwn-ai] /model persist skipped: #{e.class}: #{e.message}"
|
|
1924
|
+
false
|
|
1925
|
+
end
|
|
1926
|
+
|
|
1745
1927
|
public_class_method def self.pwn_ai_run_cron(opts = {})
|
|
1746
1928
|
args = Array(opts[:args])
|
|
1747
1929
|
sub = args[0] || 'list'
|
data/lib/pwn/version.rb
CHANGED
|
@@ -427,12 +427,13 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
427
427
|
expect(src).not_to match(/dispatch #\{name\} ok_len=/)
|
|
428
428
|
end
|
|
429
429
|
|
|
430
|
-
it 'records a
|
|
430
|
+
it 'records a timeout increment mistake instead of treating success:true as ok' do
|
|
431
431
|
tmp = Dir.mktmpdir
|
|
432
432
|
stub_const('PWN::AI::Agent::Mistakes::MISTAKES_FILE', File.join(tmp, 'mistakes.json'))
|
|
433
|
+
PWN::AI::Agent::ToolGuard.reset_timeout_budget! if PWN::AI::Agent::ToolGuard.respond_to?(:reset_timeout_budget!)
|
|
433
434
|
raw = JSON.generate(
|
|
434
435
|
success: true,
|
|
435
|
-
result: { stdout: '', error: 'timeout after 20s', hint: 'x', scenario: '
|
|
436
|
+
result: { stdout: '', error: 'timeout after 20s', hint: 'x', scenario: 'deadline' }
|
|
436
437
|
)
|
|
437
438
|
tele = described_class.send(
|
|
438
439
|
:record_metrics,
|
|
@@ -443,7 +444,7 @@ describe PWN::AI::Agent::Loop do # rubocop:disable Metrics/BlockLength
|
|
|
443
444
|
)
|
|
444
445
|
expect(tele[:ok]).to eq false
|
|
445
446
|
expect(tele[:mistake]).to be_a(Hash)
|
|
446
|
-
expect(tele[:mistake][:error].to_s).to match(/
|
|
447
|
+
expect(tele[:mistake][:error].to_s).to match(/timeout \+= 180|too short/i)
|
|
447
448
|
expect(tele[:mistake][:shape].to_s).to eq('timeout')
|
|
448
449
|
end
|
|
449
450
|
|
|
@@ -68,40 +68,101 @@ describe PWN::AI::Agent::ToolGuard do
|
|
|
68
68
|
expect(snap[:mem_avail_mb].to_i).to be >= 0
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
it '
|
|
71
|
+
it 'honors any explicit timeout up to 3 hours and derives a default when omitted' do
|
|
72
72
|
expect(described_class.deadline_s(timeout: 1, kind: :eval)).to eq(1)
|
|
73
|
-
expect(described_class.deadline_s(timeout:
|
|
74
|
-
expect(described_class.deadline_s(timeout:
|
|
73
|
+
expect(described_class.deadline_s(timeout: 300, kind: :eval)).to eq(300)
|
|
74
|
+
expect(described_class.deadline_s(timeout: 3_600, kind: :shell)).to eq(3_600)
|
|
75
|
+
expect(described_class.deadline_s(timeout: 99_999, kind: :eval)).to eq(10_800)
|
|
75
76
|
omitted = described_class.deadline_s(kind: :eval)
|
|
76
77
|
expect(omitted).to be_between(8, 90)
|
|
77
78
|
end
|
|
79
|
+
|
|
80
|
+
it 'does not sniff payloads for named tools' do
|
|
81
|
+
src = File.read(described_class.method(:deadline_s).source_location.first)
|
|
82
|
+
expect(src).not_to match(/\bnmap\b/i)
|
|
83
|
+
expect(src).not_to match(/\bsqlmap\b/i)
|
|
84
|
+
expect(src).not_to match(/\bhydra\b/i)
|
|
85
|
+
expect(described_class).not_to respond_to(:long_work?)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe '.next_timeout' do
|
|
90
|
+
it 'adds 180 seconds until the 3-hour cap' do
|
|
91
|
+
expect(described_class.next_timeout(timeout: 60)).to eq(240)
|
|
92
|
+
expect(described_class.next_timeout(timeout: 180)).to eq(360)
|
|
93
|
+
expect(described_class.next_timeout(timeout: 10_800)).to eq(10_800)
|
|
94
|
+
end
|
|
78
95
|
end
|
|
79
96
|
|
|
80
97
|
describe '.timeout_lesson' do
|
|
81
|
-
|
|
98
|
+
before do
|
|
99
|
+
described_class.reset_timeout_budget!
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'increments timeout on the same payload before rewriting' do
|
|
82
103
|
tmp = Dir.mktmpdir
|
|
83
104
|
stub_const('PWN::AI::Agent::Mistakes::MISTAKES_FILE', File.join(tmp, 'mistakes.json'))
|
|
84
|
-
first = described_class.timeout_lesson(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
first = described_class.timeout_lesson(
|
|
106
|
+
tool: 'pwn_eval',
|
|
107
|
+
payload: 'sleep 3',
|
|
108
|
+
timeout: 180,
|
|
109
|
+
task: 't1'
|
|
110
|
+
)
|
|
111
|
+
expect(first[:scenario]).to eq(:deadline)
|
|
112
|
+
expect(first[:hint]).to match(/timeout \+= 180|next_timeout/i)
|
|
113
|
+
expect(first[:hint]).not_to match(/reconstruct/i)
|
|
114
|
+
expect(described_class.next_timeout(timeout: 180, spent: 180)).to eq(360)
|
|
89
115
|
end
|
|
90
116
|
|
|
91
|
-
it '
|
|
117
|
+
it 'rewrites the payload only after the 3-hour budget is exhausted' do
|
|
92
118
|
tmp = Dir.mktmpdir
|
|
93
119
|
stub_const('PWN::AI::Agent::Mistakes::MISTAKES_FILE', File.join(tmp, 'mistakes.json'))
|
|
94
|
-
|
|
120
|
+
described_class.note_timeout!(
|
|
121
|
+
tool: 'shell',
|
|
122
|
+
payload: 'sleep 3',
|
|
123
|
+
timeout: 10_800,
|
|
124
|
+
task: 't1'
|
|
125
|
+
)
|
|
126
|
+
lesson = described_class.timeout_lesson(
|
|
127
|
+
tool: 'shell',
|
|
128
|
+
payload: 'sleep 3',
|
|
129
|
+
timeout: 10_800,
|
|
130
|
+
task: 't1'
|
|
131
|
+
)
|
|
132
|
+
expect(lesson[:scenario]).to eq(:construction)
|
|
133
|
+
expect(lesson[:hint]).to match(/3-hour budget|Generate different/i)
|
|
134
|
+
expect(described_class.mutation_count(task: 't1')).to eq(1)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'stops after 10 mutations on the same task' do
|
|
138
|
+
10.times do |i|
|
|
139
|
+
described_class.note_timeout!(
|
|
140
|
+
tool: 'shell',
|
|
141
|
+
payload: "payload-#{i}",
|
|
142
|
+
timeout: 10_800,
|
|
143
|
+
task: 't1'
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
last = described_class.timeout_lesson(
|
|
147
|
+
tool: 'shell',
|
|
148
|
+
payload: 'payload-9',
|
|
149
|
+
timeout: 10_800,
|
|
150
|
+
task: 't1'
|
|
151
|
+
)
|
|
152
|
+
expect(described_class.mutation_count(task: 't1')).to eq(10)
|
|
153
|
+
expect(last[:scenario]).to eq(:exhausted)
|
|
154
|
+
expect(last[:hint]).to match(/10 mutations|mutation cap/i)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'includes next_timeout on a mid-budget timeout result' do
|
|
158
|
+
out = described_class.timeout_result(
|
|
95
159
|
tool: 'shell',
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
shape: :timeout
|
|
160
|
+
payload: 'sleep 3',
|
|
161
|
+
timeout: 180,
|
|
162
|
+
task: 't1'
|
|
100
163
|
)
|
|
101
|
-
|
|
102
|
-
expect(
|
|
103
|
-
expect(second[:hint]).to match(/timeout was too short|deadline too short|raise a conservative/i)
|
|
104
|
-
expect(second[:error]).to match(/too short/)
|
|
164
|
+
expect(out[:scenario]).to eq(:deadline)
|
|
165
|
+
expect(out[:next_timeout]).to eq(360)
|
|
105
166
|
end
|
|
106
167
|
end
|
|
107
168
|
end
|
|
@@ -49,8 +49,8 @@ describe 'PWN::AI::Agent::Tools ruby_eval' do
|
|
|
49
49
|
result = entry.handler.call(code: 'sleep 3', timeout: 1)
|
|
50
50
|
expect(Time.now - t0).to be < 2.5
|
|
51
51
|
expect(result[:error].to_s).to match(/timeout after 1s/)
|
|
52
|
-
expect(result[:hint].to_s).to match(/
|
|
53
|
-
expect(result[:scenario].to_s).to eq('
|
|
52
|
+
expect(result[:hint].to_s).to match(/timeout \+= 180|next_timeout|same .*payload/i)
|
|
53
|
+
expect(result[:scenario].to_s).to eq('deadline')
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
it 'Dispatch.call JSON-wraps syntax errors instead of raising' do
|
|
@@ -60,12 +60,12 @@ describe 'PWN::AI::Agent::Tools shell' do
|
|
|
60
60
|
expect(r[:exit]).to eq(0)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
it 'on timeout
|
|
63
|
+
it 'on timeout keeps the same command and increments timeout' do
|
|
64
64
|
tmp = Dir.mktmpdir
|
|
65
65
|
stub_const('PWN::AI::Agent::Mistakes::MISTAKES_FILE', File.join(tmp, 'mistakes.json'))
|
|
66
66
|
r = handler.call(command: 'sleep 3', timeout: 1)
|
|
67
67
|
expect(r[:error].to_s).to match(/timeout after 1s/)
|
|
68
|
-
expect(r[:hint].to_s).to match(/
|
|
69
|
-
expect(r[:scenario].to_s).to eq('
|
|
68
|
+
expect(r[:hint].to_s).to match(/timeout \+= 180|next_timeout|same .*payload/i)
|
|
69
|
+
expect(r[:scenario].to_s).to eq('deadline')
|
|
70
70
|
end
|
|
71
71
|
end
|
|
@@ -12,4 +12,11 @@ describe PWN::Plugins::NmapIt do
|
|
|
12
12
|
help_response = PWN::Plugins::NmapIt
|
|
13
13
|
expect(help_response).to respond_to :help
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
it 'port_scan accepts opts = {} and aliases xml to output_xml' do
|
|
17
|
+
expect(described_class.method(:port_scan).parameters).to include(%i[opt opts])
|
|
18
|
+
src = File.read(described_class.method(:port_scan).source_location.first)
|
|
19
|
+
expect(src).to include('opts[')
|
|
20
|
+
expect(src).to include('output_xml')
|
|
21
|
+
end
|
|
15
22
|
end
|
|
@@ -67,11 +67,15 @@ describe PWN::Plugins::REPL do
|
|
|
67
67
|
hits = described_class.pwn_ai_complete(target: '/sk', line: '/sk')
|
|
68
68
|
expect(hits).to include('/skills')
|
|
69
69
|
hits = described_class.pwn_ai_complete(target: '/', line: '/')
|
|
70
|
-
%w[/cron /skills /sessions /memory /debug /back /help].each do |cmd|
|
|
70
|
+
%w[/cron /skills /sessions /memory /debug /back /help /model].each do |cmd|
|
|
71
71
|
expect(hits).to include(cmd)
|
|
72
72
|
end
|
|
73
73
|
hits = described_class.pwn_ai_complete(target: 'li', line: '/cron li')
|
|
74
74
|
expect(hits).to include('list')
|
|
75
|
+
hits = described_class.pwn_ai_complete(target: 'li', line: '/model li')
|
|
76
|
+
expect(hits).to include('list')
|
|
77
|
+
hits = described_class.pwn_ai_complete(target: 'll', line: '/model list ll')
|
|
78
|
+
expect(hits).to include('llms')
|
|
75
79
|
end
|
|
76
80
|
|
|
77
81
|
it 'completes host-native paths when slash is not the first character' do
|
|
@@ -101,5 +105,37 @@ describe PWN::Plugins::REPL do
|
|
|
101
105
|
expect(hook).to match(/pwn_ai_dispatch_slash!/)
|
|
102
106
|
expect(described_class).to respond_to(:pwn_ai_dispatch_slash!)
|
|
103
107
|
end
|
|
108
|
+
|
|
109
|
+
it 'switches the live engine and model via /model without Loop.run' do
|
|
110
|
+
expect(described_class).to respond_to(:pwn_ai_run_model)
|
|
111
|
+
PWN::Env[:ai] ||= {}
|
|
112
|
+
PWN::Env[:ai][:grok] ||= {}
|
|
113
|
+
prev_active = PWN::Env[:ai][:active]
|
|
114
|
+
prev_model = PWN::Env[:ai][:grok][:model]
|
|
115
|
+
allow(described_class).to receive(:persist_ai_selection).and_return(false)
|
|
116
|
+
out = described_class.pwn_ai_run_model(args: %w[grok pwn-ai-test-model])
|
|
117
|
+
expect(PWN::Env[:ai][:active].to_s).to eq('grok')
|
|
118
|
+
expect(PWN::Env[:ai][:grok][:model]).to eq('pwn-ai-test-model')
|
|
119
|
+
expect(out.to_s).to match(/grok/i)
|
|
120
|
+
ensure
|
|
121
|
+
if PWN::Env.is_a?(Hash) && PWN::Env[:ai].is_a?(Hash)
|
|
122
|
+
PWN::Env[:ai][:active] = prev_active if defined?(prev_active)
|
|
123
|
+
PWN::Env[:ai][:grok][:model] = prev_model if defined?(prev_model) && PWN::Env[:ai][:grok].is_a?(Hash)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'lists llm ids from the active provider via /model list llms' do
|
|
128
|
+
expect(described_class).to respond_to(:pwn_ai_list_llms)
|
|
129
|
+
PWN::Env[:ai] ||= {}
|
|
130
|
+
prev_active = PWN::Env[:ai][:active]
|
|
131
|
+
PWN::Env[:ai][:active] = 'grok'
|
|
132
|
+
allow(PWN::AI::Grok).to receive(:get_models).and_return(
|
|
133
|
+
[{ id: 'grok-test-a' }, { id: 'grok-test-b' }]
|
|
134
|
+
)
|
|
135
|
+
ids = described_class.pwn_ai_run_model(args: %w[list llms])
|
|
136
|
+
expect(ids).to eq(%w[grok-test-a grok-test-b])
|
|
137
|
+
ensure
|
|
138
|
+
PWN::Env[:ai][:active] = prev_active if PWN::Env.is_a?(Hash) && PWN::Env[:ai].is_a?(Hash)
|
|
139
|
+
end
|
|
104
140
|
end
|
|
105
141
|
end
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -383,6 +383,7 @@
|
|
|
383
383
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.task_summary_flush! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.task_summary_flush!`: "}]}
|
|
384
384
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.task_summary_plan! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.task_summary_plan!`: "}]}
|
|
385
385
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.task_summary_record! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.task_summary_record!`: "}]}
|
|
386
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.timeout_task Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.timeout_task`: "}]}
|
|
386
387
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.tool_effects Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.tool_effects`: "}]}
|
|
387
388
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.world_knowledge? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.world_knowledge?`: "}]}
|
|
388
389
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::Loop.write_verified? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Loop.write_verified?`: "}]}
|
|
@@ -706,18 +707,30 @@
|
|
|
706
707
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TaskSummarizer.why_bit Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TaskSummarizer.why_bit`: "}]}
|
|
707
708
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.authors`: "}]}
|
|
708
709
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.bashism? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.bashism?`: "}]}
|
|
710
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.budget_exhausted? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.budget_exhausted?`: "}]}
|
|
709
711
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.coerce_args Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.coerce_args`: "}]}
|
|
710
712
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.deadline_s Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.deadline_s`: "}]}
|
|
711
713
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.help`: "}]}
|
|
712
714
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.host_load Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.host_load`: "}]}
|
|
713
715
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.invalid_payload Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.invalid_payload`: "}]}
|
|
716
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.mutation_count Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.mutation_count`: "}]}
|
|
717
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.next_timeout Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.next_timeout`: "}]}
|
|
718
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.note_timeout! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.note_timeout!`: "}]}
|
|
719
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.payload_key Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.payload_key`: "}]}
|
|
720
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.payload_spent Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.payload_spent`: "}]}
|
|
714
721
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.placeholder? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.placeholder?`: "}]}
|
|
715
722
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.present? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.present?`: "}]}
|
|
723
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.reset_timeout_budget Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.reset_timeout_budget`: "}]}
|
|
724
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.reset_timeout_budget! Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.reset_timeout_budget!`: "}]}
|
|
716
725
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.shell_bash? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.shell_bash?`: "}]}
|
|
717
726
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.shell_name Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.shell_name`: "}]}
|
|
727
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.task_key Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.task_key`: "}]}
|
|
718
728
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.timeout_lesson Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.timeout_lesson`: "}]}
|
|
729
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.timeout_mutated Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.timeout_mutated`: "}]}
|
|
730
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.timeout_mutations Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.timeout_mutations`: "}]}
|
|
719
731
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.timeout_prior_count Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.timeout_prior_count`: "}]}
|
|
720
732
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.timeout_result Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.timeout_result`: "}]}
|
|
733
|
+
{"messages":[{"role":"user","content":"PWN::AI::Agent::ToolGuard.timeout_spent Usage"},{"role":"assistant","content":"`PWN::AI::Agent::ToolGuard.timeout_spent`: "}]}
|
|
721
734
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TransparentBrowser.analyze Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TransparentBrowser.analyze`: Supported Method Parameters\n\nai_analysis = PWN::AI::Agent::TransparentBrowser.analyze(\n\nrequest: 'required - current step in the JavaScript debugging session to analyze',\nsource_to_review: 'required - the block of JavaScript code in which the current step resides'\n\n)\n"}]}
|
|
722
735
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TransparentBrowser.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TransparentBrowser.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
723
736
|
{"messages":[{"role":"user","content":"PWN::AI::Agent::TransparentBrowser.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::TransparentBrowser.help`: "}]}
|
|
@@ -2140,11 +2153,13 @@
|
|
|
2140
2153
|
{"messages":[{"role":"user","content":"PWN::Plugins::NexposeVulnScan.logout Usage"},{"role":"assistant","content":"`PWN::Plugins::NexposeVulnScan.logout`: Supported Method Parameters\n\nPWN::Plugins::NexposeVulnScan.logout(\n\nnsc_obj: 'required nsc_obj returned from login method'\n\n)\n"}]}
|
|
2141
2154
|
{"messages":[{"role":"user","content":"PWN::Plugins::NexposeVulnScan.scan_site_by_name Usage"},{"role":"assistant","content":"`PWN::Plugins::NexposeVulnScan.scan_site_by_name`: Supported Method Parameters\n\nPWN::Plugins::NexposeVulnScan.scan_site_by_name(\n\nnsc_obj: 'required nsc_obj returned from login method',\nsite_name: 'required Nexpose site name to scan (case-sensitive),\npoll_interval: 'optional poll interval to check the completion status of the scan (defaults to 3 minutes)\n\n)\n"}]}
|
|
2142
2155
|
{"messages":[{"role":"user","content":"PWN::Plugins::NexposeVulnScan.update_site_assets Usage"},{"role":"assistant","content":"`PWN::Plugins::NexposeVulnScan.update_site_assets`: Supported Method Parameters\n\nPWN::Plugins::NexposeVulnScan.update_site_assets(\n\nnsc_obj: 'required nsc_obj returned from login method',\nsite_name: 'required Nexpose site name to update (case-sensitive),\nassets: 'required array of hashes containing called :ip => values being IP address (All IPs not included in the :assets parameter will be removed from Nexpose)'\n\n)\n"}]}
|
|
2156
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.apply_port_scan_opts Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.apply_port_scan_opts`: "}]}
|
|
2143
2157
|
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
2144
2158
|
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.diff_xml_results Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.diff_xml_results`: Supported Method Parameters\n\nPWN::Plugins::NmapIt.diff_xml_results(\n\nxml_a: 'required - path to nmap xml results',\nxml_b: 'required - path to nmap xml results',\ndiff: 'required - path to nmap xml results diff'\n\n)\n"}]}
|
|
2145
2159
|
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.help Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.help`: "}]}
|
|
2160
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.nmap_compat Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.nmap_compat`: "}]}
|
|
2146
2161
|
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.parse_xml_results Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.parse_xml_results`: Supported Method Parameters\n\nPWN::Plugins::NmapIt.parse_xml_results(:xml_file => ‘required - path to nmap xml results’) do |xml|\n\nputs xml.public_methods\nxml.each_host do |host|\n puts \"[#{host.ip}]\"\n\n host.scripts.each do |name,output|\n output.each_line { |line| puts \" #{line}\" }\n end\n\n host.each_port do |port|\n puts \" [#{port.number}/#{port.protocol}]\"\n\n port.scripts.each do |name,output|\n puts \" [#{name}]\"\n output.each_line { |line| puts \" #{line}\" }\n end\n end\nend\n\n\nend\n"}]}
|
|
2147
|
-
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.port_scan Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.port_scan`: Supported Method Parameters\n\nPWN::Plugins::NmapIt.port_scan do |nmap|\n\nputs nmap.public_methods\nnmap.connect_scan = true\nnmap.service_scan = true\nnmap.verbose = true\nnmap.ports = [1..1024,1337]\nnmap.targets = '127.0.0.1'\nnmap.xml = '/tmp/nmap_port_scan_res.xml'\n\n\nend\n"}]}
|
|
2162
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::NmapIt.port_scan Usage"},{"role":"assistant","content":"`PWN::Plugins::NmapIt.port_scan`: Supported Method Parameters\n\nPWN::Plugins::NmapIt.port_scan do |nmap|\n\nputs nmap.public_methods\nnmap.connect_scan = true\nnmap.service_scan = true\nnmap.verbose = true\nnmap.ports = [1..1024, 1337]\nnmap.targets = '127.0.0.1'\nnmap.xml = '/tmp/nmap_port_scan_res.xml' # alias of output_xml\n\n\nend\n\nPWN::Plugins::NmapIt.port_scan(\n\ntargets: '127.0.0.1',\nports: '1-65535',\nconnect_scan: true,\nservice_scan: true,\nscript: 'vuln,safe',\nxml: '/tmp/nmap_port_scan_res.xml'\n\n)\n"}]}
|
|
2148
2163
|
{"messages":[{"role":"user","content":"PWN::Plugins::OAuth2.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::OAuth2.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
2149
2164
|
{"messages":[{"role":"user","content":"PWN::Plugins::OAuth2.decode Usage"},{"role":"assistant","content":"`PWN::Plugins::OAuth2.decode`: Supported Method Parameters\n\nPWN::Plugins::OAuth2.decode(\n\noauth2_token: 'required oauth2 token'\n\n)\n"}]}
|
|
2150
2165
|
{"messages":[{"role":"user","content":"PWN::Plugins::OAuth2.get_value_by_key Usage"},{"role":"assistant","content":"`PWN::Plugins::OAuth2.get_value_by_key`: Supported Method Parameters\n\nPWN::Plugins::OAuth2.get_value_by_key(\n\noauth2_token: 'required oauth2 token',\nkey: 'required oauth2 token key name located within the Base64 encoded token as symbol, e.g. :company_id'\n\n)\n"}]}
|
|
@@ -2228,14 +2243,21 @@
|
|
|
2228
2243
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.enable_autocomplete Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.enable_autocomplete`: Supported Method Parameters\n\nPWN::Plugins::REPL.enable_autocomplete(\n\nenabled: 'optional - Boolean (default true). false reverts to single-line cycling.'\n\n)\n\nIRB-style suggest-as-you-type for the pwn REPL.\n\nReplaces Pry’s default input (rb-readline — single-candidate TAB cycling) with Reline and turns on Reline.autocompletion, which renders a live dropdown of candidates below the cursor as you type (the same widget IRB uses). Pry already wires Reline.completion_proc → @pry.complete (Pry::InputCompleter) when input == Reline, so the menu is fed by the full Ruby/PWN object graph: constants (PWN::Plugins::Nm<TAB>), instance methods, local/global variables, and Pry slash-commands.\n\nNavigate with ↑/↓ or TAB, accept with → or ENTER, dismiss with ESC.\n\nScope: this drives the MAIN pwn REPL (Ruby). pwn-ai swaps PWNMultiLineInput onto Reline.readmultiline and installs install_pwn_ai_completer! so TAB is command / path / Ruby menus. pwn-asm keeps opcode input without those menus.\n"}]}
|
|
2229
2244
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.help Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.help`: "}]}
|
|
2230
2245
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.install_pwn_ai_completer! Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.install_pwn_ai_completer!`: "}]}
|
|
2246
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.persist_ai_selection Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.persist_ai_selection`: "}]}
|
|
2231
2247
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_complete Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_complete`: Supported Method Parameters\n\nhits = PWN::Plugins::REPL.pwn_ai_complete(\n\ntarget: 'required - token Reline is completing',\nline: 'optional - full line buffer',\npry: 'optional - Pry instance for Ruby completion'\n\n)\n"}]}
|
|
2232
2248
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_complete_command Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_complete_command`: "}]}
|
|
2233
2249
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_complete_kind Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_complete_kind`: Supported Method Parameters\n\nkind = PWN::Plugins::REPL.pwn_ai_complete_kind(line: ‘optional - current input buffer’)\n\nfirst char is ‘/’ → :command (pwn-ai slash menu)\n\n‘/’ anywhere else → :path (host-native file nav)\n\nelse → :ruby (same Pry::InputCompleter menu as the pwn REPL)\n\n"}]}
|
|
2234
2250
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_complete_path Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_complete_path`: "}]}
|
|
2235
2251
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_complete_ruby Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_complete_ruby`: "}]}
|
|
2236
2252
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_dispatch_slash! Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_dispatch_slash!`: "}]}
|
|
2253
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_engine_model Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_engine_model`: "}]}
|
|
2254
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_engines Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_engines`: "}]}
|
|
2255
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_list_llms Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_list_llms`: "}]}
|
|
2256
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_model_ids Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_model_ids`: "}]}
|
|
2257
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_provider_class Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_provider_class`: "}]}
|
|
2237
2258
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_run_cron Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_run_cron`: "}]}
|
|
2238
2259
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_run_memory Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_run_memory`: "}]}
|
|
2260
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_run_model Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_run_model`: "}]}
|
|
2239
2261
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_run_sessions Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_run_sessions`: "}]}
|
|
2240
2262
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.pwn_ai_run_skills Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.pwn_ai_run_skills`: "}]}
|
|
2241
2263
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL.ready_tty! Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL.ready_tty!`: "}]}
|