pwn 0.5.620 → 0.5.622

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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +1 -1
  4. data/documentation/Agent-Tool-Registry.md +3 -3
  5. data/documentation/Configuration.md +292 -47
  6. data/documentation/Cron.md +14 -1
  7. data/documentation/Diagrams.md +1 -1
  8. data/documentation/Extrospection.md +71 -5
  9. data/documentation/Home.md +2 -2
  10. data/documentation/How-PWN-Works.md +1 -1
  11. data/documentation/Mistakes.md +2 -1
  12. data/documentation/Persistence.md +2 -1
  13. data/documentation/Skills-Memory-Learning.md +3 -2
  14. data/documentation/Transparent-Browser.md +18 -1
  15. data/documentation/diagrams/agent-tool-registry.svg +55 -54
  16. data/documentation/diagrams/ai-integration-tool-calling.svg +24 -24
  17. data/documentation/diagrams/aws-cloud-security.svg +25 -25
  18. data/documentation/diagrams/burp-vs-zap-preference.svg +12 -12
  19. data/documentation/diagrams/code-scanning-sast.svg +25 -25
  20. data/documentation/diagrams/cron-scheduling.svg +23 -23
  21. data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
  22. data/documentation/diagrams/dot/extrospection-world-awareness.dot +29 -8
  23. data/documentation/diagrams/dot/memory-skills-detailed.dot +4 -0
  24. data/documentation/diagrams/dot/overall-pwn-architecture.dot +2 -2
  25. data/documentation/diagrams/dot/persistence-filesystem.dot +2 -1
  26. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +11 -5
  27. data/documentation/diagrams/driver-framework.svg +13 -13
  28. data/documentation/diagrams/extrospection-world-awareness.svg +229 -99
  29. data/documentation/diagrams/fuzzing-workflow.svg +24 -24
  30. data/documentation/diagrams/hardware-hacking.svg +18 -18
  31. data/documentation/diagrams/history-to-drivers.svg +18 -18
  32. data/documentation/diagrams/memory-skills-detailed.svg +124 -101
  33. data/documentation/diagrams/mistakes-negative-feedback.svg +55 -55
  34. data/documentation/diagrams/network-infra-testing.svg +27 -27
  35. data/documentation/diagrams/overall-pwn-architecture.svg +85 -85
  36. data/documentation/diagrams/penetration-testing-workflow.svg +30 -30
  37. data/documentation/diagrams/persistence-filesystem.svg +94 -85
  38. data/documentation/diagrams/plugin-ecosystem.svg +35 -35
  39. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +226 -180
  40. data/documentation/diagrams/pwn-repl-prototyping.svg +20 -20
  41. data/documentation/diagrams/reporting-pipeline.svg +18 -18
  42. data/documentation/diagrams/reverse-engineering-flow.svg +21 -21
  43. data/documentation/diagrams/sdr-radio-flow.svg +31 -31
  44. data/documentation/diagrams/sessions-cron-automation.svg +18 -18
  45. data/documentation/diagrams/swarm-multi-agent.svg +39 -39
  46. data/documentation/diagrams/web-application-testing.svg +26 -26
  47. data/documentation/diagrams/zero-day-research-flow.svg +25 -25
  48. data/documentation/pwn-ai-Agent.md +4 -4
  49. data/lib/pwn/ai/agent/assembly.rb +1 -1
  50. data/lib/pwn/ai/agent/btc.rb +1 -1
  51. data/lib/pwn/ai/agent/burp_suite.rb +1 -1
  52. data/lib/pwn/ai/agent/extrospection.rb +435 -6
  53. data/lib/pwn/ai/agent/gqrx.rb +2 -2
  54. data/lib/pwn/ai/agent/hacker_one.rb +1 -1
  55. data/lib/pwn/ai/agent/learning.rb +16 -16
  56. data/lib/pwn/ai/agent/loop.rb +1 -1
  57. data/lib/pwn/ai/agent/{introspection.rb → reflect.rb} +12 -12
  58. data/lib/pwn/ai/agent/sast.rb +1 -1
  59. data/lib/pwn/ai/agent/tools/extrospection.rb +89 -4
  60. data/lib/pwn/ai/agent/tools/learning.rb +7 -7
  61. data/lib/pwn/ai/agent/transparent_browser.rb +1 -1
  62. data/lib/pwn/ai/agent/vuln_gen.rb +2 -2
  63. data/lib/pwn/ai/agent.rb +1 -1
  64. data/lib/pwn/ai/open_ai.rb +10 -6
  65. data/lib/pwn/config.rb +5 -4
  66. data/lib/pwn/plugins/burp_suite.rb +2 -2
  67. data/lib/pwn/sast/pom_version.rb +2 -2
  68. data/lib/pwn/sast/test_case_engine.rb +1 -1
  69. data/lib/pwn/sdr/decoder/adsb.rb +14 -33
  70. data/lib/pwn/sdr/decoder/apt.rb +97 -36
  71. data/lib/pwn/sdr/decoder/base.rb +258 -171
  72. data/lib/pwn/sdr/decoder/bluetooth.rb +13 -20
  73. data/lib/pwn/sdr/decoder/dect.rb +13 -27
  74. data/lib/pwn/sdr/decoder/dsp.rb +396 -0
  75. data/lib/pwn/sdr/decoder/flex.rb +177 -210
  76. data/lib/pwn/sdr/decoder/gps.rb +12 -21
  77. data/lib/pwn/sdr/decoder/gsm.rb +21 -73
  78. data/lib/pwn/sdr/decoder/iridium.rb +11 -26
  79. data/lib/pwn/sdr/decoder/lora.rb +16 -23
  80. data/lib/pwn/sdr/decoder/lte.rb +11 -32
  81. data/lib/pwn/sdr/decoder/morse.rb +95 -21
  82. data/lib/pwn/sdr/decoder/p25.rb +19 -16
  83. data/lib/pwn/sdr/decoder/pager.rb +22 -38
  84. data/lib/pwn/sdr/decoder/pocsag.rb +175 -68
  85. data/lib/pwn/sdr/decoder/rfid.rb +24 -33
  86. data/lib/pwn/sdr/decoder/rtl433.rb +21 -30
  87. data/lib/pwn/sdr/decoder/rtty.rb +100 -27
  88. data/lib/pwn/sdr/decoder/wifi.rb +21 -42
  89. data/lib/pwn/sdr/decoder/zigbee.rb +18 -40
  90. data/lib/pwn/sdr/decoder.rb +8 -0
  91. data/lib/pwn/sdr/gqrx.rb +1 -1
  92. data/lib/pwn/version.rb +1 -1
  93. data/spec/lib/pwn/ai/agent/{introspection_spec.rb → reflect_spec.rb} +3 -3
  94. data/spec/lib/pwn/sdr/decoder/dsp_spec.rb +15 -0
  95. data/third_party/pwn_rdoc.jsonl +75 -19
  96. metadata +7 -5
