pwn 0.5.739 → 0.5.741
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/Gemfile +2 -2
- data/documentation/Agent-Tool-Registry.md +163 -6
- data/documentation/Diagrams.md +1 -1
- data/documentation/Home.md +1 -1
- data/documentation/How-PWN-Works.md +1 -1
- data/documentation/Plugin-Degradation.md +6 -2
- data/documentation/Recon-Findings-API.md +33 -1
- data/documentation/What-is-PWN.md +1 -1
- data/documentation/pwn-ai-Agent.md +1 -1
- data/etc/default_skills/pwn/ai/agent/confirmation/SKILL.md +49 -0
- data/etc/default_skills/pwn/ai/agent/result/SKILL.md +4 -1
- data/etc/default_skills/pwn/ai/agent/task_dag/SKILL.md +50 -0
- data/etc/default_skills/pwn/ai/agent/tool_guard/SKILL.md +2 -0
- data/etc/default_skills/pwn/ai/agent/tools/browser_goto/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/nmap_scan/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/nuclei_scan/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/radare2/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/turn_finalizer/SKILL.md +3 -0
- data/etc/default_skills/pwn/ai/router/SKILL.md +51 -0
- data/etc/default_skills/pwn/engagement/SKILL.md +2 -0
- data/etc/default_skills/pwn/ffi/capstone/SKILL.md +54 -0
- data/etc/default_skills/pwn/ffi/keystone/SKILL.md +50 -0
- data/etc/default_skills/pwn/plugins/aflplusplus/SKILL.md +5 -1
- data/etc/default_skills/pwn/plugins/ai_sandbox/SKILL.md +52 -0
- data/etc/default_skills/pwn/plugins/artifact_registry/SKILL.md +3 -0
- data/etc/default_skills/pwn/plugins/assembly/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/binary_parser/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/exploit_dev/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/findings/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/gdbmi/SKILL.md +8 -2
- data/etc/default_skills/pwn/plugins/httpx/SKILL.md +49 -0
- data/etc/default_skills/pwn/plugins/jobs/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/method_catalog/SKILL.md +50 -0
- data/etc/default_skills/pwn/plugins/nmap_it/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/nuclei/SKILL.md +2 -1
- data/etc/default_skills/pwn/plugins/process_tube/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/radare2/SKILL.md +3 -0
- data/etc/default_skills/pwn/plugins/recon/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/rop/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/transparent_browser/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/vault/SKILL.md +3 -0
- data/etc/default_skills/pwn/reports/SKILL.md +2 -0
- data/lib/pwn/ai/agent/confirmation.rb +163 -0
- data/lib/pwn/ai/agent/dispatch.rb +43 -5
- data/lib/pwn/ai/agent/loop.rb +45 -11
- data/lib/pwn/ai/agent/prompt_builder.rb +7 -4
- data/lib/pwn/ai/agent/registry.rb +2 -1
- data/lib/pwn/ai/agent/result.rb +76 -6
- data/lib/pwn/ai/agent/task_dag.rb +220 -0
- data/lib/pwn/ai/agent/tool_guard.rb +166 -24
- data/lib/pwn/ai/agent/tools/artifacts.rb +35 -5
- data/lib/pwn/ai/agent/tools/binary_triage.rb +7 -3
- data/lib/pwn/ai/agent/tools/browser_goto.rb +32 -0
- data/lib/pwn/ai/agent/tools/debug_lane.rb +23 -0
- data/lib/pwn/ai/agent/tools/exploitdev.rb +32 -12
- data/lib/pwn/ai/agent/tools/finding_record.rb +31 -5
- data/lib/pwn/ai/agent/tools/fuzz_campaign.rb +44 -21
- data/lib/pwn/ai/agent/tools/job_run.rb +30 -18
- data/lib/pwn/ai/agent/tools/loot_query.rb +44 -0
- data/lib/pwn/ai/agent/tools/nmap_scan.rb +32 -0
- data/lib/pwn/ai/agent/tools/nuclei_scan.rb +31 -0
- data/lib/pwn/ai/agent/tools/radare2.rb +55 -0
- data/lib/pwn/ai/agent/tools/ruby_eval.rb +19 -2
- data/lib/pwn/ai/agent/tools/shell.rb +22 -10
- data/lib/pwn/ai/agent/turn_finalizer.rb +46 -5
- data/lib/pwn/ai/agent.rb +3 -1
- data/lib/pwn/ai/cli.rb +26 -2
- data/lib/pwn/ai/context.rb +4 -2
- data/lib/pwn/ai/router.rb +196 -0
- data/lib/pwn/ai/tools/README.md +5 -0
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/config.rb +10 -0
- data/lib/pwn/engagement.rb +45 -1
- data/lib/pwn/ffi/capstone.rb +120 -0
- data/lib/pwn/ffi/keystone.rb +107 -0
- data/lib/pwn/ffi.rb +2 -0
- data/lib/pwn/migrate.rb +5 -1
- data/lib/pwn/plugins/aflplusplus.rb +238 -13
- data/lib/pwn/plugins/ai_sandbox.rb +240 -0
- data/lib/pwn/plugins/artifact_registry.rb +188 -22
- data/lib/pwn/plugins/assembly.rb +73 -2
- data/lib/pwn/plugins/basic_auth.rb +17 -4
- data/lib/pwn/plugins/binary_parser.rb +439 -38
- data/lib/pwn/plugins/exploit_dev.rb +83 -14
- data/lib/pwn/plugins/findings.rb +208 -42
- data/lib/pwn/plugins/gdbmi.rb +265 -38
- data/lib/pwn/plugins/httpx.rb +93 -0
- data/lib/pwn/plugins/jobs.rb +260 -102
- data/lib/pwn/plugins/method_catalog.rb +328 -0
- data/lib/pwn/plugins/nmap_it.rb +157 -26
- data/lib/pwn/plugins/nuclei.rb +122 -29
- data/lib/pwn/plugins/preflight_checker.rb +3 -2
- data/lib/pwn/plugins/process_tube.rb +51 -2
- data/lib/pwn/plugins/radare2.rb +120 -26
- data/lib/pwn/plugins/recon.rb +33 -0
- data/lib/pwn/plugins/repl/mesh.rb +203 -13
- data/lib/pwn/plugins/rop.rb +121 -31
- data/lib/pwn/plugins/transparent_browser.rb +212 -14
- data/lib/pwn/plugins/vault.rb +182 -24
- data/lib/pwn/plugins.rb +4 -0
- data/lib/pwn/reports/engagement.rb +8 -2
- data/lib/pwn/reports/html.rb +28 -9
- data/lib/pwn/reports/json.rb +2 -1
- data/lib/pwn/reports/markdown.rb +49 -12
- data/lib/pwn/reports/sarif.rb +17 -3
- data/lib/pwn/reports.rb +184 -34
- data/lib/pwn/version.rb +1 -1
- data/spec/integration/reports_spec.rb +93 -0
- data/spec/integration/result_condition_spec.rb +11 -8
- data/spec/lib/pwn/ai/agent/confirmation_spec.rb +59 -0
- data/spec/lib/pwn/ai/agent/dispatch_confirmation_spec.rb +52 -0
- data/spec/lib/pwn/ai/agent/dispatch_policy_spec.rb +51 -0
- data/spec/lib/pwn/ai/agent/loop_spec.rb +86 -0
- data/spec/lib/pwn/ai/agent/registry_spec.rb +1 -1
- data/spec/lib/pwn/ai/agent/result_spec.rb +84 -1
- data/spec/lib/pwn/ai/agent/task_dag_spec.rb +63 -0
- data/spec/lib/pwn/ai/agent/tool_guard_spec.rb +48 -0
- data/spec/lib/pwn/ai/agent/tools/ai_sandbox_spec.rb +36 -0
- data/spec/lib/pwn/ai/agent/tools/binary_triage_spec.rb +21 -0
- data/spec/lib/pwn/ai/agent/tools/browser_goto_spec.rb +57 -0
- data/spec/lib/pwn/ai/agent/tools/exploitdev_spec.rb +42 -0
- data/spec/lib/pwn/ai/agent/tools/finding_record_spec.rb +46 -2
- data/spec/lib/pwn/ai/agent/tools/fuzz_campaign_spec.rb +46 -0
- data/spec/lib/pwn/ai/agent/tools/job_run_spec.rb +52 -0
- data/spec/lib/pwn/ai/agent/tools/loot_query_spec.rb +46 -0
- data/spec/lib/pwn/ai/agent/tools/nmap_scan_spec.rb +26 -0
- data/spec/lib/pwn/ai/agent/tools/nuclei_scan_spec.rb +37 -0
- data/spec/lib/pwn/ai/agent/tools/pwn_eval_schema_spec.rb +27 -0
- data/spec/lib/pwn/ai/agent/tools/radare2_spec.rb +15 -0
- data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +76 -0
- data/spec/lib/pwn/ai/cli_spec.rb +12 -1
- data/spec/lib/pwn/ai/router_spec.rb +57 -0
- data/spec/lib/pwn/ffi/capstone_spec.rb +33 -0
- data/spec/lib/pwn/ffi/keystone_spec.rb +33 -0
- data/spec/lib/pwn/migrate_spec.rb +30 -2
- data/spec/lib/pwn/plugins/aflplusplus_spec.rb +40 -0
- data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +44 -0
- data/spec/lib/pwn/plugins/artifact_registry_spec.rb +116 -1
- data/spec/lib/pwn/plugins/assembly_spec.rb +12 -0
- data/spec/lib/pwn/plugins/basic_auth_spec.rb +15 -0
- data/spec/lib/pwn/plugins/binary_parser_spec.rb +59 -2
- data/spec/lib/pwn/plugins/exploit_dev_spec.rb +44 -0
- data/spec/lib/pwn/plugins/findings_structured_spec.rb +196 -1
- data/spec/lib/pwn/plugins/gdbmi_spec.rb +99 -1
- data/spec/lib/pwn/plugins/httpx_spec.rb +23 -0
- data/spec/lib/pwn/plugins/jobs_spec.rb +141 -0
- data/spec/lib/pwn/plugins/method_catalog_spec.rb +29 -0
- data/spec/lib/pwn/plugins/nmap_it_spec.rb +49 -0
- data/spec/lib/pwn/plugins/nuclei_spec.rb +33 -1
- data/spec/lib/pwn/plugins/radare2_spec.rb +115 -0
- data/spec/lib/pwn/plugins/recon_spec.rb +15 -0
- data/spec/lib/pwn/plugins/repl_mesh_channel_route_spec.rb +107 -1
- data/spec/lib/pwn/plugins/repl_mesh_history_spec.rb +122 -0
- data/spec/lib/pwn/plugins/rop_spec.rb +19 -0
- data/spec/lib/pwn/plugins/transparent_browser_spec.rb +71 -0
- data/spec/lib/pwn/plugins/vault_spec.rb +41 -0
- data/spec/lib/pwn/reports/engagement_spec.rb +51 -0
- data/spec/lib/pwn/reports_spec.rb +196 -0
- data/spec/spec_helper.rb +3 -0
- data/third_party/pwn_rdoc.jsonl +230 -4
- metadata +47 -5
|
@@ -7,37 +7,57 @@ PWN::AI::Agent::Registry.register(
|
|
|
7
7
|
toolset: 'pwn',
|
|
8
8
|
schema: {
|
|
9
9
|
name: 'exploitdev',
|
|
10
|
-
description: 'Exploit-dev
|
|
10
|
+
description: 'Exploit-dev: cyclic pattern generate/offset, ROP gadgets, assemble/disassemble, ELF GOT/PLT resolve, process/remote IO, ret2libc chain.',
|
|
11
11
|
parameters: {
|
|
12
12
|
type: 'object',
|
|
13
13
|
properties: {
|
|
14
|
-
action: { type: 'string', description: 'pattern_create|pattern_offset|rop_gadgets|p32|p64|checksec' },
|
|
14
|
+
action: { type: 'string', description: 'pattern_create|pattern_offset|rop_gadgets|assemble|disassemble|elf_resolve|p32|p64|io|ret2libc|checksec' },
|
|
15
15
|
n: { type: 'integer' },
|
|
16
16
|
length: { type: 'integer' },
|
|
17
17
|
value: { type: 'string' },
|
|
18
18
|
path: { type: 'string' },
|
|
19
|
-
filter: { type: 'string' }
|
|
19
|
+
filter: { type: 'string' },
|
|
20
|
+
asm: { type: 'string' },
|
|
21
|
+
bytes: { type: 'string' },
|
|
22
|
+
arch: { type: 'string' },
|
|
23
|
+
offset: { type: 'integer' },
|
|
24
|
+
extra: { type: 'string' },
|
|
25
|
+
cmd: { type: 'string' },
|
|
26
|
+
host: { type: 'string' },
|
|
27
|
+
port: { type: 'integer' },
|
|
28
|
+
kind: { type: 'string' }
|
|
20
29
|
},
|
|
21
30
|
required: %w[action]
|
|
22
31
|
}
|
|
23
32
|
},
|
|
24
33
|
handler: lambda { |args|
|
|
25
|
-
|
|
34
|
+
args = args.transform_keys(&:to_sym)
|
|
35
|
+
act = args[:action].to_s
|
|
26
36
|
case act
|
|
27
37
|
when 'pattern_create'
|
|
28
|
-
PWN::Plugins::ExploitDev.cyclic(length: args[:n] || args[:length] ||
|
|
38
|
+
PWN::Plugins::ExploitDev.cyclic(length: args[:n] || args[:length] || 200)
|
|
29
39
|
when 'pattern_offset'
|
|
30
|
-
PWN::Plugins::ExploitDev.cyclic_find(value: args[:value]
|
|
40
|
+
PWN::Plugins::ExploitDev.cyclic_find(value: args[:value], length: args[:length] || 400)
|
|
31
41
|
when 'rop_gadgets'
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
PWN::Plugins::ExploitDev.gadgets(path: args[:path], filter: args[:filter], backend: args[:backend] || 'scan')
|
|
43
|
+
when 'assemble'
|
|
44
|
+
PWN::Plugins::Assembly.assemble(asm: args[:asm] || args[:value], arch: args[:arch] || 'x86_64')
|
|
45
|
+
when 'disassemble'
|
|
46
|
+
raw = args[:bytes] || args[:value]
|
|
47
|
+
raw = [raw].pack('H*') if raw.to_s.match?(/\A[0-9a-fA-F]+\z/)
|
|
48
|
+
PWN::Plugins::Assembly.disassemble(bytes: raw, arch: args[:arch] || 'x86_64')
|
|
49
|
+
when 'elf_resolve'
|
|
50
|
+
PWN::Plugins::BinaryParser.elf_resolve(path: args[:path])
|
|
35
51
|
when 'p32'
|
|
36
|
-
PWN::Plugins::ExploitDev.p32(value: args[:value]
|
|
52
|
+
PWN::Plugins::ExploitDev.p32(value: args[:value])
|
|
37
53
|
when 'p64'
|
|
38
|
-
PWN::Plugins::ExploitDev.p64(value: args[:value]
|
|
54
|
+
PWN::Plugins::ExploitDev.p64(value: args[:value])
|
|
55
|
+
when 'io'
|
|
56
|
+
PWN::Plugins::ExploitDev.io(kind: args[:kind] || 'process', cmd: args[:cmd] || args[:path], host: args[:host], port: args[:port])
|
|
57
|
+
when 'ret2libc'
|
|
58
|
+
PWN::Plugins::ExploitDev.ret2libc(path: args[:path], offset: args[:offset], extra: args[:extra])
|
|
39
59
|
when 'checksec'
|
|
40
|
-
PWN::Plugins::GDB.mitigations(binary: args[:path]
|
|
60
|
+
PWN::Plugins::GDB.mitigations(binary: args[:path])
|
|
41
61
|
else
|
|
42
62
|
{ error: "unknown action #{act}" }
|
|
43
63
|
end
|
|
@@ -7,7 +7,7 @@ PWN::AI::Agent::Registry.register(
|
|
|
7
7
|
toolset: 'pwn',
|
|
8
8
|
schema: {
|
|
9
9
|
name: 'finding_record',
|
|
10
|
-
description: 'Record a
|
|
10
|
+
description: 'Record a finding with CVSS 3.x, severity justification, ordered reproduction steps, and evidence paths or artifact handles (pcap, screenshot, crash, PoC). Export embeds hash-verified evidence. Hashes are not execution proof.',
|
|
11
11
|
parameters: {
|
|
12
12
|
type: 'object',
|
|
13
13
|
properties: {
|
|
@@ -17,7 +17,21 @@ PWN::AI::Agent::Registry.register(
|
|
|
17
17
|
cvss_score: { type: 'number', minimum: 0, maximum: 10 },
|
|
18
18
|
affected_asset: { type: 'string' },
|
|
19
19
|
evidence_paths: { type: 'array', minItems: 1, items: { type: 'string' } },
|
|
20
|
+
artifact_handles: {
|
|
21
|
+
type: 'array', minItems: 1,
|
|
22
|
+
items: {
|
|
23
|
+
anyOf: [
|
|
24
|
+
{ type: 'string', minLength: 1 },
|
|
25
|
+
{ type: 'object', required: %w[handle], additionalProperties: false,
|
|
26
|
+
properties: { handle: { type: 'string', minLength: 1 }, kind: { type: 'string', enum: %w[pcap screenshot crash poc evidence] },
|
|
27
|
+
label: { type: 'string', minLength: 1 }, sha256: { type: 'string', pattern: '^[0-9a-f]{64}$' } } }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
severity_justification: { type: 'string', minLength: 1, description: 'Explain demonstrated impact and why it supports the chosen CVSS metrics; do not claim untested escalation.' },
|
|
32
|
+
reproduction_steps: { type: 'array', minItems: 1, items: { type: 'string', minLength: 1 }, description: 'Ordered setup, PoC commands and expected observations for independent reproduction.' },
|
|
20
33
|
attack_chain_refs: { type: 'array', items: { type: 'string' } },
|
|
34
|
+
enables: { type: 'array', uniqueItems: true, items: { type: 'string', minLength: 1 }, description: 'Outgoing finding IDs this finding enables, within the same engagement. Use op=link to update an existing finding.' },
|
|
21
35
|
remediation: { type: 'string' },
|
|
22
36
|
confidence: { type: 'number', minimum: 0, maximum: 1 },
|
|
23
37
|
dir_path: { type: 'string' },
|
|
@@ -28,7 +42,8 @@ PWN::AI::Agent::Registry.register(
|
|
|
28
42
|
poc: { type: 'string' },
|
|
29
43
|
poc_artifacts: { type: 'array', items: { type: 'string' } },
|
|
30
44
|
session_id: { type: 'string' },
|
|
31
|
-
op: { type: 'string', enum: %w[query export chain record verify retest chain_impact gaps] },
|
|
45
|
+
op: { type: 'string', enum: %w[query export chain record link verify retest chain_impact gaps] },
|
|
46
|
+
id: { type: 'string', minLength: 1 },
|
|
32
47
|
parent_id: { type: 'string' },
|
|
33
48
|
engagement_id: { type: 'string' },
|
|
34
49
|
kind: { type: 'string', enum: %w[http script] },
|
|
@@ -36,21 +51,30 @@ PWN::AI::Agent::Registry.register(
|
|
|
36
51
|
request_path: { type: 'string' },
|
|
37
52
|
response_path: { type: 'string' },
|
|
38
53
|
execution_log: { type: 'string' },
|
|
39
|
-
ids: { type: 'array', items: { type: 'string' } },
|
|
54
|
+
ids: { type: 'array', uniqueItems: true, items: { type: 'string' }, description: 'Ordered source-to-impact finding IDs for chain_impact.' },
|
|
40
55
|
combined_impact_path: { type: 'string' },
|
|
41
56
|
escalate: { type: 'boolean' },
|
|
42
57
|
combined_severity: { type: 'string' }
|
|
43
58
|
},
|
|
44
59
|
required: %w[],
|
|
45
60
|
anyOf: [
|
|
46
|
-
{ properties: { op: { enum: %w[query export] } }, required: %w[op] },
|
|
47
|
-
{
|
|
61
|
+
{ properties: { op: { enum: %w[query export gaps] } }, required: %w[op] },
|
|
62
|
+
{ properties: { op: { enum: %w[verify retest] } }, required: %w[op id kind impact],
|
|
63
|
+
anyOf: [{ required: %w[request_path response_path] }, { required: %w[execution_log] }] },
|
|
64
|
+
{ properties: { op: { enum: %w[chain_impact] } }, required: %w[op ids combined_impact_path] },
|
|
65
|
+
{ properties: { op: { enum: %w[link] } }, required: %w[op id enables] },
|
|
66
|
+
{ properties: { op: { enum: %w[record chain] } }, required: %w[title cwe cvss_vector cvss_score affected_asset poc attack_chain_refs remediation confidence severity_justification reproduction_steps],
|
|
67
|
+
anyOf: [{ required: %w[evidence_paths] }, { required: %w[artifact_handles] }] }
|
|
48
68
|
]
|
|
49
69
|
}
|
|
50
70
|
},
|
|
51
71
|
handler: lambda { |args|
|
|
52
72
|
args = args.transform_keys(&:to_sym)
|
|
53
73
|
op = (args[:op] || 'record').to_s
|
|
74
|
+
if %w[record chain].include?(op)
|
|
75
|
+
raise ArgumentError, 'severity_justification must be a nonempty string' unless args[:severity_justification].is_a?(String) && !args[:severity_justification].strip.empty?
|
|
76
|
+
raise ArgumentError, 'reproduction_steps are required' unless args.key?(:reproduction_steps) && !args[:reproduction_steps].nil?
|
|
77
|
+
end
|
|
54
78
|
case op
|
|
55
79
|
when 'query'
|
|
56
80
|
PWN::Plugins::Findings.query(args)
|
|
@@ -67,6 +91,8 @@ PWN::AI::Agent::Registry.register(
|
|
|
67
91
|
PWN::Plugins::Findings.retest(args)
|
|
68
92
|
when 'chain_impact'
|
|
69
93
|
PWN::Plugins::Findings.chain_impact(args)
|
|
94
|
+
when 'link'
|
|
95
|
+
PWN::Plugins::Findings.link(args)
|
|
70
96
|
when 'gaps'
|
|
71
97
|
PWN::Plugins::Findings.issue_work_gaps(args)
|
|
72
98
|
when 'record'
|
|
@@ -1,54 +1,77 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'pwn/ai/agent/registry'
|
|
4
|
+
require 'shellwords'
|
|
4
5
|
|
|
5
6
|
PWN::AI::Agent::Registry.register(
|
|
6
7
|
name: 'fuzz_campaign',
|
|
7
8
|
toolset: 'pwn',
|
|
8
9
|
schema: {
|
|
9
10
|
name: 'fuzz_campaign',
|
|
10
|
-
description: '
|
|
11
|
+
description: 'AFL++/libFuzzer campaign: corpus, dictionary from binary strings, durable Jobs, crash dedup by backtrace hash, auto PWN-RE-003 triage (GDBMI + binary_triage + exploitdev). Poll job_status; no manual glue.',
|
|
11
12
|
parameters: {
|
|
12
13
|
type: 'object',
|
|
13
14
|
properties: {
|
|
14
15
|
action: { type: 'string', description: 'start|status|stop|triage|minimize' },
|
|
16
|
+
engine: { type: 'string', description: 'aflplusplus (default) or libfuzzer' },
|
|
15
17
|
in_dir: { type: 'string' },
|
|
16
18
|
out_dir: { type: 'string' },
|
|
17
19
|
target: { type: 'string' },
|
|
18
|
-
|
|
20
|
+
max_runtime: { type: 'integer', minimum: 0 },
|
|
21
|
+
session_id: { type: 'string' },
|
|
22
|
+
cwd: { type: 'string' },
|
|
23
|
+
env: { type: 'object', additionalProperties: { type: 'string' } },
|
|
24
|
+
idempotency_key: { type: 'string' },
|
|
25
|
+
id: { type: 'string' },
|
|
26
|
+
crash: { type: 'string' },
|
|
27
|
+
out: { type: 'string' }
|
|
19
28
|
},
|
|
20
29
|
required: %w[action]
|
|
21
30
|
}
|
|
22
31
|
},
|
|
23
32
|
handler: lambda { |args|
|
|
24
|
-
|
|
33
|
+
args = args.transform_keys(&:to_sym)
|
|
34
|
+
act = args[:action].to_s
|
|
25
35
|
case act
|
|
26
36
|
when 'start'
|
|
27
|
-
in_dir = args[:in_dir]
|
|
28
|
-
out_dir = args[:out_dir]
|
|
29
|
-
target = args[:target]
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
in_dir = args[:in_dir]
|
|
38
|
+
out_dir = args[:out_dir]
|
|
39
|
+
target = args[:target]
|
|
40
|
+
raise ArgumentError, 'in_dir, out_dir and target are required' if [in_dir, out_dir, target].any? { |value| value.to_s.empty? }
|
|
41
|
+
|
|
42
|
+
env = args[:env]
|
|
43
|
+
cmd = PWN::Plugins::AFLplusplus.campaign_command(
|
|
44
|
+
engine: args[:engine] || 'aflplusplus',
|
|
45
|
+
in_dir: in_dir,
|
|
46
|
+
out_dir: out_dir,
|
|
47
|
+
target: target,
|
|
48
|
+
max_runtime: args[:max_runtime],
|
|
49
|
+
cwd: args[:cwd],
|
|
50
|
+
env: env&.transform_keys(&:to_s)
|
|
51
|
+
)
|
|
52
|
+
PWN::Plugins::Jobs.start(
|
|
53
|
+
command: cmd,
|
|
54
|
+
max_runtime: args[:max_runtime],
|
|
55
|
+
session_id: args[:session_id] || Thread.current[:pwn_session_id],
|
|
56
|
+
cwd: args[:cwd],
|
|
57
|
+
env: env&.transform_keys(&:to_s),
|
|
58
|
+
idempotency_key: args[:idempotency_key]
|
|
59
|
+
)
|
|
32
60
|
when 'status'
|
|
33
|
-
row = PWN::Plugins::Jobs.status(id: args[:id]
|
|
34
|
-
out_dir = args[:out_dir]
|
|
61
|
+
row = PWN::Plugins::Jobs.status(id: args[:id])
|
|
62
|
+
out_dir = args[:out_dir]
|
|
35
63
|
row = row.merge(PWN::Plugins::AFLplusplus.parse_stats(out_dir: out_dir)) if out_dir
|
|
36
|
-
if out_dir
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
row = row.merge(triage: triage) if triage
|
|
40
|
-
rescue StandardError
|
|
41
|
-
nil
|
|
42
|
-
end
|
|
64
|
+
if out_dir
|
|
65
|
+
triage = PWN::Plugins::AFLplusplus.crash_triage(out_dir: out_dir, target: args[:target], handoff: true)
|
|
66
|
+
row = row.merge(triage: triage)
|
|
43
67
|
end
|
|
44
68
|
row
|
|
45
69
|
when 'stop'
|
|
46
|
-
PWN::Plugins::Jobs.stop(id: args[:id]
|
|
70
|
+
PWN::Plugins::Jobs.stop(id: args[:id])
|
|
47
71
|
when 'triage'
|
|
48
|
-
out_dir
|
|
49
|
-
PWN::Plugins::Fuzz.triage(dir: File.join(out_dir.to_s, 'crashes'), record: true, target: args[:target] || args['target'])
|
|
72
|
+
PWN::Plugins::AFLplusplus.crash_triage(out_dir: args[:out_dir], target: args[:target], handoff: true)
|
|
50
73
|
when 'minimize'
|
|
51
|
-
PWN::Plugins::AFLplusplus.minimize(crash: args[:crash]
|
|
74
|
+
PWN::Plugins::AFLplusplus.minimize(crash: args[:crash], out: args[:out], target: args[:target])
|
|
52
75
|
else
|
|
53
76
|
{ error: "unknown action #{act}" }
|
|
54
77
|
end
|
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'pwn/ai/agent/registry'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
require 'rbconfig'
|
|
4
6
|
|
|
5
7
|
PWN::AI::Agent::Registry.register(
|
|
6
8
|
name: 'job_run',
|
|
7
9
|
toolset: 'terminal',
|
|
8
10
|
schema: {
|
|
9
11
|
name: 'job_run',
|
|
10
|
-
description: 'Start
|
|
12
|
+
description: 'Start durable detached work: fuzzers, full scans, Ghidra analysis or a job graph. Returns a job id immediately; survives agent/session exit. Poll job_status and incrementally read job_tail using next_offset. max_runtime is the supervisor lifetime, NOT a blocking tool timeout; 21600 allows six hours. Reuse idempotency_key on launch retries, never relaunch to poll.',
|
|
11
13
|
parameters: {
|
|
12
14
|
type: 'object',
|
|
13
15
|
properties: {
|
|
14
16
|
command: { type: 'string' },
|
|
15
17
|
cmd: { type: 'string' },
|
|
16
18
|
tag: { type: 'string' },
|
|
19
|
+
session_id: { type: 'string' },
|
|
20
|
+
max_runtime: { type: 'integer', minimum: 0, description: 'Independent wall runtime in seconds; 0 means no runtime limit.' },
|
|
21
|
+
cwd: { type: 'string' },
|
|
22
|
+
env: { type: 'object', additionalProperties: { type: 'string' } },
|
|
23
|
+
idempotency_key: { type: 'string', description: 'Stable launch key reused across retries/sessions to recover the same job.' },
|
|
17
24
|
jobs: { type: 'array' },
|
|
18
25
|
artifact_dir: { type: 'string' }
|
|
19
26
|
},
|
|
20
|
-
required: %w[]
|
|
27
|
+
anyOf: [{ required: %w[command] }, { required: %w[cmd] }, { required: %w[jobs] }]
|
|
21
28
|
}
|
|
22
29
|
},
|
|
23
30
|
handler: lambda { |args|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
args = args.transform_keys(&:to_sym)
|
|
32
|
+
command = args[:command] || args[:cmd]
|
|
33
|
+
if args[:jobs]
|
|
34
|
+
payload = JSON.generate(jobs: args[:jobs], artifact_dir: args[:artifact_dir])
|
|
35
|
+
script = 'r=PWN::Plugins::Jobs.graph(JSON.parse(ARGV.fetch(0), symbolize_names:true)); puts JSON.generate(r); exit(r[:ok] ? 0 : 1)'
|
|
36
|
+
command = Shellwords.join([RbConfig.ruby, '-I', File.expand_path('../../../..', __dir__), '-rpwn', '-rjson', '-e', script, payload])
|
|
28
37
|
end
|
|
38
|
+
PWN::Plugins::Jobs.start(command: command, session_id: args[:session_id] || args[:tag] || Thread.current[:pwn_session_id], max_runtime: args[:max_runtime], cwd: args[:cwd], env: args[:env]&.transform_keys(&:to_s), idempotency_key: args[:idempotency_key])
|
|
29
39
|
}
|
|
30
40
|
)
|
|
31
41
|
PWN::AI::Agent::Registry.register(
|
|
@@ -33,15 +43,15 @@ PWN::AI::Agent::Registry.register(
|
|
|
33
43
|
toolset: 'terminal',
|
|
34
44
|
schema: {
|
|
35
45
|
name: 'job_status',
|
|
36
|
-
description: 'Poll
|
|
46
|
+
description: 'Poll durable job status by id, including exit/signal. Omit id to rediscover recent jobs from prior sessions. RUNNING is not completion; poll again instead of rerunning job_run.',
|
|
37
47
|
parameters: {
|
|
38
48
|
type: 'object',
|
|
39
|
-
properties: { id: { type: 'string' } }
|
|
40
|
-
required: %w[id]
|
|
49
|
+
properties: { id: { type: 'string' } }
|
|
41
50
|
}
|
|
42
51
|
},
|
|
43
52
|
handler: lambda { |args|
|
|
44
|
-
|
|
53
|
+
id = args[:id] || args['id']
|
|
54
|
+
id ? PWN::Plugins::Jobs.status(id: id) : PWN::Plugins::Jobs.list(limit: 20)
|
|
45
55
|
}
|
|
46
56
|
)
|
|
47
57
|
PWN::AI::Agent::Registry.register(
|
|
@@ -49,7 +59,7 @@ PWN::AI::Agent::Registry.register(
|
|
|
49
59
|
toolset: 'terminal',
|
|
50
60
|
schema: {
|
|
51
61
|
name: 'job_result',
|
|
52
|
-
description: 'Fetch
|
|
62
|
+
description: 'Fetch durable status and a bounded log tail for a job id. Use job_tail for incremental byte-cursor reads.',
|
|
53
63
|
parameters: {
|
|
54
64
|
type: 'object',
|
|
55
65
|
properties: { id: { type: 'string' }, lines: { type: 'integer' } },
|
|
@@ -65,15 +75,17 @@ PWN::AI::Agent::Registry.register(
|
|
|
65
75
|
toolset: 'terminal',
|
|
66
76
|
schema: {
|
|
67
77
|
name: 'job_tail',
|
|
68
|
-
description: '
|
|
78
|
+
description: 'Read a bounded incremental job log page. Start offset=0, then use next_offset. eof means current log end, not job completion: also poll job_status. Preserve encoding when decoding binary logs.',
|
|
69
79
|
parameters: {
|
|
70
80
|
type: 'object',
|
|
71
|
-
properties: { id: { type: 'string' }, handle: { type: 'string' }, lines: { type: 'integer' } },
|
|
72
|
-
required: %w[id]
|
|
81
|
+
properties: { id: { type: 'string' }, handle: { type: 'string' }, offset: { type: 'integer', minimum: 0 }, length: { type: 'integer', minimum: 1 }, lines: { type: 'integer' } },
|
|
82
|
+
anyOf: [{ required: %w[id] }, { required: %w[handle] }]
|
|
73
83
|
}
|
|
74
84
|
},
|
|
75
85
|
handler: lambda { |args|
|
|
76
|
-
|
|
86
|
+
args = args.transform_keys(&:to_sym)
|
|
87
|
+
length = (args[:length] || PWN::AI::Agent::Result.page_length).to_i.clamp(1, PWN::AI::Agent::Result.page_length)
|
|
88
|
+
PWN::Plugins::Jobs.tail(id: args[:id] || args[:handle], offset: args[:offset] || 0, length: length)
|
|
77
89
|
}
|
|
78
90
|
)
|
|
79
91
|
PWN::AI::Agent::Registry.register(
|
|
@@ -81,14 +93,14 @@ PWN::AI::Agent::Registry.register(
|
|
|
81
93
|
toolset: 'terminal',
|
|
82
94
|
schema: {
|
|
83
95
|
name: 'job_kill',
|
|
84
|
-
description: '
|
|
96
|
+
description: 'Request supervised cancellation of a job and its process group. Poll job_status for terminal STOPPED state.',
|
|
85
97
|
parameters: {
|
|
86
98
|
type: 'object',
|
|
87
99
|
properties: { id: { type: 'string' }, handle: { type: 'string' } },
|
|
88
|
-
required: %w[id]
|
|
100
|
+
anyOf: [{ required: %w[id] }, { required: %w[handle] }]
|
|
89
101
|
}
|
|
90
102
|
},
|
|
91
103
|
handler: lambda { |args|
|
|
92
|
-
PWN::Plugins::Jobs.job_kill(id: args[:id] || args[:handle] || args['id'])
|
|
104
|
+
PWN::Plugins::Jobs.job_kill(id: args[:id] || args[:handle] || args['id'] || args['handle'])
|
|
93
105
|
}
|
|
94
106
|
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
|
|
5
|
+
PWN::AI::Agent::Registry.register(
|
|
6
|
+
name: 'loot_query',
|
|
7
|
+
toolset: 'pwn',
|
|
8
|
+
schema: {
|
|
9
|
+
name: 'loot_query',
|
|
10
|
+
description: 'Query the engagement-scoped encrypted loot store (creds/tokens/keys with provenance). Use for lateral-movement planning; BasicAuth.encode(host:) auto-offers matching secrets when a service prompts for auth.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
host: { type: 'string' },
|
|
15
|
+
service: { type: 'string' },
|
|
16
|
+
kind: { type: 'string' },
|
|
17
|
+
engagement: { type: 'string' },
|
|
18
|
+
engagement_id: { type: 'string' },
|
|
19
|
+
finding_id: { type: 'string' },
|
|
20
|
+
op: { type: 'string', enum: %w[query offer ingest store] },
|
|
21
|
+
text: { type: 'string' },
|
|
22
|
+
secret: { type: 'string' },
|
|
23
|
+
username: { type: 'string' },
|
|
24
|
+
where: { type: 'string' },
|
|
25
|
+
source: { type: 'string' },
|
|
26
|
+
label: { type: 'string' }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
handler: lambda { |args|
|
|
31
|
+
args = args.transform_keys(&:to_sym)
|
|
32
|
+
op = (args[:op] || 'query').to_s
|
|
33
|
+
case op
|
|
34
|
+
when 'offer'
|
|
35
|
+
PWN::Plugins::Vault.offer(args)
|
|
36
|
+
when 'ingest'
|
|
37
|
+
PWN::Plugins::Vault.ingest(args.merge(source: args[:source] || 'recon'))
|
|
38
|
+
when 'store'
|
|
39
|
+
PWN::Plugins::Vault.store(args)
|
|
40
|
+
else
|
|
41
|
+
{ records: PWN::Plugins::Vault.query(args) }
|
|
42
|
+
end
|
|
43
|
+
}
|
|
44
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
|
|
5
|
+
PWN::AI::Agent::Registry.register(
|
|
6
|
+
name: 'nmap_scan',
|
|
7
|
+
toolset: 'pwn',
|
|
8
|
+
schema: {
|
|
9
|
+
name: 'nmap_scan',
|
|
10
|
+
description: 'Ingest nmap XML into the engagement store (hosts, services, scripts) and return diff vs last scan. since=yesterday answers what changed since yesterday in one call.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
xml: { type: 'string', description: 'Path to nmap XML (ingest).' },
|
|
15
|
+
xml_file: { type: 'string' },
|
|
16
|
+
since: { type: 'string', description: 'yesterday | last-scan | ISO8601. Omit xml to only query the diff.' },
|
|
17
|
+
engagement: { type: 'string' },
|
|
18
|
+
override: { type: 'boolean' },
|
|
19
|
+
at: { type: 'string' }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
handler: lambda { |args|
|
|
24
|
+
args = args.transform_keys(&:to_sym)
|
|
25
|
+
xml = args[:xml] || args[:xml_file]
|
|
26
|
+
if xml.to_s.empty?
|
|
27
|
+
PWN::Plugins::NmapIt.changes(since: args[:since] || 'yesterday', engagement: args[:engagement])
|
|
28
|
+
else
|
|
29
|
+
PWN::Plugins::NmapIt.scan(xml: xml, engagement: args[:engagement], override: args[:override], at: args[:at])
|
|
30
|
+
end
|
|
31
|
+
}
|
|
32
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
|
|
5
|
+
PWN::AI::Agent::Registry.register(
|
|
6
|
+
name: 'nuclei_scan',
|
|
7
|
+
toolset: 'pwn',
|
|
8
|
+
schema: {
|
|
9
|
+
name: 'nuclei_scan',
|
|
10
|
+
description: 'Run or ingest nuclei JSONL (optionally after httpx tech-detect). Records web findings with matched-at URL, template id, and severity into the findings store.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
jsonl: { type: 'string', description: 'nuclei JSONL path or raw JSONL (skips the nuclei binary).' },
|
|
15
|
+
target: { type: 'string' },
|
|
16
|
+
url: { type: 'string' },
|
|
17
|
+
httpx_jsonl: { type: 'string', description: 'httpx JSONL used to select nuclei tags by tech stack.' },
|
|
18
|
+
techs: { type: 'array', items: { type: 'string' } },
|
|
19
|
+
record: { type: 'boolean' },
|
|
20
|
+
templates: { type: 'string' },
|
|
21
|
+
severity: { type: 'string' }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
handler: lambda { |args|
|
|
26
|
+
args = args.transform_keys(&:to_sym)
|
|
27
|
+
techs = Array(args[:techs])
|
|
28
|
+
techs = PWN::Plugins::Httpx.probe(jsonl: args[:httpx_jsonl])[:techs] if techs.empty? && !args[:httpx_jsonl].to_s.empty?
|
|
29
|
+
PWN::Plugins::Nuclei.scan(args.merge(techs: techs, record: args.fetch(:record, true)))
|
|
30
|
+
}
|
|
31
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
|
|
5
|
+
PWN::AI::Agent::Registry.register(
|
|
6
|
+
name: 'r2_functions',
|
|
7
|
+
toolset: 'pwn',
|
|
8
|
+
schema: {
|
|
9
|
+
name: 'r2_functions',
|
|
10
|
+
description: 'Open a binary in r2/rizin (r2pipe) and return parsed aflj function JSON. Pair with r2_disasm for main.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
bin: { type: 'string', description: 'Filesystem path of the binary.' },
|
|
15
|
+
path: { type: 'string', description: 'Alias for bin.' },
|
|
16
|
+
session: { type: 'string', description: 'Existing r2pipe session id from a prior r2_* call.' },
|
|
17
|
+
backend: { type: 'string', enum: %w[r2 rizin] }
|
|
18
|
+
},
|
|
19
|
+
anyOf: [{ required: %w[bin] }, { required: %w[path] }, { required: %w[session] }]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
handler: lambda { |args|
|
|
23
|
+
args = args.transform_keys(&:to_sym)
|
|
24
|
+
session = args[:session] || PWN::Plugins::Radare2.open(args)
|
|
25
|
+
{ session: session, command: 'aflj', functions: PWN::Plugins::Radare2.functions(session: session) }
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
PWN::AI::Agent::Registry.register(
|
|
29
|
+
name: 'r2_disasm',
|
|
30
|
+
toolset: 'pwn',
|
|
31
|
+
schema: {
|
|
32
|
+
name: 'r2_disasm',
|
|
33
|
+
description: 'Return parsed pdfj/pdj disassembly JSON for an address or function (default main) from an r2pipe session.',
|
|
34
|
+
parameters: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
bin: { type: 'string' },
|
|
38
|
+
path: { type: 'string' },
|
|
39
|
+
session: { type: 'string' },
|
|
40
|
+
addr: { type: 'string', description: 'Function, symbol, or 0x address. Defaults to main.' },
|
|
41
|
+
n: { type: 'integer', minimum: 1, maximum: 4096, description: 'If set, use pdj for n instructions; otherwise pdfj.' },
|
|
42
|
+
backend: { type: 'string', enum: %w[r2 rizin] }
|
|
43
|
+
},
|
|
44
|
+
anyOf: [{ required: %w[bin] }, { required: %w[path] }, { required: %w[session] }]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
handler: lambda { |args|
|
|
48
|
+
args = args.transform_keys(&:to_sym)
|
|
49
|
+
session = args[:session] || PWN::Plugins::Radare2.open(args)
|
|
50
|
+
addr = args[:addr]
|
|
51
|
+
addr = 'main' if addr.nil? || addr.to_s.empty?
|
|
52
|
+
{ session: session, command: args[:n] ? 'pdj' : 'pdfj', addr: addr,
|
|
53
|
+
disasm: PWN::Plugins::Radare2.disasm(args.merge(session: session, addr: addr)) }
|
|
54
|
+
}
|
|
55
|
+
)
|
|
@@ -35,6 +35,7 @@ PWN::AI::Agent::Registry.register(
|
|
|
35
35
|
code: { type: 'string', description: 'Ruby source to evaluate.' },
|
|
36
36
|
encoding: { type: 'string', description: 'Set to base64 when data holds the Ruby source.' },
|
|
37
37
|
data: { type: 'string', description: 'Base64 Ruby source when encoding is base64.' },
|
|
38
|
+
placeholder_ok: { type: 'boolean', description: 'Explicitly permit placeholder tokens; Ruby must still parse and execute. Literal strings/heredocs never require this.' },
|
|
38
39
|
timeout: {
|
|
39
40
|
type: 'integer',
|
|
40
41
|
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).'
|
|
@@ -58,11 +59,27 @@ PWN::AI::Agent::Registry.register(
|
|
|
58
59
|
code = args[:code].to_s
|
|
59
60
|
return PWN::AI::Agent::ToolGuard.invalid_payload(hint: 'code must be a nonempty string') unless args[:code].is_a?(String) && !code.empty?
|
|
60
61
|
|
|
62
|
+
placeholder = PWN::AI::Agent::ToolGuard.placeholder_match(text: code, language: :ruby, placeholder_ok: args[:placeholder_ok])
|
|
63
|
+
if placeholder
|
|
64
|
+
return PWN::AI::Agent::ToolGuard.invalid_payload(
|
|
65
|
+
**placeholder,
|
|
66
|
+
text: code,
|
|
67
|
+
hint: 'Ruby source contains an elided code stub outside literal data.',
|
|
68
|
+
remedy: 'Replace the matched stub with complete Ruby code. Strings and heredocs may contain ellipses without placeholder_ok. Explicit placeholder_ok: true skips this guard but not Ruby parsing; base64 data is decoded before validation.'
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
timeout = PWN::AI::Agent::ToolGuard.deadline_s(timeout: args[:timeout], kind: :eval, payload: code)
|
|
73
|
+
PWN::AI::Agent::ToolGuard.protect_http!
|
|
74
|
+
sandboxed = PWN::Plugins::AISandbox.wrap_ruby(code: code, timeout: timeout)
|
|
75
|
+
return sandboxed unless sandboxed.nil?
|
|
76
|
+
|
|
77
|
+
blocked = PWN::Plugins::MethodCatalog.guard_eval(code: code)
|
|
78
|
+
return blocked if blocked
|
|
79
|
+
|
|
61
80
|
old_stdout = $stdout
|
|
62
81
|
buf = StringIO.new
|
|
63
82
|
$stdout = buf
|
|
64
|
-
timeout = PWN::AI::Agent::ToolGuard.deadline_s(timeout: args[:timeout], kind: :eval, payload: code)
|
|
65
|
-
PWN::AI::Agent::ToolGuard.protect_http!
|
|
66
83
|
begin
|
|
67
84
|
# rubocop:disable Security/Eval
|
|
68
85
|
# INTENTIONAL: this IS the pwn-ai → PWN bridge
|