pwn 0.5.615 → 0.5.618
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 +109 -85
- data/README.md.bak +200 -0
- data/bin/pwn +47 -2
- data/bin/pwn_gqrx_scanner +75 -18
- data/documentation/AI-Integration.md +42 -26
- data/documentation/AWS.md +57 -0
- data/documentation/Agent-Tool-Registry.md +56 -0
- data/documentation/Banner.md +17 -0
- data/documentation/Blockchain.md +18 -0
- data/documentation/Bounty.md +21 -0
- data/documentation/BurpSuite.md +41 -16
- data/documentation/CLI-Drivers.md +58 -0
- data/documentation/Configuration.md +66 -0
- data/documentation/Contributing.md +33 -19
- data/documentation/Cron.md +47 -0
- data/documentation/Diagrams.md +123 -40
- data/documentation/Drivers.md +43 -16
- data/documentation/Extrospection.md +54 -0
- data/documentation/FFI.md +14 -0
- data/documentation/Fuzzing.md +36 -0
- data/documentation/General-PWN-Usage.md +50 -30
- data/documentation/Hardware.md +40 -0
- data/documentation/Home.md +78 -51
- data/documentation/How-PWN-Works.md +59 -57
- data/documentation/Installation.md +53 -28
- data/documentation/Metasploit.md +34 -0
- data/documentation/NmapIt.md +18 -12
- data/documentation/PWN.png +0 -0
- data/documentation/PWN_Contributors_and_Users.png +0 -0
- data/documentation/Persistence.md +38 -0
- data/documentation/Plugins.md +97 -47
- data/documentation/Reporting.md +25 -18
- data/documentation/SAST.md +39 -22
- data/documentation/SDR.md +40 -0
- data/documentation/Sessions.md +39 -0
- data/documentation/Skills-Memory-Learning.md +49 -29
- data/documentation/Swarm.md +71 -0
- data/documentation/Transparent-Browser.md +26 -22
- data/documentation/Troubleshooting.md +44 -25
- data/documentation/WWW.md +32 -0
- data/documentation/What-is-PWN.md +51 -32
- data/documentation/Why-PWN.md +41 -11
- data/documentation/diagrams/agent-tool-registry.svg +284 -0
- data/documentation/diagrams/ai-integration-tool-calling.svg +141 -103
- data/documentation/diagrams/aws-cloud-security.svg +166 -0
- 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/cron-scheduling.svg +148 -0
- 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/hardware-hacking.dot +48 -0
- 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/persistence-filesystem.dot +29 -0
- 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/extrospection-world-awareness.svg +185 -0
- data/documentation/diagrams/fuzzing-workflow.svg +128 -115
- data/documentation/diagrams/hardware-hacking.svg +163 -0
- 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/persistence-filesystem.svg +174 -0
- 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/sdr-radio-flow.svg +146 -0
- data/documentation/diagrams/sessions-cron-automation.svg +112 -68
- data/documentation/diagrams/swarm-multi-agent.svg +225 -0
- data/documentation/diagrams/web-application-testing.svg +136 -100
- data/documentation/diagrams/zero-day-research-flow.svg +116 -112
- data/documentation/fax-spectrogram.png +0 -0
- data/documentation/fax-waveform.png +0 -0
- data/documentation/pwn-REPL.md +40 -24
- data/documentation/pwn-ai-Agent.md +59 -30
- data/documentation/pwn_android_war_dialer_session.png +0 -0
- data/documentation/pwn_install.png +0 -0
- data/documentation/pwn_wallpaper.jpg +0 -0
- data/documentation/ringing-spectrogram.png +0 -0
- data/documentation/ringing-waveform.png +0 -0
- 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 +199 -37
- metadata +65 -3
- data/lib/pwn/ai/introspection.rb +0 -76
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'time'
|
|
6
|
+
require 'digest'
|
|
7
|
+
require 'socket'
|
|
8
|
+
require 'open3'
|
|
9
|
+
require 'etc'
|
|
10
|
+
require 'uri'
|
|
11
|
+
require 'shellwords'
|
|
12
|
+
|
|
13
|
+
module PWN
|
|
14
|
+
module AI
|
|
15
|
+
module Agent
|
|
16
|
+
# PWN::AI::Agent::Extrospection is the outward-facing counterpart to
|
|
17
|
+
# PWN::AI::Agent::Learning (introspection). Where Learning/Metrics
|
|
18
|
+
# look INWARD at the agent's own tool telemetry, task outcomes and
|
|
19
|
+
# session transcripts, Extrospection looks OUTWARD at the world the
|
|
20
|
+
# agent operates in: host state, toolchain versions, network posture,
|
|
21
|
+
# repo drift, and external threat-intel (CVE / Exploit-DB / ATT&CK).
|
|
22
|
+
#
|
|
23
|
+
# Together they close BOTH halves of the pwn-ai feedback loop:
|
|
24
|
+
#
|
|
25
|
+
# INTROSPECTIVE (self) EXTROSPECTIVE (world)
|
|
26
|
+
# ---------------------- -------------------------------------
|
|
27
|
+
# Metrics.record Extrospection.snapshot (host probe)
|
|
28
|
+
# Learning.note_outcome Extrospection.observe (recon fact)
|
|
29
|
+
# Learning.reflect Extrospection.drift (env delta)
|
|
30
|
+
# Learning.stats Extrospection.intel (CVE/EDB)
|
|
31
|
+
# Extrospection.correlate (self x world)
|
|
32
|
+
#
|
|
33
|
+
# PromptBuilder re-injects Extrospection.to_context on every turn so
|
|
34
|
+
# the model gains situational awareness of what changed on THIS host
|
|
35
|
+
# between sessions ("kernel upgraded", "nmap now missing", "port 8080
|
|
36
|
+
# newly listening", "CVE-2026-XXXX matches installed openssl") and
|
|
37
|
+
# can correlate that drift against introspective failures.
|
|
38
|
+
#
|
|
39
|
+
# Everything is file-backed under ~/.pwn/extrospection.json so it
|
|
40
|
+
# survives across REPL restarts and is shared by every future session.
|
|
41
|
+
module Extrospection
|
|
42
|
+
EXTRO_FILE = File.join(Dir.home, '.pwn', 'extrospection.json')
|
|
43
|
+
MAX_OBSERVATIONS = 500
|
|
44
|
+
PROBE_BINS = %w[nmap curl git ruby python3 gcc msfconsole sqlmap burpsuite zaproxy openssl docker].freeze
|
|
45
|
+
|
|
46
|
+
# Supported Method Parameters::
|
|
47
|
+
# store = PWN::AI::Agent::Extrospection.load
|
|
48
|
+
|
|
49
|
+
public_class_method def self.load
|
|
50
|
+
FileUtils.mkdir_p(File.dirname(EXTRO_FILE))
|
|
51
|
+
return { snapshot: {}, previous: {}, observations: [], updated_at: nil } unless File.exist?(EXTRO_FILE)
|
|
52
|
+
|
|
53
|
+
JSON.parse(File.read(EXTRO_FILE), symbolize_names: true)
|
|
54
|
+
rescue StandardError
|
|
55
|
+
{ snapshot: {}, previous: {}, observations: [], updated_at: nil }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Supported Method Parameters::
|
|
59
|
+
# PWN::AI::Agent::Extrospection.save(
|
|
60
|
+
# store: 'required - Hash returned by .load / mutated in place'
|
|
61
|
+
# )
|
|
62
|
+
|
|
63
|
+
public_class_method def self.save(opts = {})
|
|
64
|
+
store = opts[:store] ||= { snapshot: {}, previous: {}, observations: [] }
|
|
65
|
+
store[:updated_at] = Time.now.utc.iso8601
|
|
66
|
+
FileUtils.mkdir_p(File.dirname(EXTRO_FILE))
|
|
67
|
+
File.write(EXTRO_FILE, JSON.pretty_generate(store))
|
|
68
|
+
store
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Supported Method Parameters::
|
|
72
|
+
# snap = PWN::AI::Agent::Extrospection.snapshot(
|
|
73
|
+
# persist: 'optional - Boolean, write snapshot to disk & rotate previous (default true)',
|
|
74
|
+
# sections: 'optional - Array subset of [:host, :net, :toolchain, :repo, :env] (default all)'
|
|
75
|
+
# )
|
|
76
|
+
#
|
|
77
|
+
# Captures a fingerprint of the OUTSIDE world. When persist:true the
|
|
78
|
+
# prior snapshot is rotated into :previous so .drift can diff them.
|
|
79
|
+
|
|
80
|
+
public_class_method def self.snapshot(opts = {})
|
|
81
|
+
persist = if opts.key?(:persist)
|
|
82
|
+
opts[:persist] ? true : false
|
|
83
|
+
else
|
|
84
|
+
true
|
|
85
|
+
end
|
|
86
|
+
sections = Array(opts[:sections]).map(&:to_sym)
|
|
87
|
+
sections = %i[host net toolchain repo env] if sections.empty?
|
|
88
|
+
|
|
89
|
+
snap = {}
|
|
90
|
+
snap[:host] = probe_host if sections.include?(:host)
|
|
91
|
+
snap[:net] = probe_net if sections.include?(:net)
|
|
92
|
+
snap[:toolchain] = probe_toolchain if sections.include?(:toolchain)
|
|
93
|
+
snap[:repo] = probe_repo if sections.include?(:repo)
|
|
94
|
+
snap[:env] = probe_env if sections.include?(:env)
|
|
95
|
+
snap[:captured_at] = Time.now.utc.iso8601
|
|
96
|
+
snap[:fingerprint] = Digest::SHA256.hexdigest(JSON.generate(snap.except(:captured_at)))[0, 16]
|
|
97
|
+
|
|
98
|
+
if persist
|
|
99
|
+
store = load
|
|
100
|
+
store[:previous] = store[:snapshot] || {}
|
|
101
|
+
store[:snapshot] = snap
|
|
102
|
+
save(store: store)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
drift = compute_drift(before: load[:previous] || {}, after: snap)
|
|
106
|
+
{ snapshot: snap, drift: drift, persisted: persist }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Supported Method Parameters::
|
|
110
|
+
# obs = PWN::AI::Agent::Extrospection.observe(
|
|
111
|
+
# source: 'required - where the observation came from (nmap, shodan, burp, cve, human, ...)',
|
|
112
|
+
# data: 'required - the observation payload (String or Hash)',
|
|
113
|
+
# category: 'optional - :recon, :vuln, :intel, :target, :network, :misc (default :misc)',
|
|
114
|
+
# target: 'optional - host/ip/url/asset the observation is about',
|
|
115
|
+
# tags: 'optional - Array of String labels',
|
|
116
|
+
# ttl: 'optional - seconds until this observation is considered stale (default nil = forever)'
|
|
117
|
+
# )
|
|
118
|
+
#
|
|
119
|
+
# Records a fact about the OUTSIDE world (as opposed to
|
|
120
|
+
# Learning.note_outcome which records a fact about the agent's own
|
|
121
|
+
# behaviour). Observations are re-injected via .to_context so recon
|
|
122
|
+
# findings and threat-intel persist across sessions.
|
|
123
|
+
|
|
124
|
+
public_class_method def self.observe(opts = {})
|
|
125
|
+
source = opts[:source].to_s
|
|
126
|
+
data = opts[:data]
|
|
127
|
+
raise 'ERROR: source is required' if source.strip.empty?
|
|
128
|
+
raise 'ERROR: data is required' if data.to_s.strip.empty?
|
|
129
|
+
|
|
130
|
+
entry = {
|
|
131
|
+
id: Digest::SHA256.hexdigest("#{source}-#{data}-#{Time.now.to_f}")[0, 12],
|
|
132
|
+
source: source,
|
|
133
|
+
category: (opts[:category] || :misc).to_s,
|
|
134
|
+
target: opts[:target].to_s.empty? ? nil : opts[:target].to_s,
|
|
135
|
+
data: data.is_a?(String) ? data[0, 4_000] : data,
|
|
136
|
+
tags: Array(opts[:tags]).map(&:to_s),
|
|
137
|
+
ttl: opts[:ttl]&.to_i,
|
|
138
|
+
timestamp: Time.now.utc.iso8601
|
|
139
|
+
}
|
|
140
|
+
store = load
|
|
141
|
+
store[:observations] ||= []
|
|
142
|
+
store[:observations] << entry
|
|
143
|
+
store[:observations].shift(store[:observations].length - MAX_OBSERVATIONS) if store[:observations].length > MAX_OBSERVATIONS
|
|
144
|
+
save(store: store)
|
|
145
|
+
entry
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Supported Method Parameters::
|
|
149
|
+
# rows = PWN::AI::Agent::Extrospection.observations(
|
|
150
|
+
# limit: 'optional - max entries newest-first (default 50)',
|
|
151
|
+
# source: 'optional - filter by source substring',
|
|
152
|
+
# category: 'optional - filter by category',
|
|
153
|
+
# target: 'optional - filter by target substring',
|
|
154
|
+
# tag: 'optional - filter by tag substring',
|
|
155
|
+
# fresh_only: 'optional - drop entries whose ttl has expired (default false)'
|
|
156
|
+
# )
|
|
157
|
+
|
|
158
|
+
public_class_method def self.observations(opts = {})
|
|
159
|
+
limit = opts[:limit] || 50
|
|
160
|
+
src = opts[:source].to_s.downcase
|
|
161
|
+
cat = opts[:category].to_s.downcase
|
|
162
|
+
tgt = opts[:target].to_s.downcase
|
|
163
|
+
tag = opts[:tag].to_s.downcase
|
|
164
|
+
fresh = opts[:fresh_only] ? true : false
|
|
165
|
+
now = Time.now.utc
|
|
166
|
+
|
|
167
|
+
rows = Array(load[:observations])
|
|
168
|
+
rows = rows.reject do |o|
|
|
169
|
+
next true if fresh && o[:ttl] && (Time.parse(o[:timestamp].to_s) + o[:ttl].to_i) < now
|
|
170
|
+
|
|
171
|
+
(src.empty? ? false : !o[:source].to_s.downcase.include?(src)) ||
|
|
172
|
+
(cat.empty? ? false : o[:category].to_s.downcase != cat) ||
|
|
173
|
+
(tgt.empty? ? false : !o[:target].to_s.downcase.include?(tgt)) ||
|
|
174
|
+
(tag.empty? ? false : Array(o[:tags]).none? { |t| t.to_s.downcase.include?(tag) })
|
|
175
|
+
rescue StandardError
|
|
176
|
+
false
|
|
177
|
+
end
|
|
178
|
+
rows.reverse.first(limit)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Supported Method Parameters::
|
|
182
|
+
# delta = PWN::AI::Agent::Extrospection.drift(
|
|
183
|
+
# live: 'optional - probe host NOW and diff vs stored snapshot (default true). When false, diff stored :snapshot vs stored :previous.'
|
|
184
|
+
# )
|
|
185
|
+
|
|
186
|
+
public_class_method def self.drift(opts = {})
|
|
187
|
+
live = if opts.key?(:live)
|
|
188
|
+
opts[:live] ? true : false
|
|
189
|
+
else
|
|
190
|
+
true
|
|
191
|
+
end
|
|
192
|
+
store = load
|
|
193
|
+
if live
|
|
194
|
+
after = snapshot(persist: false)[:snapshot]
|
|
195
|
+
before = store[:snapshot] || {}
|
|
196
|
+
else
|
|
197
|
+
after = store[:snapshot] || {}
|
|
198
|
+
before = store[:previous] || {}
|
|
199
|
+
end
|
|
200
|
+
compute_drift(before: before, after: after)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Supported Method Parameters::
|
|
204
|
+
# hits = PWN::AI::Agent::Extrospection.intel(
|
|
205
|
+
# query: 'required - keyword / product / CVE id to search external threat-intel for',
|
|
206
|
+
# feeds: 'optional - Array subset of [:nvd, :circl, :exploitdb] (default all)',
|
|
207
|
+
# limit: 'optional - max results per feed (default 5)',
|
|
208
|
+
# record: 'optional - also persist each hit as an observation (default false)'
|
|
209
|
+
# )
|
|
210
|
+
#
|
|
211
|
+
# Best-effort external lookups. Network / API failures degrade to
|
|
212
|
+
# an empty result for that feed rather than raising, so learning
|
|
213
|
+
# never breaks the primary loop when offline.
|
|
214
|
+
|
|
215
|
+
public_class_method def self.intel(opts = {})
|
|
216
|
+
query = opts[:query].to_s.strip
|
|
217
|
+
raise 'ERROR: query is required' if query.empty?
|
|
218
|
+
|
|
219
|
+
feeds = Array(opts[:feeds]).map(&:to_sym)
|
|
220
|
+
feeds = %i[nvd circl exploitdb] if feeds.empty?
|
|
221
|
+
limit = opts[:limit] || 5
|
|
222
|
+
record = opts[:record] ? true : false
|
|
223
|
+
|
|
224
|
+
results = {}
|
|
225
|
+
results[:nvd] = intel_nvd(query: query, limit: limit) if feeds.include?(:nvd)
|
|
226
|
+
results[:circl] = intel_circl(query: query, limit: limit) if feeds.include?(:circl)
|
|
227
|
+
results[:exploitdb] = intel_exploitdb(query: query, limit: limit) if feeds.include?(:exploitdb)
|
|
228
|
+
|
|
229
|
+
if record
|
|
230
|
+
results.each do |feed, hits|
|
|
231
|
+
Array(hits).each do |h|
|
|
232
|
+
observe(source: feed.to_s, category: :intel, data: h, tags: ['intel', query], target: h.is_a?(Hash) ? h[:id].to_s : nil)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
{ query: query, feeds: feeds, results: results, total: results.values.flatten.compact.length }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Supported Method Parameters::
|
|
240
|
+
# findings = PWN::AI::Agent::Extrospection.correlate(
|
|
241
|
+
# limit: 'optional - max findings returned (default 10)'
|
|
242
|
+
# )
|
|
243
|
+
#
|
|
244
|
+
# THE join between introspection and extrospection. Cross-references:
|
|
245
|
+
# * Metrics tools with success_rate < 50 % vs toolchain drift / missing bins
|
|
246
|
+
# * Learning failures vs host / net drift on the same day
|
|
247
|
+
# * Observations tagged :vuln / :intel vs installed package versions
|
|
248
|
+
# Emits human-readable, actionable findings the model can reason on.
|
|
249
|
+
|
|
250
|
+
public_class_method def self.correlate(opts = {})
|
|
251
|
+
limit = opts[:limit] || 10
|
|
252
|
+
findings = []
|
|
253
|
+
delta = drift(live: false)
|
|
254
|
+
snap = load[:snapshot] || {}
|
|
255
|
+
|
|
256
|
+
# 1) failing tools whose backing binary vanished / changed version
|
|
257
|
+
if defined?(Metrics)
|
|
258
|
+
Metrics.summary(limit: 50).each do |m|
|
|
259
|
+
next unless m[:success_rate] < 0.5 && m[:calls] > 2
|
|
260
|
+
|
|
261
|
+
bin = m[:name].to_s.split('_').first
|
|
262
|
+
tc = Array(delta[:changed]).find { |c| c[:path].to_s.include?("toolchain.#{bin}") }
|
|
263
|
+
miss = snap.dig(:toolchain, bin.to_sym).to_s.empty?
|
|
264
|
+
next unless tc || miss
|
|
265
|
+
|
|
266
|
+
findings << { kind: :tool_env_mismatch, tool: m[:name], success_rate: m[:success_rate], evidence: tc || "binary '#{bin}' not found in PATH", advice: "Re-verify `which #{bin}` / reinstall before relying on #{m[:name]}." }
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# 2) introspective failures coinciding with extrospective drift
|
|
271
|
+
if defined?(Learning)
|
|
272
|
+
Learning.outcomes(limit: 30, success: false).each do |o|
|
|
273
|
+
day = o[:timestamp].to_s[0, 10]
|
|
274
|
+
hit = Array(delta[:changed]).find { |c| c[:after].to_s.include?(day) || c[:path].to_s.match?(/kernel|repo|net/) }
|
|
275
|
+
findings << { kind: :failure_during_drift, task: o[:task], on: day, drift: hit, advice: 'Environment changed around this failure — re-test under current snapshot before trusting the negative result.' } if hit
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# 3) intel observations matching installed components
|
|
280
|
+
pkgs = snap[:toolchain] || {}
|
|
281
|
+
observations(category: 'intel', limit: 100).each do |ob|
|
|
282
|
+
blob = ob[:data].to_s.downcase
|
|
283
|
+
pkgs.each do |bin, ver|
|
|
284
|
+
next if ver.to_s.empty?
|
|
285
|
+
next unless blob.include?(bin.to_s.downcase)
|
|
286
|
+
|
|
287
|
+
findings << { kind: :intel_matches_host, component: bin, installed: ver, intel: ob[:data], source: ob[:source], advice: "Review #{ob[:source]} advisory for #{bin} #{ver} on this host." }
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# 4) raw drift as low-priority findings when nothing else matched
|
|
292
|
+
Array(delta[:added]).first(5).each { |c| findings << { kind: :env_added, detail: c } } if findings.empty?
|
|
293
|
+
Array(delta[:removed]).first(5).each { |c| findings << { kind: :env_removed, detail: c } } if findings.empty?
|
|
294
|
+
|
|
295
|
+
findings.uniq.first(limit)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Supported Method Parameters::
|
|
299
|
+
# ctx = PWN::AI::Agent::Extrospection.to_context(
|
|
300
|
+
# drift_limit: 'optional - max drift lines (default 4)',
|
|
301
|
+
# obs_limit: 'optional - max observation lines (default 4)'
|
|
302
|
+
# )
|
|
303
|
+
|
|
304
|
+
public_class_method def self.to_context(opts = {})
|
|
305
|
+
dlim = opts[:drift_limit] || 4
|
|
306
|
+
olim = opts[:obs_limit] || 4
|
|
307
|
+
store = load
|
|
308
|
+
return '' if (store[:snapshot] || {}).empty? && Array(store[:observations]).empty?
|
|
309
|
+
|
|
310
|
+
snap = store[:snapshot] || {}
|
|
311
|
+
delta = compute_drift(before: store[:previous] || {}, after: snap)
|
|
312
|
+
lines = []
|
|
313
|
+
host = snap[:host] || {}
|
|
314
|
+
lines << " host_fp : #{snap[:fingerprint]} (#{host[:os]} #{host[:kernel]}, #{host[:arch]})" if snap[:fingerprint]
|
|
315
|
+
lines << " captured : #{snap[:captured_at]}" if snap[:captured_at]
|
|
316
|
+
|
|
317
|
+
ch = Array(delta[:changed]).first(dlim).map { |c| " ~ #{c[:path]}: #{c[:before].to_s[0, 40]} -> #{c[:after].to_s[0, 40]}" }
|
|
318
|
+
ad = Array(delta[:added]).first(dlim).map { |c| " + #{c[:path]}: #{c[:after].to_s[0, 60]}" }
|
|
319
|
+
rm = Array(delta[:removed]).first(dlim).map { |c| " - #{c[:path]}: #{c[:before].to_s[0, 60]}" }
|
|
320
|
+
unless ch.empty? && ad.empty? && rm.empty?
|
|
321
|
+
lines << ' drift :'
|
|
322
|
+
lines.concat(ch).concat(ad).concat(rm)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
obs = observations(limit: olim, fresh_only: true)
|
|
326
|
+
unless obs.empty?
|
|
327
|
+
lines << ' observed :'
|
|
328
|
+
obs.each { |o| lines << " * [#{o[:category]}/#{o[:source]}] #{"#{o[:target]} — " if o[:target]}#{o[:data].to_s.gsub(/\s+/, ' ')[0, 120]}" }
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
"EXTROSPECTION (world-state; correlate with introspective failures)\n#{lines.join("\n")}\n\n"
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Supported Method Parameters::
|
|
335
|
+
# stats = PWN::AI::Agent::Extrospection.stats
|
|
336
|
+
|
|
337
|
+
public_class_method def self.stats
|
|
338
|
+
store = load
|
|
339
|
+
snap = store[:snapshot] || {}
|
|
340
|
+
delta = compute_drift(before: store[:previous] || {}, after: snap)
|
|
341
|
+
{
|
|
342
|
+
snapshot_captured_at: snap[:captured_at],
|
|
343
|
+
snapshot_fingerprint: snap[:fingerprint],
|
|
344
|
+
observations: Array(store[:observations]).length,
|
|
345
|
+
drift_changed: Array(delta[:changed]).length,
|
|
346
|
+
drift_added: Array(delta[:added]).length,
|
|
347
|
+
drift_removed: Array(delta[:removed]).length,
|
|
348
|
+
toolchain_bins: (snap[:toolchain] || {}).count { |_, v| !v.to_s.empty? },
|
|
349
|
+
listening_ports: Array(snap.dig(:net, :listening)).length
|
|
350
|
+
}
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Supported Method Parameters::
|
|
354
|
+
# PWN::AI::Agent::Extrospection.auto_extrospect(
|
|
355
|
+
# session_id: 'optional - id of the just-completed session (for tagging)'
|
|
356
|
+
# )
|
|
357
|
+
#
|
|
358
|
+
# Called by Learning.auto_reflect when
|
|
359
|
+
# PWN::Env[:ai][:agent][:auto_extrospect] is truthy. Captures a fresh
|
|
360
|
+
# snapshot and, if drift is non-trivial, records it as an observation
|
|
361
|
+
# and a PWN::Memory :env fact so the NEXT session's system prompt
|
|
362
|
+
# already knows the world moved. Never raises.
|
|
363
|
+
|
|
364
|
+
public_class_method def self.auto_extrospect(opts = {})
|
|
365
|
+
sid = opts[:session_id]
|
|
366
|
+
return unless auto_extrospect_enabled?
|
|
367
|
+
|
|
368
|
+
res = snapshot(persist: true)
|
|
369
|
+
delta = res[:drift]
|
|
370
|
+
moved = Array(delta[:changed]).length + Array(delta[:added]).length + Array(delta[:removed]).length
|
|
371
|
+
return res if moved.zero?
|
|
372
|
+
|
|
373
|
+
summary = summarise_drift(delta: delta)
|
|
374
|
+
observe(source: 'auto_extrospect', category: :env, data: summary, tags: ['drift', sid.to_s].reject(&:empty?))
|
|
375
|
+
if defined?(PWN::Memory)
|
|
376
|
+
key = :"extro_drift_#{res[:snapshot][:fingerprint]}"
|
|
377
|
+
PWN::Memory.remember(key: key, value: "ENV DRIFT #{res[:snapshot][:captured_at]}: #{summary[0, 260]}", category: :env)
|
|
378
|
+
end
|
|
379
|
+
res
|
|
380
|
+
rescue StandardError => e
|
|
381
|
+
warn "[pwn-ai/extrospection] auto_extrospect swallowed: #{e.class}: #{e.message}"
|
|
382
|
+
nil
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Supported Method Parameters::
|
|
386
|
+
# PWN::AI::Agent::Extrospection.reset
|
|
387
|
+
|
|
388
|
+
public_class_method def self.reset
|
|
389
|
+
FileUtils.rm_f(EXTRO_FILE)
|
|
390
|
+
{ cleared: true }
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# -------------------------------------------------------------
|
|
394
|
+
# privates
|
|
395
|
+
# -------------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
private_class_method def self.auto_extrospect_enabled?
|
|
398
|
+
return false unless defined?(PWN::Env) && PWN::Env.is_a?(Hash)
|
|
399
|
+
|
|
400
|
+
PWN::Env.dig(:ai, :agent, :auto_extrospect) ? true : false
|
|
401
|
+
rescue StandardError
|
|
402
|
+
false
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
private_class_method def self.sh(opts = {})
|
|
406
|
+
cmd = opts[:cmd].to_s
|
|
407
|
+
return '' if cmd.empty?
|
|
408
|
+
|
|
409
|
+
out, = Open3.capture2e(cmd)
|
|
410
|
+
out.to_s.strip
|
|
411
|
+
rescue StandardError
|
|
412
|
+
''
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
private_class_method def self.probe_host
|
|
416
|
+
{
|
|
417
|
+
hostname: begin
|
|
418
|
+
Socket.gethostname
|
|
419
|
+
rescue StandardError
|
|
420
|
+
''
|
|
421
|
+
end,
|
|
422
|
+
os: sh(cmd: 'uname -s'),
|
|
423
|
+
kernel: sh(cmd: 'uname -r'),
|
|
424
|
+
arch: sh(cmd: 'uname -m'),
|
|
425
|
+
distro: begin
|
|
426
|
+
File.read('/etc/os-release')[/PRETTY_NAME="?([^"\n]+)/, 1]
|
|
427
|
+
rescue StandardError
|
|
428
|
+
nil
|
|
429
|
+
end,
|
|
430
|
+
uptime_s: begin
|
|
431
|
+
File.read('/proc/uptime').split.first.to_i
|
|
432
|
+
rescue StandardError
|
|
433
|
+
nil
|
|
434
|
+
end,
|
|
435
|
+
user: ENV.fetch('USER', ''),
|
|
436
|
+
cpu_count: (Etc.respond_to?(:nprocessors) ? Etc.nprocessors : nil)
|
|
437
|
+
}
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
private_class_method def self.probe_net
|
|
441
|
+
listen = sh(cmd: 'ss -tlnH 2>/dev/null').lines.map { |l| l.split[3].to_s.split(':').last }.compact.uniq.sort
|
|
442
|
+
ifaces = sh(cmd: 'ip -o -4 addr show 2>/dev/null').lines.map do |l|
|
|
443
|
+
p = l.split
|
|
444
|
+
{ if: p[1], addr: p[3] }
|
|
445
|
+
end
|
|
446
|
+
route = sh(cmd: 'ip route show default 2>/dev/null').lines.first.to_s.strip
|
|
447
|
+
{ listening: listen, interfaces: ifaces, default_route: route }
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
private_class_method def self.probe_toolchain
|
|
451
|
+
PROBE_BINS.each_with_object({}) do |b, h|
|
|
452
|
+
path = sh(cmd: "which #{b} 2>/dev/null")
|
|
453
|
+
h[b.to_sym] = path.empty? ? '' : "#{path} #{sh(cmd: "#{b} --version 2>/dev/null").lines.first.to_s.strip[0, 80]}"
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
private_class_method def self.probe_repo
|
|
458
|
+
root = defined?(PWN::ROOT) ? PWN::ROOT.to_s : Dir.pwd
|
|
459
|
+
return {} unless Dir.exist?(File.join(root, '.git'))
|
|
460
|
+
|
|
461
|
+
{
|
|
462
|
+
root: root,
|
|
463
|
+
head: sh(cmd: "git -C #{root} rev-parse --short HEAD"),
|
|
464
|
+
branch: sh(cmd: "git -C #{root} rev-parse --abbrev-ref HEAD"),
|
|
465
|
+
dirty: !sh(cmd: "git -C #{root} status --porcelain").empty?,
|
|
466
|
+
pwn_version: (defined?(PWN::VERSION) ? PWN::VERSION : nil)
|
|
467
|
+
}
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private_class_method def self.probe_env
|
|
471
|
+
{
|
|
472
|
+
cwd: Dir.pwd,
|
|
473
|
+
ruby: RUBY_VERSION,
|
|
474
|
+
path_entries: ENV.fetch('PATH', '').split(':').length,
|
|
475
|
+
ai_engine: (PWN::Env.dig(:ai, :active) if defined?(PWN::Env) && PWN::Env.is_a?(Hash))
|
|
476
|
+
}
|
|
477
|
+
rescue StandardError
|
|
478
|
+
{ cwd: Dir.pwd, ruby: RUBY_VERSION }
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
private_class_method def self.flatten(opts = {})
|
|
482
|
+
hash = opts[:hash] || {}
|
|
483
|
+
prefix = opts[:prefix].to_s
|
|
484
|
+
out = opts[:out] || {}
|
|
485
|
+
hash.each do |k, v|
|
|
486
|
+
path = prefix.empty? ? k.to_s : "#{prefix}.#{k}"
|
|
487
|
+
case v
|
|
488
|
+
when Hash then flatten(hash: v, prefix: path, out: out)
|
|
489
|
+
when Array then out[path] = v.map(&:to_s).sort
|
|
490
|
+
else out[path] = v
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
out
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
private_class_method def self.compute_drift(opts = {})
|
|
497
|
+
before = flatten(hash: (opts[:before] || {}).except(:captured_at, :fingerprint))
|
|
498
|
+
after = flatten(hash: (opts[:after] || {}).except(:captured_at, :fingerprint))
|
|
499
|
+
changed = []
|
|
500
|
+
added = []
|
|
501
|
+
removed = []
|
|
502
|
+
(before.keys | after.keys).each do |k|
|
|
503
|
+
next if k =~ /uptime_s$/
|
|
504
|
+
|
|
505
|
+
b = before[k]
|
|
506
|
+
a = after[k]
|
|
507
|
+
if b.nil? && !a.nil?
|
|
508
|
+
added << { path: k, after: a }
|
|
509
|
+
elsif a.nil? && !b.nil?
|
|
510
|
+
removed << { path: k, before: b }
|
|
511
|
+
elsif a != b
|
|
512
|
+
changed << { path: k, before: b, after: a }
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
{ changed: changed, added: added, removed: removed }
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
private_class_method def self.summarise_drift(opts = {})
|
|
519
|
+
d = opts[:delta] || {}
|
|
520
|
+
parts = Array(d[:changed]).first(5).map { |c| "~#{c[:path]}:#{c[:before].to_s[0, 30]}->#{c[:after].to_s[0, 30]}" }
|
|
521
|
+
Array(d[:added]).first(5).each { |c| parts << "+#{c[:path]}" }
|
|
522
|
+
Array(d[:removed]).first(5).each { |c| parts << "-#{c[:path]}" }
|
|
523
|
+
parts.empty? ? '(no drift)' : parts.join(' | ')
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
private_class_method def self.http_get_json(opts = {})
|
|
527
|
+
url = opts[:url].to_s
|
|
528
|
+
return nil if url.empty?
|
|
529
|
+
|
|
530
|
+
require 'rest-client'
|
|
531
|
+
resp = RestClient::Request.execute(method: :get, url: url, timeout: 8, open_timeout: 4, headers: { accept: :json, user_agent: 'pwn-ai-extrospection' })
|
|
532
|
+
JSON.parse(resp.body, symbolize_names: true)
|
|
533
|
+
rescue StandardError
|
|
534
|
+
nil
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
private_class_method def self.intel_nvd(opts = {})
|
|
538
|
+
q = opts[:query].to_s
|
|
539
|
+
limit = opts[:limit] || 5
|
|
540
|
+
url = "https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=#{URI.encode_www_form_component(q)}&resultsPerPage=#{limit}"
|
|
541
|
+
body = http_get_json(url: url)
|
|
542
|
+
return [] unless body
|
|
543
|
+
|
|
544
|
+
Array(body[:vulnerabilities]).first(limit).map do |v|
|
|
545
|
+
c = v[:cve] || {}
|
|
546
|
+
{ id: c[:id], published: c[:published], desc: Array(c[:descriptions])&.first&.dig(:value).to_s[0, 300], cvss: c.dig(:metrics, :cvssMetricV31, 0, :cvssData, :baseScore) }
|
|
547
|
+
end
|
|
548
|
+
rescue StandardError
|
|
549
|
+
[]
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
private_class_method def self.intel_circl(opts = {})
|
|
553
|
+
q = opts[:query].to_s
|
|
554
|
+
limit = opts[:limit] || 5
|
|
555
|
+
body = if q =~ /^CVE-\d{4}-\d+$/i
|
|
556
|
+
r = http_get_json(url: "https://cve.circl.lu/api/cve/#{q.upcase}")
|
|
557
|
+
r ? [r] : []
|
|
558
|
+
else
|
|
559
|
+
http_get_json(url: "https://cve.circl.lu/api/search/#{URI.encode_www_form_component(q)}") || []
|
|
560
|
+
end
|
|
561
|
+
Array(body).flatten.first(limit).map do |c|
|
|
562
|
+
{ id: c[:id], cvss: c[:cvss], summary: c[:summary].to_s[0, 300], refs: Array(c[:references]).first(3) }
|
|
563
|
+
end
|
|
564
|
+
rescue StandardError
|
|
565
|
+
[]
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
private_class_method def self.intel_exploitdb(opts = {})
|
|
569
|
+
q = opts[:query].to_s
|
|
570
|
+
limit = opts[:limit] || 5
|
|
571
|
+
bin = sh(cmd: 'which searchsploit 2>/dev/null')
|
|
572
|
+
return [] if bin.empty?
|
|
573
|
+
|
|
574
|
+
raw = sh(cmd: "searchsploit --json #{q.shellescape} 2>/dev/null")
|
|
575
|
+
j = begin
|
|
576
|
+
JSON.parse(raw, symbolize_names: true)
|
|
577
|
+
rescue StandardError
|
|
578
|
+
nil
|
|
579
|
+
end
|
|
580
|
+
return [] unless j
|
|
581
|
+
|
|
582
|
+
Array(j[:RESULTS_EXPLOIT]).first(limit).map { |e| { id: "EDB-#{e[:'EDB-ID']}", title: e[:Title].to_s[0, 200], path: e[:Path] } }
|
|
583
|
+
rescue StandardError
|
|
584
|
+
[]
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
588
|
+
|
|
589
|
+
public_class_method def self.authors
|
|
590
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# Display Usage for this Module
|
|
594
|
+
|
|
595
|
+
public_class_method def self.help
|
|
596
|
+
puts <<~USAGE
|
|
597
|
+
USAGE:
|
|
598
|
+
PWN::AI::Agent::Extrospection.snapshot # probe host, persist, return {snapshot:, drift:}
|
|
599
|
+
PWN::AI::Agent::Extrospection.drift(live: true) # what changed vs last snapshot
|
|
600
|
+
PWN::AI::Agent::Extrospection.observe(source: 'nmap', category: :recon, target: '10.0.0.5', data: '22/tcp open ssh 9.6')
|
|
601
|
+
PWN::AI::Agent::Extrospection.observations(category: 'recon', target: '10.0.0.5')
|
|
602
|
+
PWN::AI::Agent::Extrospection.intel(query: 'openssl 3.0', record: true)
|
|
603
|
+
PWN::AI::Agent::Extrospection.correlate # introspection x extrospection findings
|
|
604
|
+
PWN::AI::Agent::Extrospection.to_context # injected by PromptBuilder
|
|
605
|
+
PWN::AI::Agent::Extrospection.stats
|
|
606
|
+
PWN::AI::Agent::Extrospection.auto_extrospect(session_id: sid) # called from Learning.auto_reflect
|
|
607
|
+
PWN::AI::Agent::Extrospection.reset
|
|
608
|
+
|
|
609
|
+
Enable end-of-run auto-extrospection with:
|
|
610
|
+
PWN::Env[:ai][:agent][:auto_extrospect] = true
|
|
611
|
+
|
|
612
|
+
#{self}.authors
|
|
613
|
+
USAGE
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
end
|
data/lib/pwn/ai/agent/gqrx.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module PWN
|
|
4
4
|
module AI
|
|
5
5
|
module Agent
|
|
6
|
-
# This module is an AI agent designed to analyze signal data captured by a software-defined-radio using GQRX. It uses the PWN::AI::Introspection.reflect_on method to analyze the signal data and provide insights based on the location where the data was captured. The agent can determine if the frequency is licensed or unlicensed based on FCC records and provide relevant information about the transmission. This module is useful for security professionals, researchers, and hobbyists interested in analyzing radio signals and understanding their context.
|
|
6
|
+
# This module is an AI agent designed to analyze signal data captured by a software-defined-radio using GQRX. It uses the PWN::AI::Agent::Introspection.reflect_on method to analyze the signal data and provide insights based on the location where the data was captured. The agent can determine if the frequency is licensed or unlicensed based on FCC records and provide relevant information about the transmission. This module is useful for security professionals, researchers, and hobbyists interested in analyzing radio signals and understanding their context.
|
|
7
7
|
module GQRX
|
|
8
8
|
# Supported Method Parameters::
|
|
9
9
|
# ai_analysis = PWN::AI::Agent::GQRX.analyze(
|
|
@@ -20,7 +20,7 @@ module PWN
|
|
|
20
20
|
|
|
21
21
|
system_role_content = "Analyze signal data captured by a software-defined-radio using GQRX at the following location: #{location}. Respond with just FCC information about the transmission if available. If the frequency is unlicensed or not found in FCC records, state that clearly. Be clear and concise in your analysis."
|
|
22
22
|
|
|
23
|
-
PWN::AI::Introspection.reflect_on(
|
|
23
|
+
PWN::AI::Agent::Introspection.reflect_on(
|
|
24
24
|
system_role_content: system_role_content,
|
|
25
25
|
request: request,
|
|
26
26
|
suppress_pii_warning: true
|