@@ -5,14 +5,14 @@ require 'json'
5
5
  module PWN
6
6
  module AI
7
7
  module Agent
8
- # PWN::AI::Agent::Introspection is the inward-facing counterpart to
8
+ # PWN::AI::Agent::Reflect is the inward-facing counterpart to
9
9
  # PWN::AI::Agent::Extrospection. Where Extrospection looks OUTWARD at
10
10
  # the world the agent operates in (host state, toolchain, network,
11
- # threat-intel), Introspection looks INWARD - it lets pwn hand a
12
- # request to the active AI engine and reflect on its own artifacts,
13
- # transcripts, findings, code, or decisions.
11
+ # threat-intel), Reflect looks INWARD - it lets pwn hand a request to
12
+ # the active AI engine and reflect on its own artifacts, transcripts,
13
+ # findings, code, or decisions.
14
14
  #
15
- # This module is gated by `PWN::Env[:ai][:introspection]` so that
15
+ # This module is gated by `PWN::Env[:ai][:module_reflection]` so that
16
16
  # potentially-sensitive local data is never shipped to a remote LLM
17
17
  # unless the operator has explicitly opted in via pwn-vault / config.
18
18
  #
@@ -21,16 +21,16 @@ module PWN
21
21
  # through when it wants an LLM opinion on locally-produced data, and
22
22
  # it is also what PWN::AI::Agent::Learning.reflect uses to distill
23
23
  # session transcripts into durable PWN::Memory lessons.
24
- module Introspection
24
+ module Reflect
25
25
  # Supported Method Parameters::
26
- # response = PWN::AI::Agent::Introspection.reflect_on(
26
+ # response = PWN::AI::Agent::Reflect.on(
27
27
  # request: 'required - String - What you want the AI to reflect on',
28
28
  # system_role_content: 'optional - context to set up the model behavior for reflection',
29
29
  # spinner: 'optional - Boolean - Display spinner during operation (default: false)',
30
30
  # suppress_pii_warning: 'optional - Boolean - Suppress PII Warnings (default: false)'
31
31
  # )
32
32
 
33
- public_class_method def self.reflect_on(opts = {})
33
+ public_class_method def self.on(opts = {})
34
34
  request = opts[:request]
35
35
  raise 'ERROR: request must be provided' if request.nil?
36
36
 
@@ -42,14 +42,14 @@ module PWN
42
42
 
43
43
  response = nil
44
44
 
45
- ai_introspection = PWN::Env[:ai][:introspection]
45
+ ai_module_reflection = PWN::Env[:ai][:module_reflection]
46
46
 
47
- if ai_introspection && request.length.positive?
47
+ if ai_module_reflection && request.length.positive?
48
48
  engine = PWN::Env[:ai][:active].to_s.downcase.to_sym
49
49
  valid_ai_engines = PWN::AI.help.reject { |e| e.downcase == :agent }.map(&:downcase)
50
50
  raise "ERROR: Unsupported AI engine. Supported engines are: #{valid_ai_engines}" unless valid_ai_engines.include?(engine)
51
51
 
