pwn 0.5.615 → 0.5.617
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/README.md +24 -10
- data/bin/pwn +47 -2
- data/bin/pwn_gqrx_scanner +75 -18
- data/documentation/Diagrams.md +123 -40
- data/documentation/General-PWN-Usage.md +50 -30
- data/documentation/Home.md +78 -51
- data/documentation/How-PWN-Works.md +59 -57
- data/documentation/Installation.md +53 -28
- data/documentation/What-is-PWN.md +51 -32
- data/documentation/Why-PWN.md +41 -11
- data/documentation/diagrams/ai-integration-tool-calling.svg +141 -103
- data/documentation/diagrams/build.sh +20 -0
- data/documentation/diagrams/burp-vs-zap-preference.svg +47 -62
- data/documentation/diagrams/code-scanning-sast.svg +139 -79
- data/documentation/diagrams/dot/_THEME.md +27 -0
- data/documentation/diagrams/dot/agent-tool-registry.dot +59 -0
- data/documentation/diagrams/dot/ai-integration-tool-calling.dot +47 -20
- data/documentation/diagrams/dot/aws-cloud-security.dot +48 -0
- data/documentation/diagrams/dot/burp-vs-zap-preference.dot +23 -12
- data/documentation/diagrams/dot/code-scanning-sast.dot +46 -18
- data/documentation/diagrams/dot/cron-scheduling.dot +40 -0
- data/documentation/diagrams/dot/driver-framework.dot +36 -14
- data/documentation/diagrams/dot/extrospection-world-awareness.dot +55 -0
- data/documentation/diagrams/dot/fuzzing-workflow.dot +44 -24
- data/documentation/diagrams/dot/history-to-drivers.dot +31 -17
- data/documentation/diagrams/dot/memory-skills-detailed.dot +57 -25
- data/documentation/diagrams/dot/network-infra-testing.dot +51 -19
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +110 -44
- data/documentation/diagrams/dot/penetration-testing-workflow.dot +56 -25
- data/documentation/diagrams/dot/plugin-ecosystem.dot +41 -27
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +87 -46
- data/documentation/diagrams/dot/pwn-repl-prototyping.dot +49 -30
- data/documentation/diagrams/dot/reporting-pipeline.dot +43 -14
- data/documentation/diagrams/dot/reverse-engineering-flow.dot +40 -18
- data/documentation/diagrams/dot/sdr-radio-flow.dot +45 -0
- data/documentation/diagrams/dot/sessions-cron-automation.dot +41 -14
- data/documentation/diagrams/dot/swarm-multi-agent.dot +70 -0
- data/documentation/diagrams/dot/web-application-testing.dot +45 -23
- data/documentation/diagrams/dot/zero-day-research-flow.dot +46 -24
- data/documentation/diagrams/driver-framework.svg +87 -66
- data/documentation/diagrams/fuzzing-workflow.svg +128 -115
- data/documentation/diagrams/history-to-drivers.svg +84 -82
- data/documentation/diagrams/memory-skills-detailed.svg +167 -119
- data/documentation/diagrams/network-infra-testing.svg +146 -84
- data/documentation/diagrams/overall-pwn-architecture.svg +356 -185
- data/documentation/diagrams/penetration-testing-workflow.svg +161 -108
- data/documentation/diagrams/plugin-ecosystem.svg +225 -121
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +257 -185
- data/documentation/diagrams/pwn-repl-prototyping.svg +126 -137
- data/documentation/diagrams/reporting-pipeline.svg +128 -68
- data/documentation/diagrams/reverse-engineering-flow.svg +100 -80
- data/documentation/diagrams/sessions-cron-automation.svg +112 -68
- data/documentation/diagrams/web-application-testing.svg +136 -100
- data/documentation/diagrams/zero-day-research-flow.svg +116 -112
- data/git_commit.sh +1 -1
- data/lib/pwn/ai/agent/assembly.rb +1 -1
- data/lib/pwn/ai/agent/btc.rb +1 -1
- data/lib/pwn/ai/agent/burp_suite.rb +1 -1
- data/lib/pwn/ai/agent/extrospection.rb +618 -0
- data/lib/pwn/ai/agent/gqrx.rb +2 -2
- data/lib/pwn/ai/agent/hacker_one.rb +1 -1
- data/lib/pwn/ai/agent/introspection.rb +91 -0
- data/lib/pwn/ai/agent/learning.rb +6 -4
- data/lib/pwn/ai/agent/loop.rb +15 -0
- data/lib/pwn/ai/agent/prompt_builder.rb +10 -1
- data/lib/pwn/ai/agent/sast.rb +1 -1
- data/lib/pwn/ai/agent/swarm.rb +437 -0
- data/lib/pwn/ai/agent/tools/cron.rb +163 -0
- data/lib/pwn/ai/agent/tools/extrospection.rb +280 -0
- data/lib/pwn/ai/agent/tools/learning.rb +108 -0
- data/lib/pwn/ai/agent/tools/memory.rb +27 -0
- data/lib/pwn/ai/agent/tools/metrics.rb +61 -0
- data/lib/pwn/ai/agent/tools/sessions.rb +139 -0
- data/lib/pwn/ai/agent/tools/skills.rb +30 -0
- data/lib/pwn/ai/agent/tools/swarm.rb +229 -0
- data/lib/pwn/ai/agent/transparent_browser.rb +1 -1
- data/lib/pwn/ai/agent/vuln_gen.rb +2 -2
- data/lib/pwn/ai/agent.rb +3 -0
- data/lib/pwn/ai/anthropic.rb +19 -4
- data/lib/pwn/ai.rb +0 -1
- data/lib/pwn/config.rb +10 -23
- data/lib/pwn/cron.rb +16 -7
- data/lib/pwn/plugins/repl.rb +90 -281
- data/lib/pwn/sdr/decoder/base.rb +251 -0
- data/lib/pwn/sdr/decoder/gsm.rb +84 -185
- data/lib/pwn/sdr/decoder/pocsag.rb +62 -217
- data/lib/pwn/sdr/decoder.rb +1 -0
- data/lib/pwn/sdr/gqrx.rb +446 -65
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/extrospection_spec.rb +15 -0
- data/spec/lib/pwn/ai/agent/introspection_spec.rb +15 -0
- data/spec/lib/pwn/ai/agent/swarm_spec.rb +34 -0
- data/spec/lib/pwn/ai/agent/tools/cron_spec.rb +40 -0
- data/spec/lib/pwn/ai/agent/tools/extrospection_spec.rb +40 -0
- data/spec/lib/pwn/ai/agent/tools/metrics_spec.rb +20 -0
- data/spec/lib/pwn/ai/agent/tools/sessions_spec.rb +35 -0
- data/spec/lib/pwn/ai/agent/tools/swarm_spec.rb +17 -0
- data/spec/lib/pwn/{ai/introspection_spec.rb → sdr/decoder/base_spec.rb} +3 -3
- data/third_party/pwn_rdoc.jsonl +178 -33
- metadata +27 -3
- data/lib/pwn/ai/introspection.rb +0 -76
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
require 'pwn/cron'
|
|
5
|
+
|
|
6
|
+
# Thin wrappers around PWN::Cron so the model can schedule, inspect, run,
|
|
7
|
+
# enable/disable and remove recurring pwn-ai jobs (~/.pwn/cron/jobs.yml).
|
|
8
|
+
# Jobs may carry a pwn-ai `prompt`, a `ruby` snippet, or an external
|
|
9
|
+
# `script` path; system-cron installation is opt-in via :install_crontab.
|
|
10
|
+
|
|
11
|
+
PWN::AI::Agent::Registry.register(
|
|
12
|
+
name: 'cron_list',
|
|
13
|
+
toolset: 'cron',
|
|
14
|
+
schema: {
|
|
15
|
+
name: 'cron_list',
|
|
16
|
+
description: 'List all pwn-ai cron jobs from ~/.pwn/cron/jobs.yml ' \
|
|
17
|
+
'(id, name, schedule, enabled, last_run, last_status).',
|
|
18
|
+
parameters: { type: 'object', properties: {}, required: [] }
|
|
19
|
+
},
|
|
20
|
+
check: -> { defined?(PWN::Cron) },
|
|
21
|
+
handler: lambda { |_args|
|
|
22
|
+
PWN::Cron.list.map do |id, job|
|
|
23
|
+
{
|
|
24
|
+
id: id,
|
|
25
|
+
name: job[:name],
|
|
26
|
+
schedule: job[:schedule],
|
|
27
|
+
enabled: job[:enabled],
|
|
28
|
+
delivery: job[:delivery],
|
|
29
|
+
prompt: job[:prompt].to_s[0, 120],
|
|
30
|
+
ruby: job[:ruby].to_s[0, 120],
|
|
31
|
+
script: job[:script],
|
|
32
|
+
last_run: job[:last_run],
|
|
33
|
+
last_status: job[:last_status]
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
PWN::AI::Agent::Registry.register(
|
|
40
|
+
name: 'cron_create',
|
|
41
|
+
toolset: 'cron',
|
|
42
|
+
schema: {
|
|
43
|
+
name: 'cron_create',
|
|
44
|
+
description: 'Create a scheduled pwn-ai job. Provide exactly one of ' \
|
|
45
|
+
'prompt (sent to the active AI engine), ruby (evaluated ' \
|
|
46
|
+
'in-process), or script (external path). Set ' \
|
|
47
|
+
'install_crontab:true to also append a system crontab ' \
|
|
48
|
+
'entry that invokes PWN::Cron.run for this job id.',
|
|
49
|
+
parameters: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
name: { type: 'string', description: 'Human-friendly job name (default job-<id>).' },
|
|
53
|
+
schedule: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'Cron expression, e.g. "0 * * * *". Required when ' \
|
|
56
|
+
'install_crontab is true; otherwise informational.'
|
|
57
|
+
},
|
|
58
|
+
prompt: { type: 'string', description: 'pwn-ai prompt to run against the active engine.' },
|
|
59
|
+
ruby: { type: 'string', description: 'Ruby snippet evaluated in TOPLEVEL_BINDING.' },
|
|
60
|
+
script: { type: 'string', description: 'Path to an external executable script.' },
|
|
61
|
+
delivery: { type: 'string', enum: %w[log stdout], default: 'log' },
|
|
62
|
+
enabled: { type: 'boolean', default: true },
|
|
63
|
+
install_crontab: {
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
default: false,
|
|
66
|
+
description: 'Also append a `crontab -l` entry that runs this job id via pwn.'
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
required: %w[schedule]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
check: -> { defined?(PWN::Cron) },
|
|
73
|
+
handler: lambda { |args|
|
|
74
|
+
PWN::Cron.create(
|
|
75
|
+
name: args[:name],
|
|
76
|
+
schedule: args[:schedule],
|
|
77
|
+
prompt: args[:prompt],
|
|
78
|
+
ruby: args[:ruby],
|
|
79
|
+
script: args[:script],
|
|
80
|
+
delivery: args[:delivery],
|
|
81
|
+
enabled: args.fetch(:enabled, true),
|
|
82
|
+
install_crontab: args[:install_crontab]
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
PWN::AI::Agent::Registry.register(
|
|
88
|
+
name: 'cron_run',
|
|
89
|
+
toolset: 'cron',
|
|
90
|
+
schema: {
|
|
91
|
+
name: 'cron_run',
|
|
92
|
+
description: 'Execute a cron job immediately by id (or name) and ' \
|
|
93
|
+
'return { job:, result:, duration:, status: }. Updates ' \
|
|
94
|
+
'last_run / last_status in jobs.yml.',
|
|
95
|
+
parameters: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
id: { type: 'string', description: 'Job id or job name.' }
|
|
99
|
+
},
|
|
100
|
+
required: %w[id]
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
check: -> { defined?(PWN::Cron) },
|
|
104
|
+
handler: lambda { |args|
|
|
105
|
+
PWN::Cron.run(id: args[:id])
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
PWN::AI::Agent::Registry.register(
|
|
110
|
+
name: 'cron_enable',
|
|
111
|
+
toolset: 'cron',
|
|
112
|
+
schema: {
|
|
113
|
+
name: 'cron_enable',
|
|
114
|
+
description: 'Set enabled:true on a cron job by id.',
|
|
115
|
+
parameters: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: { id: { type: 'string' } },
|
|
118
|
+
required: %w[id]
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
check: -> { defined?(PWN::Cron) },
|
|
122
|
+
handler: lambda { |args|
|
|
123
|
+
{ id: args[:id], job: PWN::Cron.enable(id: args[:id]) }
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
PWN::AI::Agent::Registry.register(
|
|
128
|
+
name: 'cron_disable',
|
|
129
|
+
toolset: 'cron',
|
|
130
|
+
schema: {
|
|
131
|
+
name: 'cron_disable',
|
|
132
|
+
description: 'Set enabled:false on a cron job by id.',
|
|
133
|
+
parameters: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties: { id: { type: 'string' } },
|
|
136
|
+
required: %w[id]
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
check: -> { defined?(PWN::Cron) },
|
|
140
|
+
handler: lambda { |args|
|
|
141
|
+
{ id: args[:id], job: PWN::Cron.disable(id: args[:id]) }
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
PWN::AI::Agent::Registry.register(
|
|
146
|
+
name: 'cron_remove',
|
|
147
|
+
toolset: 'cron',
|
|
148
|
+
schema: {
|
|
149
|
+
name: 'cron_remove',
|
|
150
|
+
description: 'Delete a cron job by id from ~/.pwn/cron/jobs.yml. ' \
|
|
151
|
+
'Does NOT scrub any system crontab entry — remove that ' \
|
|
152
|
+
'manually with `crontab -e` if install_crontab was used.',
|
|
153
|
+
parameters: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: { id: { type: 'string' } },
|
|
156
|
+
required: %w[id]
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
check: -> { defined?(PWN::Cron) },
|
|
160
|
+
handler: lambda { |args|
|
|
161
|
+
{ id: args[:id], removed: PWN::Cron.remove(id: args[:id]) }
|
|
162
|
+
}
|
|
163
|
+
)
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
require 'pwn/ai/agent/extrospection'
|
|
5
|
+
|
|
6
|
+
# Expose the OUTWARD-facing half of the pwn-ai learning feedback loop to
|
|
7
|
+
# the model. Where the `learning_*` / `metrics_*` tools are INTROSPECTIVE
|
|
8
|
+
# (self-telemetry, own outcomes, own transcripts), the `extro_*` tools are
|
|
9
|
+
# EXTROSPECTIVE: they probe, record and reason about the WORLD the agent
|
|
10
|
+
# operates in — host state, toolchain versions, network posture, repo
|
|
11
|
+
# drift, recon findings, and external threat-intel — and correlate that
|
|
12
|
+
# world-state back against introspective failures. Together they close
|
|
13
|
+
# BOTH halves of the feedback loop.
|
|
14
|
+
|
|
15
|
+
PWN::AI::Agent::Registry.register(
|
|
16
|
+
name: 'extro_snapshot',
|
|
17
|
+
toolset: 'extrospection',
|
|
18
|
+
schema: {
|
|
19
|
+
name: 'extro_snapshot',
|
|
20
|
+
description: 'Capture a fingerprint of the OUTSIDE world (host, kernel, ' \
|
|
21
|
+
'distro, network interfaces, listening ports, toolchain ' \
|
|
22
|
+
'binary versions, pwn repo HEAD, ruby/engine env) and ' \
|
|
23
|
+
'persist it to ~/.pwn/extrospection.json. Returns ' \
|
|
24
|
+
'{snapshot:, drift:} where drift is the delta vs the ' \
|
|
25
|
+
'PREVIOUS snapshot. Run at the start of an engagement and ' \
|
|
26
|
+
'after any host change so extro_drift / extro_correlate ' \
|
|
27
|
+
'have a baseline.',
|
|
28
|
+
parameters: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
persist: { type: 'boolean', default: true, description: 'Write to disk & rotate previous baseline.' },
|
|
32
|
+
sections: { type: 'array', items: { type: 'string', enum: %w[host net toolchain repo env] }, description: 'Subset of probes to run (default all).' }
|
|
33
|
+
},
|
|
34
|
+
required: []
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
38
|
+
handler: lambda { |args|
|
|
39
|
+
o = {}
|
|
40
|
+
o[:persist] = args[:persist] if args.key?(:persist)
|
|
41
|
+
o[:sections] = args[:sections] if args.key?(:sections)
|
|
42
|
+
PWN::AI::Agent::Extrospection.snapshot(o)
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
PWN::AI::Agent::Registry.register(
|
|
47
|
+
name: 'extro_drift',
|
|
48
|
+
toolset: 'extrospection',
|
|
49
|
+
schema: {
|
|
50
|
+
name: 'extro_drift',
|
|
51
|
+
description: 'Diff the current host/toolchain/network/repo state ' \
|
|
52
|
+
'against the last persisted snapshot. Returns ' \
|
|
53
|
+
'{changed:[], added:[], removed:[]} with dotted-path keys ' \
|
|
54
|
+
'(e.g. "toolchain.nmap", "net.listening", "repo.head"). ' \
|
|
55
|
+
'Use to detect when the environment moved under you ' \
|
|
56
|
+
'between sessions before trusting introspective metrics.',
|
|
57
|
+
parameters: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
live: { type: 'boolean', default: true, description: 'true = probe NOW vs stored snapshot; false = stored snapshot vs stored previous.' }
|
|
61
|
+
},
|
|
62
|
+
required: []
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
66
|
+
handler: lambda { |args|
|
|
67
|
+
o = {}
|
|
68
|
+
o[:live] = args[:live] if args.key?(:live)
|
|
69
|
+
PWN::AI::Agent::Extrospection.drift(o)
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
PWN::AI::Agent::Registry.register(
|
|
74
|
+
name: 'extro_observe',
|
|
75
|
+
toolset: 'extrospection',
|
|
76
|
+
schema: {
|
|
77
|
+
name: 'extro_observe',
|
|
78
|
+
description: 'Record a fact about the OUTSIDE world (recon finding, ' \
|
|
79
|
+
'service banner, target fingerprint, CVE match, network ' \
|
|
80
|
+
'topology note). This is the extrospective analogue of ' \
|
|
81
|
+
'learning_note_outcome — it persists WHAT YOU SAW rather ' \
|
|
82
|
+
'than HOW YOU PERFORMED. Observations are re-injected into ' \
|
|
83
|
+
'every future system prompt via the EXTROSPECTION block.',
|
|
84
|
+
parameters: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
source: { type: 'string', description: 'Where it came from (nmap, shodan, burp, cve, human, ...).' },
|
|
88
|
+
data: { type: 'string', description: 'The observation itself.' },
|
|
89
|
+
category: { type: 'string', enum: %w[recon vuln intel target network env misc], default: 'misc' },
|
|
90
|
+
target: { type: 'string', description: 'Host / IP / URL / asset the observation is about.' },
|
|
91
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
92
|
+
ttl: { type: 'integer', description: 'Seconds until stale (omit = forever).' }
|
|
93
|
+
},
|
|
94
|
+
required: %w[source data]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
98
|
+
handler: lambda { |args|
|
|
99
|
+
PWN::AI::Agent::Extrospection.observe(
|
|
100
|
+
source: args[:source],
|
|
101
|
+
data: args[:data],
|
|
102
|
+
category: args[:category],
|
|
103
|
+
target: args[:target],
|
|
104
|
+
tags: args[:tags],
|
|
105
|
+
ttl: args[:ttl]
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
PWN::AI::Agent::Registry.register(
|
|
111
|
+
name: 'extro_observations',
|
|
112
|
+
toolset: 'extrospection',
|
|
113
|
+
schema: {
|
|
114
|
+
name: 'extro_observations',
|
|
115
|
+
description: 'Query recorded external observations from ' \
|
|
116
|
+
'~/.pwn/extrospection.json (the read-side of ' \
|
|
117
|
+
'extro_observe). Filter by source / category / target / ' \
|
|
118
|
+
'tag; newest-first. Use to recall recon findings and ' \
|
|
119
|
+
'threat-intel captured in prior sessions.',
|
|
120
|
+
parameters: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
limit: { type: 'integer', default: 50 },
|
|
124
|
+
source: { type: 'string' },
|
|
125
|
+
category: { type: 'string' },
|
|
126
|
+
target: { type: 'string' },
|
|
127
|
+
tag: { type: 'string' },
|
|
128
|
+
fresh_only: { type: 'boolean', default: false, description: 'Drop entries whose TTL has expired.' }
|
|
129
|
+
},
|
|
130
|
+
required: []
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
134
|
+
handler: lambda { |args|
|
|
135
|
+
PWN::AI::Agent::Extrospection.observations(
|
|
136
|
+
limit: args[:limit] || 50,
|
|
137
|
+
source: args[:source],
|
|
138
|
+
category: args[:category],
|
|
139
|
+
target: args[:target],
|
|
140
|
+
tag: args[:tag],
|
|
141
|
+
fresh_only: args[:fresh_only]
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
PWN::AI::Agent::Registry.register(
|
|
147
|
+
name: 'extro_intel',
|
|
148
|
+
toolset: 'extrospection',
|
|
149
|
+
schema: {
|
|
150
|
+
name: 'extro_intel',
|
|
151
|
+
description: 'Query external threat-intelligence feeds (NVD CVE API, ' \
|
|
152
|
+
'CIRCL CVE-Search, local searchsploit / Exploit-DB) for a ' \
|
|
153
|
+
'keyword, product, or CVE id. Best-effort: any feed that ' \
|
|
154
|
+
'is unreachable degrades to []. Set record:true to also ' \
|
|
155
|
+
'persist each hit as an :intel observation so ' \
|
|
156
|
+
'extro_correlate can match it against installed toolchain ' \
|
|
157
|
+
'versions on this host.',
|
|
158
|
+
parameters: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
query: { type: 'string', description: 'Keyword / product+version / CVE-YYYY-NNNN.' },
|
|
162
|
+
feeds: { type: 'array', items: { type: 'string', enum: %w[nvd circl exploitdb] } },
|
|
163
|
+
limit: { type: 'integer', default: 5 },
|
|
164
|
+
record: { type: 'boolean', default: false }
|
|
165
|
+
},
|
|
166
|
+
required: %w[query]
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
170
|
+
handler: lambda { |args|
|
|
171
|
+
PWN::AI::Agent::Extrospection.intel(
|
|
172
|
+
query: args[:query],
|
|
173
|
+
feeds: args[:feeds],
|
|
174
|
+
limit: args[:limit],
|
|
175
|
+
record: args[:record]
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
PWN::AI::Agent::Registry.register(
|
|
181
|
+
name: 'extro_correlate',
|
|
182
|
+
toolset: 'extrospection',
|
|
183
|
+
schema: {
|
|
184
|
+
name: 'extro_correlate',
|
|
185
|
+
description: 'THE join between introspection and extrospection. ' \
|
|
186
|
+
'Cross-references (a) Metrics tools with <50 % success ' \
|
|
187
|
+
'against toolchain drift / missing binaries, (b) Learning ' \
|
|
188
|
+
'failures against host/net/repo drift on the same day, ' \
|
|
189
|
+
'(c) recorded :intel observations against installed ' \
|
|
190
|
+
'component versions. Returns actionable findings so the ' \
|
|
191
|
+
'agent can distinguish "I did it wrong" from "the world ' \
|
|
192
|
+
'changed under me".',
|
|
193
|
+
parameters: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
limit: { type: 'integer', default: 10 }
|
|
197
|
+
},
|
|
198
|
+
required: []
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
202
|
+
handler: lambda { |args|
|
|
203
|
+
PWN::AI::Agent::Extrospection.correlate(limit: args[:limit] || 10)
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
PWN::AI::Agent::Registry.register(
|
|
208
|
+
name: 'extro_stats',
|
|
209
|
+
toolset: 'extrospection',
|
|
210
|
+
schema: {
|
|
211
|
+
name: 'extro_stats',
|
|
212
|
+
description: 'Return world-awareness metrics: snapshot age & ' \
|
|
213
|
+
'fingerprint, observation count, drift counts ' \
|
|
214
|
+
'(changed/added/removed since previous), toolchain bins ' \
|
|
215
|
+
'present, listening-port count. The extrospective ' \
|
|
216
|
+
'counterpart to learning_stats.',
|
|
217
|
+
parameters: { type: 'object', properties: {}, required: [] }
|
|
218
|
+
},
|
|
219
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
220
|
+
handler: ->(_args) { PWN::AI::Agent::Extrospection.stats }
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
PWN::AI::Agent::Registry.register(
|
|
224
|
+
name: 'extro_reset',
|
|
225
|
+
toolset: 'extrospection',
|
|
226
|
+
schema: {
|
|
227
|
+
name: 'extro_reset',
|
|
228
|
+
description: 'Wipe ~/.pwn/extrospection.json (snapshot, previous ' \
|
|
229
|
+
'baseline, and ALL recorded observations). Use when ' \
|
|
230
|
+
'moving to a new engagement / target scope so stale recon ' \
|
|
231
|
+
'and drift stop polluting the EXTROSPECTION prompt block. ' \
|
|
232
|
+
'IRREVERSIBLE — must pass confirm:true.',
|
|
233
|
+
parameters: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: {
|
|
236
|
+
confirm: { type: 'boolean', description: 'Must be true to actually reset.' }
|
|
237
|
+
},
|
|
238
|
+
required: %w[confirm]
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
check: -> { defined?(PWN::AI::Agent::Extrospection) },
|
|
242
|
+
handler: lambda { |args|
|
|
243
|
+
raise ArgumentError, 'refusing to reset extrospection without confirm:true' unless args[:confirm] == true
|
|
244
|
+
|
|
245
|
+
before = PWN::AI::Agent::Extrospection.stats
|
|
246
|
+
PWN::AI::Agent::Extrospection.reset
|
|
247
|
+
{ reset: true, cleared: before, file: PWN::AI::Agent::Extrospection::EXTRO_FILE }
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
PWN::AI::Agent::Registry.register(
|
|
252
|
+
name: 'extro_auto_toggle',
|
|
253
|
+
toolset: 'extrospection',
|
|
254
|
+
schema: {
|
|
255
|
+
name: 'extro_auto_toggle',
|
|
256
|
+
description: 'Enable/disable end-of-run auto-extrospection ' \
|
|
257
|
+
'(PWN::Env[:ai][:agent][:auto_extrospect]). When enabled, ' \
|
|
258
|
+
'Learning.auto_reflect ALSO calls Extrospection.auto_' \
|
|
259
|
+
'extrospect after every final answer, so host drift is ' \
|
|
260
|
+
'captured continuously without an explicit extro_snapshot ' \
|
|
261
|
+
'call. Omit `enabled` to just read the current state.',
|
|
262
|
+
parameters: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
enabled: { type: 'boolean', description: 'Desired state. Omit to only query.' }
|
|
266
|
+
},
|
|
267
|
+
required: []
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
check: -> { defined?(PWN::Env) && PWN::Env.is_a?(Hash) },
|
|
271
|
+
handler: lambda { |args|
|
|
272
|
+
ai = PWN::Env[:ai]
|
|
273
|
+
raise 'PWN::Env[:ai] is unavailable or immutable' unless ai.is_a?(Hash) && !ai.frozen?
|
|
274
|
+
|
|
275
|
+
ai[:agent] = (ai[:agent] || {}).dup if ai[:agent].nil? || ai[:agent].frozen?
|
|
276
|
+
prev = ai[:agent][:auto_extrospect] ? true : false
|
|
277
|
+
ai[:agent][:auto_extrospect] = (args[:enabled] ? true : false) if args.key?(:enabled)
|
|
278
|
+
{ previous: prev, current: ai[:agent][:auto_extrospect] ? true : false }
|
|
279
|
+
}
|
|
280
|
+
)
|
|
@@ -111,3 +111,111 @@ PWN::AI::Agent::Registry.register(
|
|
|
111
111
|
)
|
|
112
112
|
}
|
|
113
113
|
)
|
|
114
|
+
|
|
115
|
+
PWN::AI::Agent::Registry.register(
|
|
116
|
+
name: 'learning_outcomes',
|
|
117
|
+
toolset: 'learning',
|
|
118
|
+
schema: {
|
|
119
|
+
name: 'learning_outcomes',
|
|
120
|
+
description: 'Query recorded task outcomes from ~/.pwn/learning.jsonl ' \
|
|
121
|
+
'(the read-side of learning_note_outcome). Filter by ' \
|
|
122
|
+
'success and/or tag substring; returns newest-first.',
|
|
123
|
+
parameters: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
limit: { type: 'integer', default: 50, description: 'Max entries returned newest-first.' },
|
|
127
|
+
success: { type: 'boolean', description: 'Filter: only successes (true) or only failures (false). Omit for both.' },
|
|
128
|
+
tag: { type: 'string', description: 'Filter: substring match against outcome tags.' }
|
|
129
|
+
},
|
|
130
|
+
required: []
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
check: -> { defined?(PWN::AI::Agent::Learning) },
|
|
134
|
+
handler: lambda { |args|
|
|
135
|
+
o = { limit: args[:limit] || 50 }
|
|
136
|
+
o[:success] = args[:success] if args.key?(:success)
|
|
137
|
+
o[:tag] = args[:tag] if args.key?(:tag)
|
|
138
|
+
PWN::AI::Agent::Learning.outcomes(o)
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
PWN::AI::Agent::Registry.register(
|
|
143
|
+
name: 'learning_consolidate',
|
|
144
|
+
toolset: 'learning',
|
|
145
|
+
schema: {
|
|
146
|
+
name: 'learning_consolidate',
|
|
147
|
+
description: 'Deduplicate near-identical PWN::Memory lesson entries and ' \
|
|
148
|
+
'prune the oldest ones once max_entries is exceeded, so the ' \
|
|
149
|
+
'injected MEMORY block in every system prompt stays ' \
|
|
150
|
+
'high-signal. Returns { removed:, remaining: }.',
|
|
151
|
+
parameters: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
max_entries: { type: 'integer', default: 200, description: 'Hard cap on PWN::Memory size after consolidation.' }
|
|
155
|
+
},
|
|
156
|
+
required: []
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
check: -> { defined?(PWN::AI::Agent::Learning) },
|
|
160
|
+
handler: lambda { |args|
|
|
161
|
+
PWN::AI::Agent::Learning.consolidate(max_entries: args[:max_entries])
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
PWN::AI::Agent::Registry.register(
|
|
166
|
+
name: 'learning_reset',
|
|
167
|
+
toolset: 'learning',
|
|
168
|
+
schema: {
|
|
169
|
+
name: 'learning_reset',
|
|
170
|
+
description: 'Wipe ~/.pwn/learning.jsonl (all recorded task outcomes and ' \
|
|
171
|
+
'their success_rate). Use for a clean slate after major ' \
|
|
172
|
+
'refactors or when dev-experiment noise has polluted the ' \
|
|
173
|
+
'outcome history. IRREVERSIBLE — must pass confirm:true.',
|
|
174
|
+
parameters: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
confirm: { type: 'boolean', description: 'Must be true to actually reset.' }
|
|
178
|
+
},
|
|
179
|
+
required: %w[confirm]
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
check: -> { defined?(PWN::AI::Agent::Learning) },
|
|
183
|
+
handler: lambda { |args|
|
|
184
|
+
raise ArgumentError, 'refusing to reset learning without confirm:true' unless args[:confirm] == true
|
|
185
|
+
|
|
186
|
+
before = PWN::AI::Agent::Learning.outcomes(limit: 100_000).length
|
|
187
|
+
PWN::AI::Agent::Learning.reset
|
|
188
|
+
{ reset: true, outcomes_cleared: before, file: PWN::AI::Agent::Learning::LEARNING_FILE }
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
PWN::AI::Agent::Registry.register(
|
|
193
|
+
name: 'learning_auto_reflect_toggle',
|
|
194
|
+
toolset: 'learning',
|
|
195
|
+
schema: {
|
|
196
|
+
name: 'learning_auto_reflect_toggle',
|
|
197
|
+
description: 'Enable/disable end-of-run auto-reflection ' \
|
|
198
|
+
'(PWN::Env[:ai][:agent][:auto_reflect]). When enabled, ' \
|
|
199
|
+
'Loop.run calls Learning.auto_reflect on the session after ' \
|
|
200
|
+
'every final answer. Disable during noisy fuzzing loops; ' \
|
|
201
|
+
're-enable for the summary turn. Omit `enabled` to just ' \
|
|
202
|
+
'read the current state.',
|
|
203
|
+
parameters: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
enabled: { type: 'boolean', description: 'Desired state. Omit to only query.' }
|
|
207
|
+
},
|
|
208
|
+
required: []
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
check: -> { defined?(PWN::Env) && PWN::Env.is_a?(Hash) },
|
|
212
|
+
handler: lambda { |args|
|
|
213
|
+
ai = PWN::Env[:ai]
|
|
214
|
+
raise 'PWN::Env[:ai] is unavailable or immutable' unless ai.is_a?(Hash) && !ai.frozen?
|
|
215
|
+
|
|
216
|
+
ai[:agent] = (ai[:agent] || {}).dup if ai[:agent].nil? || ai[:agent].frozen?
|
|
217
|
+
prev = ai[:agent][:auto_reflect] ? true : false
|
|
218
|
+
ai[:agent][:auto_reflect] = (args[:enabled] ? true : false) if args.key?(:enabled)
|
|
219
|
+
{ previous: prev, current: ai[:agent][:auto_reflect] ? true : false }
|
|
220
|
+
}
|
|
221
|
+
)
|
|
@@ -73,3 +73,30 @@ PWN::AI::Agent::Registry.register(
|
|
|
73
73
|
{ forgotten: args[:key] }
|
|
74
74
|
}
|
|
75
75
|
)
|
|
76
|
+
|
|
77
|
+
PWN::AI::Agent::Registry.register(
|
|
78
|
+
name: 'memory_clear',
|
|
79
|
+
toolset: 'memory',
|
|
80
|
+
schema: {
|
|
81
|
+
name: 'memory_clear',
|
|
82
|
+
description: 'Wipe ALL persistent memory (~/.pwn/memory.json) — every ' \
|
|
83
|
+
'fact, preference, lesson and env entry. Use only for a ' \
|
|
84
|
+
'poisoned-context reset. IRREVERSIBLE — must pass ' \
|
|
85
|
+
'confirm:true.',
|
|
86
|
+
parameters: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
confirm: { type: 'boolean', description: 'Must be true to actually clear.' }
|
|
90
|
+
},
|
|
91
|
+
required: %w[confirm]
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
check: -> { defined?(PWN::Memory) },
|
|
95
|
+
handler: lambda { |args|
|
|
96
|
+
raise ArgumentError, 'refusing to clear memory without confirm:true' unless args[:confirm] == true
|
|
97
|
+
|
|
98
|
+
before = PWN::Memory.load.keys.length
|
|
99
|
+
PWN::Memory.clear
|
|
100
|
+
{ cleared: true, entries_removed: before, file: PWN::Memory::MEMORY_FILE }
|
|
101
|
+
}
|
|
102
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
require 'pwn/ai/agent/metrics'
|
|
5
|
+
|
|
6
|
+
# Direct access to the per-tool telemetry store (~/.pwn/metrics.json).
|
|
7
|
+
# `learning_stats` already surfaces a compact metrics blob, but the model
|
|
8
|
+
# needs a way to (a) pull the full table without dragging outcome/skill/
|
|
9
|
+
# memory counts along, and (b) RESET stale telemetry after a broken tool
|
|
10
|
+
# is fixed — otherwise a historical 0 %-success row actively mis-teaches
|
|
11
|
+
# tool selection on every future turn.
|
|
12
|
+
|
|
13
|
+
PWN::AI::Agent::Registry.register(
|
|
14
|
+
name: 'metrics_summary',
|
|
15
|
+
toolset: 'metrics',
|
|
16
|
+
schema: {
|
|
17
|
+
name: 'metrics_summary',
|
|
18
|
+
description: 'Return per-tool telemetry from ~/.pwn/metrics.json ' \
|
|
19
|
+
'(name, calls, success_rate, avg_duration, last_error, ' \
|
|
20
|
+
'last_at) sorted by call volume. Lighter-weight than ' \
|
|
21
|
+
'learning_stats when you only need tool effectiveness.',
|
|
22
|
+
parameters: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
limit: { type: 'integer', default: 25, description: 'Max tool rows to return.' }
|
|
26
|
+
},
|
|
27
|
+
required: []
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
check: -> { defined?(PWN::AI::Agent::Metrics) },
|
|
31
|
+
handler: lambda { |args|
|
|
32
|
+
PWN::AI::Agent::Metrics.summary(limit: args[:limit] || 25)
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
PWN::AI::Agent::Registry.register(
|
|
37
|
+
name: 'metrics_reset',
|
|
38
|
+
toolset: 'metrics',
|
|
39
|
+
schema: {
|
|
40
|
+
name: 'metrics_reset',
|
|
41
|
+
description: 'Wipe ~/.pwn/metrics.json (all per-tool call/success/duration ' \
|
|
42
|
+
'counters). Use after fixing a previously-broken tool so its ' \
|
|
43
|
+
'stale 0 %-success rate stops steering the agent away from ' \
|
|
44
|
+
'it. IRREVERSIBLE — must pass confirm:true.',
|
|
45
|
+
parameters: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
confirm: { type: 'boolean', description: 'Must be true to actually reset.' }
|
|
49
|
+
},
|
|
50
|
+
required: %w[confirm]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
check: -> { defined?(PWN::AI::Agent::Metrics) },
|
|
54
|
+
handler: lambda { |args|
|
|
55
|
+
raise ArgumentError, 'refusing to reset metrics without confirm:true' unless args[:confirm] == true
|
|
56
|
+
|
|
57
|
+
before = PWN::AI::Agent::Metrics.summary(limit: 1_000).length
|
|
58
|
+
PWN::AI::Agent::Metrics.reset
|
|
59
|
+
{ reset: true, tools_cleared: before, file: PWN::AI::Agent::Metrics::METRICS_FILE }
|
|
60
|
+
}
|
|
61
|
+
)
|