52
- warn "AI Introspection is enabled. Ensure #{engine} has been authorized for use and/or requests are sanitized properly." unless suppress_pii_warning
52
+ warn "AI Reflection is enabled. Ensure #{engine} has been authorized for use and/or requests are sanitized properly." unless suppress_pii_warning
53
53
  response = PWN::AI::Agent::Loop.run(
54
54
  request: request.chomp,
55
55
  system_role_content: system_role_content,
@@ -75,7 +75,7 @@ module PWN
75
75
 
76
76
  public_class_method def self.help
77
77
  puts "USAGE:
78
- #{self}.reflect_on(
78
+ #{self}.on(
79
79
  request: 'required - String - What you want the AI to reflect on',
80
80
  system_role_content: 'optional - context to set up the model behavior for reflection',
81
81
  spinner: 'optional - Boolean - Display spinner during operation (default: false)',
@@ -16,7 +16,7 @@ module PWN
16
16
 
17
17
  system_role_content = 'Your sole purpose is to analyze source code snippets and generate an Exploit Prediction Scoring System (EPSS) score between 0% - 100%. Just generate a score unless score is >= 75% in which a PoC and code fix should also be included.'
18
18
 
19
- PWN::AI::Agent::Introspection.reflect_on(
19
+ PWN::AI::Agent::Reflect.on(
20
20
  system_role_content: system_role_content,
21
21
  request: request,
22
22
  suppress_pii_warning: true
@@ -29,7 +29,7 @@ PWN::AI::Agent::Registry.register(
29
29
  type: 'object',
30
30
  properties: {
31
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 rf] }, description: 'Subset of probes to run (default all).' }
32
+ sections: { type: 'array', items: { type: 'string', enum: %w[host net toolchain repo env rf web] }, description: 'Subset of probes to run (default all).' }
33
33
  },
34
34
  required: []
35
35
  }
@@ -86,7 +86,7 @@ PWN::AI::Agent::Registry.register(
86
86
  properties: {
87
87
  source: { type: 'string', description: 'Where it came from (nmap, shodan, burp, cve, human, ...).' },
88
88
  data: { type: 'string', description: 'The observation itself.' },
89
- category: { type: 'string', enum: %w[recon vuln intel target network env rf misc], default: 'misc' },
89
+ category: { type: 'string', enum: %w[recon vuln intel target network env rf web misc], default: 'misc' },
90
90
  target: { type: 'string', description: 'Host / IP / URL / asset the observation is about.' },
91
91
  tags: { type: 'array', items: { type: 'string' } },
92
92
  ttl: { type: 'integer', description: 'Seconds until stale (omit = forever).' }
@@ -187,7 +187,10 @@ PWN::AI::Agent::Registry.register(
187
187
  'against toolchain drift / missing binaries, (b) Learning ' \
188
188
  'failures against host/net/repo drift on the same day, ' \
189
189
  '(c) recorded :intel observations against installed ' \
190
- 'component versions. Returns actionable findings so the ' \
190
+ 'component versions, (d) :web DOM drift on watched targets ' \
191
+ 'against Learning failures citing that host, (e) refuted ' \
192
+ 'extro_verify claims against stale PWN::Memory :fact ' \
193
+ 'entries. Returns actionable findings so the ' \
191
194
  'agent can distinguish "I did it wrong" from "the world ' \
192
195
  'changed under me".',
193
196
  parameters: {
@@ -255,7 +258,7 @@ PWN::AI::Agent::Registry.register(
255
258
  name: 'extro_auto_toggle',
256
259
  description: 'Enable/disable end-of-run auto-extrospection ' \
257
260
  '(PWN::Env[:ai][:agent][:auto_extrospect]). When enabled, ' \
258
- 'Learning.auto_reflect ALSO calls Extrospection.auto_' \
261
+ 'Learning.auto_introspect ALSO calls Extrospection.auto_' \
259
262
  'extrospect after every final answer, so host drift is ' \
260
263
  'captured continuously without an explicit extro_snapshot ' \
261
264
  'call. Omit `enabled` to just read the current state.',
@@ -278,3 +281,85 @@ PWN::AI::Agent::Registry.register(
278
281
  { previous: prev, current: ai[:agent][:auto_extrospect] ? true : false }
279
282
  }
280
283
  )
284
+
285
+ PWN::AI::Agent::Registry.register(
286
+ name: 'extro_verify',
287
+ toolset: 'extrospection',
288
+ schema: {
289
+ name: 'extro_verify',
290
+ description: 'Browser-backed SELF FACT-CHECK. Drives ' \
291
+ 'PWN::Plugins::TransparentBrowser (:headless) against a ' \
292
+ 'canonical source for the claim class (NVD/CVE.org for ' \
293
+ ':cve, rubygems/PyPI/GitHub for :version, the cited URL ' \
294
+ 'for :doc, DuckDuckGo HTML for :generic), renders the DOM ' \
295
+ 'with JS executed, and returns {claim:, kind:, verdict: ' \
296
+ ':confirmed|:refuted|:unknown, confidence:, evidence:[], ' \
297
+ 'action_taken:}. On :refuted → Mistakes.record(tool:' \
298
+ "'assumption') so KNOWN MISTAKES warns every future run; " \
299
+ 'on :confirmed → observe(:intel, ttl:30d); on :unknown → ' \
300
+ 'Learning.note_outcome(tags:[needs_human]). This is the ' \
301
+ 'PROACTIVE trigger that catches the model being wrong ' \
302
+ 'about the world before a human does — the extrospective ' \
303
+ 'mirror of mistakes_record.',
304
+ parameters: {
305
+ type: 'object',
306
+ properties: {
307
+ claim: { type: 'string', description: 'Factual claim to fact-check (a CVE assertion, "latest X is v1.2.3", a cited URL + quoted snippet, or free text).' },
308
+ kind: { type: 'string', enum: %w[cve version doc generic], description: 'Force a verifier. Omit to auto-detect from the claim.' },
309
+ url: { type: 'string', description: 'Explicit URL the claim cites (forces kind: :doc).' },
310
+ commit: { type: 'boolean', default: true, description: 'Write Mistakes/observe/Learning on the verdict.' },
311
+ proxy: { type: 'string', description: 'Upstream proxy for TransparentBrowser (e.g. "tor" or http://127.0.0.1:8080).' }
312
+ },
313
+ required: %w[claim]
314
+ }
315
+ },
316
+ check: -> { defined?(PWN::AI::Agent::Extrospection) && PWN::AI::Agent::Extrospection.respond_to?(:verify) },
317
+ handler: lambda { |args|
318
+ o = { claim: args[:claim] }
319
+ o[:kind] = args[:kind] if args[:kind]
320
+ o[:url] = args[:url] if args[:url]
321
+ o[:commit] = args[:commit] if args.key?(:commit)
322
+ o[:proxy] = args[:proxy] if args[:proxy]
323
+ PWN::AI::Agent::Extrospection.verify(o)
324
+ }
325
+ )
326
+
327
+ PWN::AI::Agent::Registry.register(
328
+ name: 'extro_watch',
329
+ toolset: 'extrospection',
330
+ schema: {
331
+ name: 'extro_watch',
332
+ description: 'Passive change-detection on an external web artefact you ' \
333
+ 'care about (a target /api/version, a vendor changelog, a ' \
334
+ 'bug-bounty scope page). Renders the URL headlessly via ' \
335
+ 'PWN::Plugins::TransparentBrowser, hashes the RENDERED DOM ' \
336
+ 'text (JS-delivered changes count), captures title / TLS ' \
337
+ 'cert fp / screenshot, and persists it as observe(' \
338
+ 'category: :web). Re-running against the same URL returns ' \
339
+ '{changed: true|false, prior_sha:, current:{…}}; a ' \
340
+ 'subsequent extro_snapshot(sections:["web"]) surfaces the ' \
341
+ 'delta in drift() as ~web.<host>.dom_sha exactly like ' \
342
+ '~toolchain.nmap today.',
343
+ parameters: {
344
+ type: 'object',
345
+ properties: {
346
+ url: { type: 'string', description: 'URL to render, hash and watch.' },
347
+ selector: { type: 'string', description: 'CSS selector whose innerText to hash (default full body).' },
348
+ ttl: { type: 'integer', description: 'Seconds until this :web observation is stale (default 604800 = 7d).' },
349
+ tags: { type: 'array', items: { type: 'string' } },
350
+ proxy: { type: 'string', description: 'Upstream proxy for TransparentBrowser (e.g. "tor").' }
351
+ },
352
+ required: %w[url]
353
+ }
354
+ },
355
+ check: -> { defined?(PWN::AI::Agent::Extrospection) && PWN::AI::Agent::Extrospection.respond_to?(:watch) },
356
+ handler: lambda { |args|
357
+ PWN::AI::Agent::Extrospection.watch(
358
+ url: args[:url],
359
+ selector: args[:selector],
360
+ ttl: args[:ttl],
361
+ tags: args[:tags],
362
+ proxy: args[:proxy]
363
+ )
364
+ }
365
+ )
@@ -190,13 +190,13 @@ PWN::AI::Agent::Registry.register(
190
190
  )
191
191
 
192
192
  PWN::AI::Agent::Registry.register(
193
- name: 'learning_auto_reflect_toggle',
193
+ name: 'learning_auto_introspect_toggle',
194
194
  toolset: 'learning',
195
195
  schema: {
196
- name: 'learning_auto_reflect_toggle',
196
+ name: 'learning_auto_introspect_toggle',
197
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 ' \
198
+ '(PWN::Env[:ai][:agent][:auto_introspect]). When enabled, ' \
199
+ 'Loop.run calls Learning.auto_introspect on the session after ' \
200
200
  'every final answer. Disable during noisy fuzzing loops; ' \
201
201
  're-enable for the summary turn. Omit `enabled` to just ' \
202
202
  'read the current state.',
@@ -214,8 +214,8 @@ PWN::AI::Agent::Registry.register(
214
214
  raise 'PWN::Env[:ai] is unavailable or immutable' unless ai.is_a?(Hash) && !ai.frozen?
215
215
 
216
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 }
217
+ prev = ai[:agent][:auto_introspect] ? true : false
218
+ ai[:agent][:auto_introspect] = (args[:enabled] ? true : false) if args.key?(:enabled)
219
+ { previous: prev, current: ai[:agent][:auto_introspect] ? true : false }
220
220
  }
221
221
  )
@@ -20,7 +20,7 @@ module PWN
20
20
 
21
21
  system_role_content = "Being an expert penetration tester skilled in code analysis, debugging, and exploitation while stepping through JavaScript in a Chrome DevTools debugging session: 1. Your sole purpose is to analyze each JavaScript step and generate an Exploit Prediction Scoring System (EPSS) score between 0% - 100%. The step currently resides in this block of JavaScript:\n```\n#{source_to_review}\n```\n2. If the score is >= 75%, generate a JavaScript proof-of-concept that would allow a threat actor to directly exploit or target a user for exploitation (i.e. no self-exploit). 3. If the EPSS score is >= 75% also provide a code fix. *** If the EPSS score is < 75%, no explanations or summaries - just the EPSS score."
22
22
 
23
- PWN::AI::Agent::Introspection.reflect_on(
23
+ PWN::AI::Agent::Reflect.on(
24
24
  system_role_content: system_role_content,
25
25
  request: request,
26
26
  suppress_pii_warning: true
@@ -12,7 +12,7 @@ module PWN
12
12
  # 4. CVSS score, vector string, and first.org calculator URI
13
13
  # 5. CWE category, brief description, and CWE URI
14
14
  # 6. Relevant NIST 800-53 control
15
- # It leverages the PWN::AI::Agent::Introspection.reflect_on method. Defaults to Jira for existing workflow compatibility.
15
+ # It leverages the PWN::AI::Agent::Reflect.on method. Defaults to Jira for existing workflow compatibility.
16
16
  module VulnGen
17
17
  # Supported Method Parameters::
18
18
  # ai_analysis = PWN::AI::Agent::VulnGen.analyze(
@@ -60,7 +60,7 @@ module PWN
60
60
  6. NIST 800-53 Security Control that is impacted by this vulnerability.
61
61
  "
62
62
 
63
- analysis = PWN::AI::Agent::Introspection.reflect_on(
63
+ analysis = PWN::AI::Agent::Reflect.on(
64
64
  system_role_content: system_role_content,
65
65
  request: request,
66
66
  suppress_pii_warning: true
data/lib/pwn/ai/agent.rb CHANGED
@@ -27,7 +27,7 @@ module PWN
27
27
  autoload :Learning, 'pwn/ai/agent/learning'
28
28
  autoload :Mistakes, 'pwn/ai/agent/mistakes'
29
29
  autoload :Extrospection, 'pwn/ai/agent/extrospection'
30
- autoload :Introspection, 'pwn/ai/agent/introspection'
30
+ autoload :Reflect, 'pwn/ai/agent/reflect'
31
31
  autoload :Swarm, 'pwn/ai/agent/swarm'
32
32
 
33
33
  # Display a List of Every PWN::AI Module
@@ -250,17 +250,21 @@ module PWN
250
250
  end
251
251
  messages.push(user_role)
252
252
 
253
- # `max_tokens` is deprecated on /v1/chat/completions; the unified
254
- # parameter is `max_completion_tokens` and works for every chat model
253
+ # Wire config key `max_tokens` (kept for cross-engine naming parity with
254
+ # Anthropic/Gemini) to the OpenAI wire-format field `max_completion_tokens`.
255
+ # OpenAI deprecated the request-body key `max_tokens` on /v1/chat/completions
256
+ # in favour of `max_completion_tokens`, which works for every chat model
255
257
  # including the reasoning family. Don't try to guess per-model caps —
256
- # let the server clamp; default to a generous ceiling that the
257
- # operator can override via PWN::Env[:ai][:openai][:max_completion_tokens].
258
- max_completion_tokens = (engine[:max_completion_tokens] || 16_384).to_i
258
+ # let the server clamp; default to a generous ceiling that the operator
259
+ # can override via PWN::Env[:ai][:openai][:max_tokens].
260
+ # Accept legacy :max_completion_tokens as a one-release alias so existing
261
+ # pwn.yaml files keep working without a silent clamp to the default.
262
+ max_tokens = (engine[:max_tokens] || engine[:max_completion_tokens] || 16_384).to_i
259
263
 
260
264
  http_body = {
261
265
  model: model,
262
266
  messages: messages,
263
- max_completion_tokens: max_completion_tokens
267
+ max_completion_tokens: max_tokens
264
268
  }
265
269
  # Reasoning models reject sampler params (temperature, top_p, etc.)
266
270
  http_body[:temperature] = temp unless reasoning
data/lib/pwn/config.rb CHANGED
@@ -29,7 +29,7 @@ module PWN
29
29
  env = {
30
30
  ai: {
31
31
  active: 'grok',
32
- introspection: false,
32
+ module_reflection: false,
33
33
  grok: {
34
34
  base_uri: 'optional - Base URI for Grok - Use private base OR defaults to https://api.x.ai/v1',
35
35
  key: 'required - xAI Grok API Key',
@@ -58,6 +58,7 @@ module PWN
58
58
  model: 'optional - OpenAI model to use',
59
59
  system_role_content: 'You are an ethically hacking OpenAI agent.',
60
60
  temp: 'optional - OpenAI temperature',
61
+ max_tokens: 'optional - Max output tokens per response (default 16384). Mapped to OpenAI wire param max_completion_tokens.',
61
62
  max_prompt_length: 128_000
62
63
  },
63
64
  ollama: {
@@ -90,9 +91,9 @@ module PWN
90
91
  max_iters: 25,
91
92
  # Swarm (agent_ask/agent_debate) sub-agent recursion cap
92
93
  max_depth: 3,
93
- # run PWN::AI::Agent::Learning.auto_reflect after every final answer
94
- auto_reflect: true,
95
- # also run PWN::AI::Agent::Extrospection.auto_extrospect from auto_reflect
94
+ # run PWN::AI::Agent::Learning.auto_introspect after every final answer
95
+ auto_introspect: true,
96
+ # also run PWN::AI::Agent::Extrospection.auto_extrospect from auto_introspect
96
97
  auto_extrospect: false,
97
98
  toolsets: nil
98
99
  # multi-agent personas : ~/.pwn/agents.yml (see PWN::AI::Agent::Swarm.help)
@@ -50,7 +50,7 @@ module PWN
50
50
  # type: 'required - type of history to introspect (:sitemap, :proxy_history, :websocket_history)'
51
51
  # )
52
52
  private_class_method def self.init_introspection_thread(opts = {})
53
- # if PWN::Env[:ai][:introspection] is true,
53
+ # if PWN::Env[:ai][:module_reflection] is true,
54
54
  # spin up Thread to:
55
55
  # 1. Periodically call get_proxy_history(burp_obj: burp_obj) method
56
56
  # 2. For each entry w/ empty comment,
@@ -65,7 +65,7 @@ module PWN
65
65
  type = opts[:type]
66
66
  raise "ERROR: type parameter is required and must be one of: #{valid_types.join(', ')}" unless valid_types.include?(type)
67
67
 
68
- if PWN::Env[:ai][:introspection]
68
+ if PWN::Env[:ai][:module_reflection]
69
69
  introspection_thread_arr = burp_obj[:introspection_threads] ||= []
70
70
  introspection_thread = Thread.new do
71
71
  get_highlight_color = lambda do |opts = {}|
@@ -19,8 +19,8 @@ module PWN
19
19
  dir_path = opts[:dir_path]
20
20
  git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
21
21
  result_arr = []
22
- ai_introspection = PWN::Env[:ai][:introspection]
23
- logger_results = "AI Introspection => #{PWN::Env[:ai][:introspecction]} => "
22
+ ai_module_reflection = PWN::Env[:ai][:module_reflection]
23
+ logger_results = "AI Module Reflection => #{PWN::Env[:ai][:module_reflection]} => "
24
24
 
25
25
  PWN::Plugins::FileFu.recurse_in_dir(dir_path: dir_path) do |entry|
26
26
  if File.file?(entry) && File.basename(entry) == 'pom.xml' && entry !~ /test/i
@@ -63,7 +63,7 @@ module PWN
63
63
  git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
64
64
 
65
65
  result_arr = []
66
- logger_results = "AI Introspection => #{PWN::Env[:ai][:introspection]} => "
66
+ logger_results = "AI Module Reflection => #{PWN::Env[:ai][:module_reflection]} => "
67
67
 
68
68
  PWN::Plugins::FileFu.recurse_in_dir(
69
69
  dir_path: dir_path,
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'shellwords'
4
-
5
3
  module PWN
6
4
  module SDR
7
5
  module Decoder
8
- # ADS-B (Automatic Dependent Surveillance Broadcast) decoder for
9
- # aircraft transponder squitters on 1090 MHz (Mode-S / ES) and 978 MHz
10
- # (UAT). 2 Mbit/s PPM cannot be recovered from GQRX audio, so this
11
- # module drives `dump1090` (or `dump978-fa`) directly against the SDR
12
- # and structures each SBS-1/BaseStation CSV line it emits on stdout.
6
+ # Pure-Ruby ADS-B (1090 MHz Mode-S / 978 MHz UAT) activity detector.
7
+ #
8
+ # Mode-S Extended Squitter is 2 Mbit/s PPM with 8/120 μs frames on a
9
+ # 2 MHz-wide channel physically unrecoverable from GQRX's 48 kHz
10
+ # demodulated-audio tap. Rather than shell out to `dump1090`, this
11
+ # module runs Base.run_detector to characterise squitter density
12
+ # (bursts/sec, peak dBFS, floor) natively in Ruby. `parse_line` is
13
+ # retained for offline SBS-1 CSV analysis.
13
14
  module ADSB
14
15
  SBS_FIELDS = %i[
15
16
  msg_type tx_type session_id aircraft_id icao24 flight_id
@@ -25,28 +26,13 @@ module PWN
25
26
 
26
27
  public_class_method def self.decode(opts = {})
27
28
  freq_obj = opts[:freq_obj]
28
- raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
29
-
30
- hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
31
- gain = (freq_obj[:rf_gain] || 40).to_s.to_f
32
- uat = hz.between?(977_000_000, 979_000_000)
33
-
34
- direct_cmd =
35
- if uat && PWN::SDR::Decoder::Base.bin_available?(bin: 'dump978-fa')
36
- "bash -c #{Shellwords.escape("dump978-fa --sdr driver=rtlsdr --sdr-gain #{gain} --raw-stdout 2>/dev/null | uat2text")}"
37
- elsif PWN::SDR::Decoder::Base.bin_available?(bin: 'dump1090-fa')
38
- "dump1090-fa --device-type rtlsdr --gain #{gain} --net-sbs-stdout --quiet"
39
- else
40
- "dump1090 --gain #{gain} --net-sbs-stdout --quiet"
41
- end
42
-
43
- PWN::SDR::Decoder::Base.run_pipeline(
29
+ hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
30
+ uat = hz.between?(977_000_000, 979_000_000)
31
+ PWN::SDR::Decoder::Base.run_detector(
44
32
  freq_obj: freq_obj,
45
33
  protocol: uat ? 'ADSB-UAT978' : 'ADSB-1090ES',
46
- required_bins: uat ? %w[dump978-fa uat2text] : %w[dump1090],
47
- direct_cmd: direct_cmd,
48
- line_match: /^(MSG,|-|\+)/,
49
- parser: proc { |line| parse_line(line: line) }
34
+ note: '2 Mbit/s PPM squitters exceed the 48 kHz audio-tap Nyquist limit; native mode reports squitter-burst density only. Feed captured SBS-1 CSV to .parse_line for full field decode.',
35
+ describe: proc { |b| { modulation: 'PPM', frame_len_us: 120, classification: b[:duration_ms] < 5 ? 'squitter' : 'interrogation-train' } }
50
36
  )
51
37
  end
52
38
 
@@ -60,7 +46,6 @@ module PWN
60
46
  f = line.split(',', -1)
61
47
  out = { protocol: 'ADSB' }
62
48
  SBS_FIELDS.each_with_index { |k, i| out[k] = f[i] unless f[i].to_s.empty? }
63
-
64
49
  bits = []
65
50
  bits << "ICAO=#{out[:icao24]}" if out[:icao24]
66
51
  bits << "CS=#{out[:callsign].to_s.strip}" if out[:callsign]
@@ -81,15 +66,11 @@ module PWN
81
66
  # Display Usage for this Module
82
67
 
83
68
  public_class_method def self.help
84
- puts "USAGE:
69
+ puts "USAGE (ruby-native detector, no external binaries):
85
70
  #{self}.decode(
86
71
  freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
87
72
  )
88
73
 
89
- NOTE: Requires `dump1090` (1090 MHz Mode-S/ES) or
90
- `dump978-fa` + `uat2text` (978 MHz UAT). Owns the SDR
91
- directly — set freq_obj[:sdr_args] if GQRX has device 0.
92
-
93
74
  #{self}.parse_line(line: 'MSG,3,1,1,ABCDEF,1,...')
94
75
 
95
76
  #{self}.authors
@@ -1,16 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'shellwords'
4
-
5
3
  module PWN
6
4
  module SDR
7
5
  module Decoder
8
- # NOAA APT (Automatic Picture Transmission) decoder for the 137 MHz
9
- # polar-orbiting weather satellites (NOAA-15/18/19). APT is a 2400 Hz
10
- # AM subcarrier inside a 34 kHz-wide FM downlink; GQRX's UDP audio is
11
- # recorded to a WAV, then handed to `noaa-apt` (or `satdump`) to render
12
- # the two-channel visible/IR image strip.
6
+ # Pure-Ruby NOAA APT (Automatic Picture Transmission) decoder for the
7
+ # 137 MHz polar-orbiting weather satellites (NOAA-15/18/19).
8
+ #
9
+ # APT is a 2400 Hz AM subcarrier inside a ~34 kHz-wide FM downlink
10
+ # carrying two 909-pixel image channels at 2 lines/second (4160
11
+ # words/line). This module envelope-demodulates the 2400 Hz carrier
12
+ # from GQRX's 48 kHz UDP audio, resamples to 4160 words/sec, aligns
13
+ # each line on the 7-pulse Sync-A pattern, and appends the resulting
14
+ # 8-bit greyscale rows to a Netpbm PGM (P5) file — all in Ruby.
15
+ # No `sox`, no `noaa-apt`.
13
16
  module APT
17
+ WORDS_PER_LINE = 4160
18
+ LINES_PER_SEC = 2
19
+ WORD_RATE = WORDS_PER_LINE * LINES_PER_SEC # 8320 Hz
20
+ # Sync-A: 7 cycles of 1040 Hz square = 1 1 0 0 repeated 7 times
21
+ SYNC_A = ([1, 1, 0, 0] * 7).freeze
22
+
23
+ # Streaming APT demodulator fed by Base.run_native.
24
+ class Demod
25
+ def initialize(rate: 48_000, out_path: nil)
26
+ @rate = rate
27
+ @carrier = 2400.0
28
+ @env_win = (rate / @carrier).round # ≈20 samples per cycle
29
+ @stamp = Time.now.strftime('%Y%m%d_%H%M%S')
30
+ @pgm_path = out_path || "/tmp/apt_#{@stamp}.pgm"
31
+ @rows = []
32
+ @word_buf = []
33
+ @minv = 1.0
34
+ @maxv = 0.0
35
+ end
36
+
37
+ def feed(samples, &)
38
+ env = PWN::SDR::Decoder::DSP.envelope(samples: samples, window: @env_win)
39
+ words = PWN::SDR::Decoder::DSP.resample(samples: env, src_rate: @rate, dst_rate: WORD_RATE)
40
+ @word_buf.concat(words)
41
+ extract_lines(&)
42
+ end
43
+
44
+ private
45
+
46
+ def extract_lines
47
+ while @word_buf.length >= WORDS_PER_LINE * 2
48
+ off = sync_offset(@word_buf[0, WORDS_PER_LINE])
49
+ @word_buf.shift(off) if off.positive?
50
+ break if @word_buf.length < WORDS_PER_LINE
51
+
52
+ row = @word_buf.shift(WORDS_PER_LINE)
53
+ lo, hi = row.minmax
54
+ @minv = lo if lo < @minv
55
+ @maxv = hi if hi > @maxv
56
+ @rows << row
57
+ write_pgm if (@rows.length % 20).zero?
58
+ yield(
59
+ protocol: 'NOAA-APT',
60
+ lines: @rows.length,
61
+ seconds: @rows.length / LINES_PER_SEC,
62
+ pgm: @pgm_path,
63
+ summary: "APT line #{@rows.length} (#{@rows.length / LINES_PER_SEC}s) → #{@pgm_path}"
64
+ )
65
+ end
66
+ end
67
+
68
+ def sync_offset(row)
69
+ mid = row.sum / row.length
70
+ best_off = 0
71
+ best_cor = -1.0
72
+ (0..(row.length - SYNC_A.length)).each do |o|
73
+ cor = 0.0
74
+ SYNC_A.each_with_index do |s, i|
75
+ cor += (row[o + i] - mid) * (s.zero? ? -1.0 : 1.0)
76
+ end
77
+ if cor > best_cor
78
+ best_cor = cor
79
+ best_off = o
80
+ end
81
+ end
82
+ best_off
83
+ end
84
+
85
+ def write_pgm
86
+ span = @maxv - @minv
87
+ span = 1.0 if span <= 0
88
+ File.open(@pgm_path, 'wb') do |f|
89
+ f.write("P5\n#{WORDS_PER_LINE} #{@rows.length}\n255\n")
90
+ @rows.each do |row|
91
+ bytes = row.map { |v| (((v - @minv) / span) * 255).clamp(0, 255).round }
92
+ f.write(bytes.pack('C*'))
93
+ end
94
+ end
95
+ end
96
+ end
97
+
14
98
  # Supported Method Parameters::
15
99
  # PWN::SDR::Decoder::APT.decode(
16
100
  # freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
@@ -18,35 +102,11 @@ module PWN
18
102
 
19
103
  public_class_method def self.decode(opts = {})
20
104
  freq_obj = opts[:freq_obj]
21
- raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)
22
-
23
- stamp = Time.now.strftime('%Y%m%d_%H%M%S')
24
- wav_out = "/tmp/apt_#{stamp}.wav"
25
- png_out = "/tmp/apt_#{stamp}.png"
26
-
27
- # Record 11 025 Hz WAV (noaa-apt's native rate) until [ENTER],
28
- # emitting a heartbeat line per second so the spinner stays live,
29
- # then decode to PNG on exit.
30
- inner = 'sox -t raw -e signed-integer -b 16 -r 22050 -c 1 - ' \
31
- "-t wav -r 11025 #{Shellwords.escape(wav_out)} & SOXPID=$!; " \
32
- 'trap "kill $SOXPID 2>/dev/null" EXIT INT TERM; ' \
33
- 'while kill -0 $SOXPID 2>/dev/null; do ' \
34
- "echo APT_REC seconds=$SECONDS file=#{wav_out}; sleep 1; done"
35
-
36
- PWN::SDR::Decoder::Base.run_pipeline(
105
+ PWN::SDR::Decoder::Base.run_native(
37
106
  freq_obj: freq_obj,
38
107
  protocol: 'NOAA-APT',
39
- required_bins: %w[sox noaa-apt],
40
- decode_cmd: "bash -c #{Shellwords.escape(inner)}",
41
- line_match: /^APT_REC/,
42
- parser: proc { |line| { protocol: 'NOAA-APT', wav: wav_out, png: png_out, summary: line.strip } }
108
+ demod: Demod.new
43
109
  )
44
-
45
- return unless File.exist?(wav_out) && File.size(wav_out).positive?
46
-
47
- system('noaa-apt', wav_out, '-o', png_out)
48
-
49
- puts "[*] APT image written: #{png_out}" if File.exist?(png_out)
50
110
  end
51
111
 
52
112
  # Author(s):: 0day Inc. <support@0dayinc.com>
@@ -58,13 +118,14 @@ module PWN
58
118
  # Display Usage for this Module
59
119
 
60
120
  public_class_method def self.help
61
- puts "USAGE:
121
+ puts "USAGE (ruby-native, no external binaries):
62
122
  #{self}.decode(
63
123
  freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
64
124
  )
65
125
 
66
- NOTE: Requires `sox` and `noaa-apt`. Set GQRX to FM, ~34 kHz BW.
67
- Records the pass to /tmp/apt_<ts>.wav, decodes to PNG on exit.
126
+ NOTE: Set GQRX to WFM (mono), ~34 kHz filter. Writes an 8-bit
127
+ greyscale Netpbm P5 image to /tmp/apt_<ts>.pgm every 10 s
128
+ of received pass. Both A/B channels are in one strip.
68
129
 
69
130
  #{self}.authors
70
131
  "