pwn 0.5.428 → 0.5.430

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb4a896d76b2eff5600f7e7464227b6223a4287e4cd4e6fba7a60df29d40bc64
4
- data.tar.gz: 93dbd97045e6ac60c4a46446bb8c681629ab462186092657e4f75bdb975eb7c9
3
+ metadata.gz: eecc95f7ab850f845600203b3fed19bb77d5d3737233a7f7f98df09b84154737
4
+ data.tar.gz: d838e72b716e9fc50e604a95600f1c9e43db9a42772e76073adc2f4cd673e96b
5
5
  SHA512:
6
- metadata.gz: 50aaa55f8d403788d10610c52a877bc99494678c0606b9d83fcdf623470faae899bfa6e095e0de4114832575184308d5a9303d0b5f1f15d0038f3e13b5c9e1b1
7
- data.tar.gz: fdc901980719c87ac59a14e247f352074271ed8b43572bdbd446462fb621e772db5cb065c117c07b55c68caee5bd559094e536c5316c958af8fcab1f5f7ad602
6
+ metadata.gz: 81dea57ae53b6401b7b5fa2784d5dc64e8b2a311ad1fc89c20c18e1024739ed66502db92be54ece95695df3b7a3fb25bbacd3aa311ac69d71bf5c2f4b23496c6
7
+ data.tar.gz: 071b3ac7ac65e4f282e3f59d223ec4c68c2f9b61b62442e613b4aa347ed6ab836cca10eafee0a148b79446c2adaf3e1a5bf60107b4871165d04d3017ef7b2fb3
data/Gemfile CHANGED
@@ -46,7 +46,7 @@ gem 'jsonpath', '1.1.5'
46
46
  gem 'json_schemer', '2.4.0'
47
47
  gem 'jwt', '3.1.2'
48
48
  gem 'libusb', '0.7.2'
49
- gem 'luhn', '1.0.2'
49
+ gem 'luhn', '2.0.0'
50
50
  gem 'mail', '2.8.1'
51
51
  gem 'meshtastic', '0.0.126'
52
52
  gem 'metasm', '1.0.5'
data/README.md CHANGED
@@ -37,7 +37,7 @@ $ cd /opt/pwn
37
37
  $ ./install.sh
38
38
  $ ./install.sh ruby-gem
39
39
  $ pwn
40
- pwn[v0.5.428]:001 >>> PWN.help
40
+ pwn[v0.5.430]:001 >>> PWN.help
41
41
  ```
42
42
 
43
43
  [![Installing the pwn Security Automation Framework](https://raw.githubusercontent.com/0dayInc/pwn/master/documentation/pwn_install.png)](https://youtu.be/G7iLUY4FzsI)
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
52
52
  $ gem uninstall --all --executables pwn
53
53
  $ gem install --verbose pwn
54
54
  $ pwn
55
- pwn[v0.5.428]:001 >>> PWN.help
55
+ pwn[v0.5.430]:001 >>> PWN.help
56
56
  ```
57
57
 
58
58
  If you're using a multi-user install of RVM do:
@@ -62,7 +62,7 @@ $ rvm use ruby-3.4.4@pwn
62
62
  $ rvmsudo gem uninstall --all --executables pwn
63
63
  $ rvmsudo gem install --verbose pwn
64
64
  $ pwn
65
- pwn[v0.5.428]:001 >>> PWN.help
65
+ pwn[v0.5.430]:001 >>> PWN.help
66
66
  ```
67
67
 
68
68
  PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
data/bin/pwn CHANGED
@@ -10,12 +10,12 @@ OptionParser.new do |options|
10
10
  #{File.basename($PROGRAM_NAME)} [opts]
11
11
  "
12
12
 
13
- options.on('-cPATH', '--yaml-config=PATH', '<Optional - PWN YAML File>') do |p|
14
- opts[:yaml_config_path] = p
13
+ options.on('-YPATH', '--pwn-config=PATH', '<Optional - PWN YAML File>') do |p|
14
+ opts[:pwn_config_path] = p
15
15
  end
16
16
 
17
- options.on('-dPATH', '--yaml-decryptor=PATH', '<Optional - Out-of-Band YAML File with :key && :iv>') do |d|
18
- opts[:yaml_decryptor_path] = d
17
+ options.on('-ZPATH', '--pwn-decryptor=PATH', '<Optional - Out-of-Band YAML File with :key && :iv>') do |d|
18
+ opts[:pwn_decryptor_path] = d
19
19
  end
20
20
  end.parse!
21
21
 
data/bin/pwn_sast CHANGED
@@ -11,6 +11,15 @@ OptionParser.new do |options|
11
11
  #{File.basename($PROGRAM_NAME)} [opts]
12
12
  "
13
13
 
14
+ # TODO: HOW TO LOAD THE pwn.yaml CONFIGURATION FILE FOR EVERYTHING UNLESS OVERRIDDEN???
15
+ options.on('-YPATH', '--pwn-config=PATH', '<Optional - PWN YAML File>') do |p|
16
+ opts[:pwn_config_path] = p
17
+ end
18
+
19
+ options.on('-ZPATH', '--pwn-decryptor=PATH', '<Optional - Out-of-Band YAML File with :key && :iv>') do |d|
20
+ opts[:pwn_decryptor_path] = d
21
+ end
22
+
14
23
  options.on('-uGITURI', '--uri-source-root=GITURI', '<Required - HTTP URI of Git Repo Scanned e.g. https://github.com/0dayInc/pwn/tree/master>') do |u|
15
24
  opts[:uri_source_root] = u
16
25
  end
@@ -35,30 +44,6 @@ OptionParser.new do |options|
35
44
  opts[:report_name] = n
36
45
  end
37
46
 
38
- options.on('-aENGINE', '--ai-engine=ENGINE', '<Optional AI Engine to Analyze Results (grok, ollama, openai) [WARNING: CAN DRAMATICALLY INCREASE AI USAGE COSTS AND/OR SCAN DURATIONS]>') do |a|
39
- opts[:ai_engine] = a
40
- end
41
-
42
- options.on('-bURI', '--ai-base-uri=URI', '<Optional AI Base API URI (Only Required for "ollama" AI Engine. Supported by other LLMs when hosted privately)>') do |b|
43
- opts[:ai_base_uri] = b
44
- end
45
-
46
- options.on('-mMODEL', '--ai-model=MODEL', '<Optional AI Model to Use for Respective AI Engine (e.g., grok-4-0709, grok-3-mini-fast, gpt5-chat-latest, chargpt-4o-latest, llama-3.1, etc.)>') do |m|
47
- opts[:ai_model] = m
48
- end
49
-
50
- options.on('-kTOKEN', '--ai-key=TOKEN', '<Optional AI Key/Token for Respective AI Engine>') do |k|
51
- opts[:ai_key] = k
52
- end
53
-
54
- options.on('-SCONTENT', '--ai-system-content=CONTENT', '<Optional AI System Role Content for Respective AI Engine (Defaults to, "Confidence score of 0-10 this is vulnerable (0 being not vulnerable, moving upwards in confidence of exploitation). Provide additional context to assist penetration tester assessment.")>') do |s|
55
- opts[:ai_system_role_content] = s
56
- end
57
-
58
- options.on('-FTEMP', '--ai-temp=TEMP', '<Optional AI Temperature for Respective AI Engine (Default 0.9)>') do |t|
59
- opts[:ai_temp] = t
60
- end
61
-
62
47
  options.on('-s', '--[no-]start-reporting-server', '<Optional - Start Simple HTTP Server for Reporting>') do |s|
63
48
  opts[:start_reporting_server] = s
64
49
  end
@@ -71,6 +56,16 @@ end
71
56
 
72
57
  begin
73
58
  timestamp = Time.now.strftime('%Y-%m-%d_%H:%M:%S%z')
59
+
60
+ pwn_config_path = opts[:pwn_config_path]
61
+ pwn_decryptor_path = opts[:pwn_decryptor_path]
62
+ if pwn_config_path
63
+ PWN::Config.refresh(
64
+ pwn_config_path: pwn_config_path,
65
+ pwn_decryptor_path: pwn_decryptor_path
66
+ )
67
+ end
68
+
74
69
  previous_dir = Dir.pwd
75
70
 
76
71
  pwn_provider = 'ruby-gem'
@@ -95,23 +90,6 @@ begin
95
90
  report_name ||= "#{File.basename(Dir.pwd)}-#{timestamp}" if dir_path == '.'
96
91
  report_name ||= "#{File.basename(dir_path)}-#{timestamp}" unless dir_path == '.'
97
92
 
98
- ai_engine = opts[:ai_engine]
99
- if ai_engine
100
- ai_engine = ai_engine.to_s.to_sym
101
- valid_ai_engines = %i[grok ollama openai]
102
- raise "ERROR: Invalid AI Engine. Valid options are: #{valid_ai_engines.join(', ')}" unless valid_ai_engines.include?(ai_engine)
103
-
104
- ai_base_uri = opts[:ai_base_uri]
105
- raise 'ERROR: --base-uri Parameter for Ollama AI engine is required.' if ai_engine == :ollama && ai_base_uri.nil?
106
-
107
- ai_model = opts[:ai_model]
108
- raise 'ERROR: AI Model is required for AI engine ollama.' if ai_engine == :ollama && ai_model.nil?
109
-
110
- ai_key = opts[:ai_key] ||= PWN::Plugins::AuthenticationHelper.mask_password(prompt: "#{ai_engine} Token")
111
- ai_system_role_content = opts[:ai_system_role_content]
112
- ai_temp = opts[:ai_temp]
113
- end
114
-
115
93
  start_reporting_server = opts[:start_reporting_server]
116
94
 
117
95
  # Define Test Cases to Run & Start Thread Pool
@@ -202,13 +180,7 @@ begin
202
180
  PWN::Reports::SAST.generate(
203
181
  dir_path: dir_path,
204
182
  results_hash: results_hash,
205
- report_name: report_name,
206
- ai_engine: ai_engine,
207
- ai_model: ai_model,
208
- ai_key: ai_key,
209
- ai_base_uri: ai_base_uri,
210
- ai_system_role_content: ai_system_role_content,
211
- ai_temp: ai_temp
183
+ report_name: report_name
212
184
  )
213
185
  puts 'complete.'
214
186
 
data/etc/pwn.yaml.EXAMPLE CHANGED
@@ -1,7 +1,33 @@
1
1
  # Drop this file in $HOME/.pwn/pwn.yaml
2
2
  # Use PWN::Plugins::Vault.create(file: 'pwn.yaml') to encrypt this file
3
+
3
4
  # ai_engine: 'openai' || 'ollama'
4
- ai_engine: 'grok'
5
+ # WARNING::
6
+ # If PWN::CONFIG[:ai][:instrospection] = true, the active ai agent will be used
7
+ # wherever possible throughout pwn. Proceeds with caution, as this may incur additional costs
8
+ ai:
9
+ active: 'grok'
10
+ introspection: false
11
+ grok:
12
+ base_uri: 'optional - Base URI for Grok - Use private base OR defaults to https://api.x.ai/v1'
13
+ key: 'required - OpenAI API Key'
14
+ model: 'optional - Grok model to use'
15
+ system_role_content: 'You are an ethically hacking OpenAI agent.'
16
+ temp: 'optional - OpenAI temperature'
17
+
18
+ openai:
19
+ base_uri: 'optional - Base URI for OpenAI - Use private base OR defaults to https://api.openai.com/v1'
20
+ key: 'required - OpenAI API Key'
21
+ model: 'optional - OpenAI model to use'
22
+ system_role_content: 'You are an ethically hacking OpenAI agent.'
23
+ temp: 'optional - OpenAI temperature'
24
+
25
+ ollama:
26
+ base_uri: 'required - Base URI for Open WebUI - e.g. https://ollama.local'
27
+ key: 'required - Open WebUI API Key Under Settings >> Account >> JWT Token'
28
+ model: 'required - Ollama model to use'
29
+ system_role_content: 'You are an ethically hacking Ollama agent.'
30
+ temp: 'optional - Ollama temperature'
5
31
 
6
32
  # Use PWN::Plugins::Assembly.list_supported_archs to list supported architectures
7
33
  asm:
@@ -28,27 +54,6 @@ irc:
28
54
  hunter:
29
55
  api_key: 'hunter.how API Key'
30
56
 
31
- grok:
32
- base_uri: 'optional - Base URI for Grok - Use private base OR defaults to https://api.x.ai/v1'
33
- key: 'required - OpenAI API Key'
34
- model: 'optional - Grok model to use'
35
- system_role_content: 'You are an ethically hacking OpenAI agent.'
36
- temp: 'optional - OpenAI temperature'
37
-
38
- openai:
39
- base_uri: 'optional - Base URI for OpenAI - Use private base OR defaults to https://api.openai.com/v1'
40
- key: 'required - OpenAI API Key'
41
- model: 'optional - OpenAI model to use'
42
- system_role_content: 'You are an ethically hacking OpenAI agent.'
43
- temp: 'optional - OpenAI temperature'
44
-
45
- ollama:
46
- base_uri: 'required - Base URI for Open WebUI - e.g. https://ollama.local'
47
- key: 'required - Open WebUI API Key Under Settings >> Account >> JWT Token'
48
- model: 'required - Ollama model to use'
49
- system_role_content: 'You are an ethically hacking Ollama agent.'
50
- temp: 'optional - Ollama temperature'
51
-
52
57
  meshtastic:
53
58
  psks:
54
59
  LongFast: 'required - PSK for LongFast channel'
data/lib/pwn/ai/grok.rb CHANGED
@@ -25,6 +25,13 @@ module PWN
25
25
 
26
26
  private_class_method def self.grok_rest_call(opts = {})
27
27
  token = opts[:token]
28
+ if token.nil?
29
+ PWN::Plugins::AuthenticationHelper.mask_password(
30
+ prompt: 'Grok Key'
31
+ )
32
+ PWN::CONFIG[:ai][:grok][:key] = token
33
+ end
34
+
28
35
  http_method = if opts[:http_method].nil?
29
36
  :get
30
37
  else
data/lib/pwn/ai/ollama.rb CHANGED
@@ -27,6 +27,13 @@ module PWN
27
27
  private_class_method def self.ollama_rest_call(opts = {})
28
28
  base_uri = opts[:base_uri]
29
29
  token = opts[:token]
30
+ if token.nil?
31
+ PWN::Plugins::AuthenticationHelper.mask_password(
32
+ prompt: 'Ollama Key'
33
+ )
34
+ PWN::CONFIG[:ai][:ollama][:key] = token
35
+ end
36
+
30
37
  http_method = if opts[:http_method].nil?
31
38
  :get
32
39
  else
@@ -26,6 +26,13 @@ module PWN
26
26
 
27
27
  private_class_method def self.open_ai_rest_call(opts = {})
28
28
  token = opts[:token]
29
+ if token.nil?
30
+ PWN::Plugins::AuthenticationHelper.mask_password(
31
+ prompt: 'OpenAI Key'
32
+ )
33
+ PWN::CONFIG[:ai][:openai][:key] = token
34
+ end
35
+
29
36
  http_method = if opts[:http_method].nil?
30
37
  :get
31
38
  else
data/lib/pwn/config.rb ADDED
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ module PWN
7
+ # Used to manage PWN configuration settings within PWN drivers.
8
+ module Config
9
+ # Supported Method Parameters::
10
+ # PWN::Config.refresh(
11
+ # pwn_config_path: 'optional - Path to pwn.yaml file. Defaults to ~/.pwn/pwn.yaml',
12
+ # pwn_decryptor_path: 'optional - Path to pwn.decryptor.yaml file. Defaults to ~/.pwn/pwn.decryptor.yaml'
13
+ # )
14
+
15
+ public_class_method def self.refresh(opts = {})
16
+ pwn_config_root = "#{Dir.home}/.pwn"
17
+ FileUtils.mkdir_p(pwn_config_root)
18
+
19
+ pwn_config_path = opts[:pwn_config_path] ||= "#{pwn_config_root}/pwn.yaml"
20
+ raise "PWN Config (#{pwn_config_path}) does not exist!" unless File.exist?(pwn_config_path)
21
+
22
+ is_encrypted = PWN::Plugins::Vault.file_encrypted?(file: pwn_config_path)
23
+
24
+ if is_encrypted
25
+ pwn_decryptor_path = opts[:pwn_decryptor_path] ||= "#{pwn_config_root}/pwn.decryptor.yaml"
26
+ raise "PWN Decryptor (#{pwn_decryptor_path}) does not exist!" unless File.exist?(pwn_decryptor_path)
27
+
28
+ pwn_decryptor = YAML.load_file(pwn_decryptor_path, symbolize_names: true)
29
+
30
+ key = opts[:key] ||= pwn_decryptor[:key] ||= ENV.fetch('PWN_DECRYPTOR_KEY')
31
+ key = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption Key') if key.nil?
32
+
33
+ iv = opts[:iv] ||= pwn_decryptor[:iv] ||= ENV.fetch('PWN_DECRYPTOR_IV')
34
+ iv = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption IV') if iv.nil?
35
+
36
+ config = PWN::Plugins::Vault.dump(
37
+ file: pwn_config_path,
38
+ key: key,
39
+ iv: iv
40
+ )
41
+ else
42
+ config = YAML.load_file(pwn_config_path, symbolize_names: true)
43
+ end
44
+
45
+ valid_ai_engines = %i[
46
+ grok
47
+ openai
48
+ ollama
49
+ ]
50
+
51
+ engine = config[:ai][:active].to_s.downcase.to_sym
52
+ raise "ERROR: Unsupported AI Engine: #{engine} in #{pwn_config_path}. Supported AI Engines:\n#{valid_ai_engines.inspect}" unless valid_ai_engines.include?(engine)
53
+
54
+ model = config[:ai][engine][:model]
55
+ system_role_content = config[:ai][engine][:system_role_content]
56
+
57
+ # Reset the ai response history on config refresh
58
+ config[:ai][engine][:response_history] = {
59
+ id: '',
60
+ object: '',
61
+ model: model,
62
+ usage: {},
63
+ choices: [
64
+ {
65
+ role: 'system',
66
+ content: system_role_content
67
+ }
68
+ ]
69
+ }
70
+
71
+ # These two lines should be immutable for the session
72
+ config[:pwn_config_path] = pwn_config_path
73
+ config[:pwn_decryptor_path] = pwn_decryptor_path if is_encrypted
74
+
75
+ Pry.config.refresh = false if defined?(Pry)
76
+
77
+ PWN.send(:remove_const, :CONFIG) if PWN.const_defined?(:CONFIG)
78
+ PWN.const_set(:CONFIG, config)
79
+ end
80
+ end
81
+ end
@@ -18,11 +18,7 @@ module PWN
18
18
  mode = opts[:mode]
19
19
 
20
20
  proc do |_target_self, _nest_level, pi|
21
- if Pry.config.refresh
22
- # puts "Refreshing PWN env via #{opts[:yaml_config_path]}"
23
- opts[:pi] = pi
24
- PWN::Plugins::Vault.refresh_config_for_repl(opts)
25
- end
21
+ pi.config.pwn = PWN::Config.refresh(opts) if Pry.config.refresh
26
22
 
27
23
  pi.config.pwn_repl_line += 1
28
24
  line_pad = format(
@@ -48,13 +44,13 @@ module PWN
48
44
  end
49
45
 
50
46
  if pi.config.pwn_ai
51
- ai_engine = pi.config.pwn[:ai_engine]
52
- model = pi.config.pwn[ai_engine][:model]
53
- system_role_content = pi.config.pwn[ai_engine][:system_role_content]
54
- temp = pi.config.pwn[ai_engine][:temp]
55
- pname = "pwn.ai:#{ai_engine}"
56
- pname = "pwn.ai:#{ai_engine}/#{model}" if model
57
- pname = "pwn.ai:#{ai_engine}/#{model}.SPEAK" if pi.config.pwn_ai_speak
47
+ engine = pi.config.pwn[:ai][:active].to_s.downcase.to_sym
48
+ model = pi.config.pwn[:ai][engine][:model]
49
+ system_role_content = pi.config.pwn[:ai][engine][:system_role_content]
50
+ temp = pi.config.pwn[:ai][engine][:temp]
51
+ pname = "pwn.ai:#{engine}"
52
+ pname = "pwn.ai:#{engine}/#{model}" if model
53
+ pname = "pwn.ai:#{engine}/#{model}.SPEAK" if pi.config.pwn_ai_speak
58
54
  pi.config.prompt_name = pname
59
55
 
60
56
  name = "\001\e[1m\002\001\e[33m\002#{pi.config.prompt_name}\001\e[0m\002"
@@ -306,12 +302,12 @@ module PWN
306
302
  next unless dm_agent == nick
307
303
 
308
304
  response_history = ai_agents[dm_agent.to_sym][:response_history]
309
- ai_engine = pi.config.pwn[:ai_engine]
310
- base_uri = pi.config.pwn[ai_engine][:base_uri]
311
- key = pi.config.pwn[ai_engine][:key] ||= ''
312
- temp = pi.config.pwn[ai_engine][:temp]
313
- model = pi.config.pwn[ai_engine][:model]
314
- system_role_content = pi.config.pwn[ai_engine][:system_role_content]
305
+ engine = pi.config.pwn[:ai][:active].to_s.downcase.to_sym
306
+ base_uri = pi.config.pwn[:ai][engine][:base_uri]
307
+ key = pi.config.pwn[:ai][engine][:key] ||= ''
308
+ temp = pi.config.pwn[:ai][engine][:temp]
309
+ model = pi.config.pwn[:ai][engine][:model]
310
+ system_role_content = pi.config.pwn[:ai][engine][:system_role_content]
315
311
 
316
312
  users_in_chan = PWN::Plugins::IRC.names(
317
313
  irc_obj: irc_obj,
@@ -342,7 +338,7 @@ module PWN
342
338
  message.
343
339
  "
344
340
 
345
- case ai_engine
341
+ case engine
346
342
  when :grok
347
343
  response = PWN::AI::Grok.chat(
348
344
  base_uri: base_uri,
@@ -459,29 +455,29 @@ module PWN
459
455
  end
460
456
  end
461
457
 
462
- Pry::Commands.create_command 'pwn-vault-edit' do
458
+ Pry::Commands.create_command 'pwn-vault' do
463
459
  description 'Edit the pwn.yaml configuration file.'
464
460
 
465
461
  def process
466
462
  pi = pry_instance
467
- yaml_config_path = pi.config.yaml_config_path ||= "#{Dir.home}/.pwn/pwn.yaml"
468
- unless File.exist?(yaml_config_path)
469
- puts "ERROR: pwn.yaml not found: #{yaml_config_path}"
463
+ pwn_config_path = pi.config.pwn_config_path ||= "#{Dir.home}/.pwn/pwn.yaml"
464
+ unless File.exist?(pwn_config_path)
465
+ puts "ERROR: pwn.yaml not found: #{pwn_config_path}"
470
466
  return
471
467
  end
472
468
 
473
- yaml_decryptor_path = pi.config.yaml_decryptor_path ||= "#{Dir.home}/.pwn/pwn.decryptor.yaml"
474
- unless File.exist?(yaml_decryptor_path)
475
- puts "ERROR: pwn.decryptor.yaml not found: #{yaml_decryptor_path}"
469
+ pwn_decryptor_path = pi.config.pwn_decryptor_path ||= "#{Dir.home}/.pwn/pwn.decryptor.yaml"
470
+ unless File.exist?(pwn_decryptor_path)
471
+ puts "ERROR: pwn.decryptor.yaml not found: #{pwn_decryptor_path}"
476
472
  return
477
473
  end
478
474
 
479
- decryptor = YAML.load_file(yaml_decryptor_path, symbolize_names: true)
475
+ decryptor = YAML.load_file(pwn_decryptor_path, symbolize_names: true)
480
476
  key = decryptor[:key]
481
477
  iv = decryptor[:iv]
482
478
 
483
479
  PWN::Plugins::Vault.edit(
484
- file: yaml_config_path,
480
+ file: pwn_config_path,
485
481
  key: key,
486
482
  iv: iv
487
483
  )
@@ -539,9 +535,7 @@ module PWN
539
535
 
540
536
  # Initialize pwn.yaml Configuration using :before_session Hook
541
537
  Pry.config.hooks.add_hook(:before_session, :init_opts) do |_output, _binding, pi|
542
- # puts "Refreshing PWN env via #{opts[:yaml_config_path]}"
543
- opts[:pi] = pi
544
- PWN::Plugins::Vault.refresh_config_for_repl(opts)
538
+ pi.config.pwn = PWN::Config.refresh(opts)
545
539
  end
546
540
 
547
541
  Pry.config.hooks.add_hook(:after_read, :pwn_asm_hook) do |request, pi|
@@ -585,23 +579,16 @@ module PWN
585
579
  if pi.config.pwn_ai && !request.chomp.empty?
586
580
  request = pi.input.line_buffer.to_s
587
581
  debug = pi.config.pwn_ai_debug
588
- ai_engine = pi.config.pwn[:ai_engine]
589
- base_uri = pi.config.pwn[ai_engine][:base_uri]
590
- key = pi.config.pwn[ai_engine][:key] ||= ''
591
- if key.empty?
592
- key = PWN::Plugins::AuthenticationHelper.mask_password(
593
- prompt: 'pwn-ai Key'
594
- )
595
- pi.config.pwn[ai_engine][:key] = key
596
- end
597
-
598
- response_history = pi.config.pwn_ai_response_history
582
+ engine = pi.config.pwn[:ai][:active].to_s.downcase.to_sym
583
+ base_uri = pi.config.pwn[:ai][engine][:base_uri]
584
+ key = pi.config.pwn[:ai][engine][:key] ||= ''
585
+ response_history = pi.config.pwn[:ai][engine][:response_history]
599
586
  speak_answer = pi.config.pwn_ai_speak
600
- model = pi.config.pwn[ai_engine][:model]
601
- system_role_content = pi.config.pwn[ai_engine][:system_role_content]
602
- temp = pi.config.pwn[ai_engine][:temp]
587
+ model = pi.config.pwn[:ai][engine][:model]
588
+ system_role_content = pi.config.pwn[:ai][engine][:system_role_content]
589
+ temp = pi.config.pwn[:ai][engine][:temp]
603
590
 
604
- case ai_engine
591
+ case engine
605
592
  when :grok
606
593
  response = PWN::AI::Grok.chat(
607
594
  base_uri: base_uri,
@@ -639,7 +626,7 @@ module PWN
639
626
  spinner: true
640
627
  )
641
628
  else
642
- raise "ERROR: Unsupported AI Engine: #{ai_engine}"
629
+ raise "ERROR: Unsupported AI Engine: #{engine}"
643
630
  end
644
631
  # puts response.inspect
645
632
 
@@ -668,7 +655,7 @@ module PWN
668
655
  pp response_history
669
656
  puts "\nresponse_history[:choices] Length: #{response_history[:choices].length}\n" unless response_history.nil?
670
657
  end
671
- pi.config.pwn_ai_response_history = response_history
658
+ pi.config.pwn[:ai][engine][:response_history] = response_history
672
659
  end
673
660
  end
674
661
  rescue StandardError => e
@@ -683,13 +670,10 @@ module PWN
683
670
  public_class_method def self.start(opts = {})
684
671
  # Monkey Patch Pry, add commands, && hooks
685
672
  PWN::Plugins::MonkeyPatch.pry
686
- add_commands
687
-
688
673
  pwn_config_root = "#{Dir.home}/.pwn"
689
- FileUtils.mkdir_p(pwn_config_root)
690
- opts[:yaml_config_path] ||= "#{pwn_config_root}/pwn.yaml"
691
- opts[:yaml_decryptor_path] ||= "#{pwn_config_root}/pwn.decryptor.yaml"
674
+ Pry.config.history_file = "#{pwn_config_root}/pwn_history"
692
675
 
676
+ add_commands
693
677
  add_hooks(opts)
694
678
 
695
679
  # Define PS1 Prompt
@@ -15,7 +15,7 @@ module PWN
15
15
  # iv: 'required - iv to decrypt'
16
16
  # )
17
17
 
18
- def self.refresh_encryption_secrets(opts = {})
18
+ public_class_method def self.refresh_encryption_secrets(opts = {})
19
19
  file = opts[:file].to_s.scrub if File.exist?(opts[:file].to_s.scrub)
20
20
  key = opts[:key]
21
21
  iv = opts[:iv]
@@ -234,84 +234,6 @@ module PWN
234
234
  raise e
235
235
  end
236
236
 
237
- # Supported Method Parameters::
238
- # PWN::Plugins::Vault.refresh_config_for_repl(
239
- # yaml_config_path: 'required - full path to pwn.yaml file',
240
- # pi: 'optional - Pry instance (default: Pry)',
241
- # yaml_decryptor_path: 'optional - full path to decryption YAML file'
242
- # )
243
- public_class_method def self.refresh_config_for_repl(opts = {})
244
- yaml_config_path = opts[:yaml_config_path]
245
-
246
- return false unless File.exist?(yaml_config_path)
247
-
248
- pi = opts[:pi]
249
- raise 'ERROR: Pry instance is required.' if pi.nil?
250
-
251
- is_encrypted = PWN::Plugins::Vault.file_encrypted?(file: yaml_config_path)
252
-
253
- if is_encrypted
254
- # TODO: Implement "something you know, something you have, && something you are?"
255
- yaml_decryptor_path = opts[:yaml_decryptor_path] ||= "#{Dir.home}/.pwn/pwn.decryptor.yaml"
256
- raise "ERROR: #{yaml_decryptor_path} does not exist." unless File.exist?(yaml_decryptor_path)
257
-
258
- yaml_decryptor = YAML.load_file(yaml_decryptor_path, symbolize_names: true)
259
-
260
- key = opts[:key] ||= yaml_decryptor[:key] ||= ENV.fetch('PWN_DECRYPTOR_KEY')
261
- key = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption Key') if key.nil?
262
-
263
- iv = opts[:iv] ||= yaml_decryptor[:iv] ||= ENV.fetch('PWN_DECRYPTOR_IV')
264
- iv = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption IV') if iv.nil?
265
-
266
- yaml_config = PWN::Plugins::Vault.dump(
267
- file: yaml_config_path,
268
- key: key,
269
- iv: iv
270
- )
271
- else
272
- yaml_config = YAML.load_file(yaml_config_path, symbolize_names: true)
273
- end
274
-
275
- valid_ai_engines = %i[
276
- grok
277
- openai
278
- ollama
279
- ]
280
-
281
- # Convert ai_engine to symbol and downcase to ensure stability
282
- yaml_config[:ai_engine] = yaml_config[:ai_engine].to_s.downcase.to_sym
283
- pi.config.pwn = yaml_config
284
- ai_engine = pi.config.pwn[:ai_engine]
285
- raise "ERROR: Unsupported AI Engine: #{ai_engine} in #{yaml_config_path}. Supported AI Engines:\n#{valid_ai_engines.inspect}" unless valid_ai_engines.include?(ai_engine)
286
-
287
- model = pi.config.pwn[ai_engine][:model]
288
- system_role_content = pi.config.pwn[ai_engine][:system_role_content]
289
-
290
- # Reset the ai response history for new configurations
291
- pi.config.pwn_ai_response_history = {
292
- id: '',
293
- object: '',
294
- model: model,
295
- usage: {},
296
- choices: [
297
- {
298
- role: 'system',
299
- content: system_role_content
300
- }
301
- ]
302
- }
303
-
304
- # These two lines should be immutable for the session
305
- pi.config.pwn[:yaml_config_path] = yaml_config_path
306
- pi.config.pwn[:yaml_decryptor_path] = yaml_decryptor_path if is_encrypted
307
-
308
- Pry.config.refresh = false
309
-
310
- true
311
- rescue StandardError => e
312
- raise e
313
- end
314
-
315
237
  # Author(s):: 0day Inc. <support@0dayinc.com>
316
238
 
317
239
  public_class_method def self.authors
@@ -364,12 +286,6 @@ module PWN
364
286
  file: 'required - file to check if encrypted'
365
287
  )
366
288
 
367
- #{self}.refresh_config_for_repl(
368
- yaml_config_path: 'required - full path to pwn.yaml file',
369
- pi: 'optional - Pry instance (default: Pry)',
370
- yaml_decryptor_path: 'optional - full path to decryption YAML file'
371
- )
372
-
373
289
  #{self}.authors
374
290
  "
375
291
  end
@@ -14,13 +14,7 @@ module PWN
14
14
  # PWN::Reports::SAST.generate(
15
15
  # dir_path: 'optional - Directory path to save the report (defaults to .)',
16
16
  # results_hash: 'optional - Hash containing the results of the SAST analysis (defaults to empty hash structure)',
17
- # report_name: 'optional - Name of the report file (defaults to current directory name)',
18
- # ai_engine: 'optional - AI engine to use for analysis (:grok, :ollama, or :openai)',
19
- # ai_model: 'optionnal - AI Model to Use for Respective AI Engine (e.g., grok-4i-0709, chargpt-4o-latest, llama-3.1, etc.)',
20
- # ai_key: 'optional - AI Key/Token for Respective AI Engine',
21
- # ai_base_uri: 'optional - AI FQDN (Only Required for "ollama" AI Engine)',
22
- # ai_system_role_content: 'optional - AI System Role Content (Defaults to "Confidence score of 0-10 this is vulnerable (0 being not vulnerable, moving upwards in confidence of exploitation). Provide additional context to assist penetration tester assessment.")',
23
- # ai_temp: 'optional - AI Temperature (Defaults to 0.1)'
17
+ # report_name: 'optional - Name of the report file (defaults to current directory name)'
24
18
  # )
25
19
 
26
20
  public_class_method def self.generate(opts = {})
@@ -31,28 +25,21 @@ module PWN
31
25
  }
32
26
  report_name = opts[:report_name] ||= File.basename(Dir.pwd)
33
27
 
34
- ai_engine = opts[:ai_engine]
35
- if ai_engine
36
- ai_engine = ai_engine.to_s.to_sym
37
- valid_ai_engines = %i[grok ollama openai]
38
- raise "ERROR: Invalid AI Engine. Valid options are: #{valid_ai_engines.join(', ')}" unless valid_ai_engines.include?(ai_engine)
28
+ ai_instrospection = PWN::CONFIG[:ai][:introspection]
29
+ if ai_instrospection
30
+ engine = PWN::CONFIG[:ai][:active].to_s.downcase.to_sym
31
+ base_uri = PWN::CONFIG[:ai][engine][:base_uri]
32
+ model = PWN::CONFIG[:ai][engine][:model]
33
+ key = PWN::CONFIG[:ai][engine][:key]
34
+ system_role_content = PWN::CONFIG[:ai][engine][:system_role_content]
35
+ temp = PWN::CONFIG[:ai][engine][:temp]
39
36
 
40
- ai_base_uri = opts[:ai_base_uri]
41
- raise 'ERROR: FQDN for Ollama AI engine is required.' if ai_engine == :ollama && ai_base_uri.nil?
42
-
43
- ai_model = opts[:ai_model]
44
- raise 'ERROR: AI Model is required for AI engine ollama.' if ai_engine == :ollama && ai_model.nil?
45
-
46
- ai_key = opts[:ai_key] ||= PWN::Plugins::AuthenticationHelper.mask_password(prompt: "#{ai_engine} Token")
47
- ai_system_role_content = opts[:ai_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 higher than 75% in which a code fic should also be included.'
48
- ai_temp = opts[:ai_temp] ||= 0.1
49
-
50
- puts "Analyzing source code using AI engine: #{ai_engine}\nModel: #{ai_model}\nSystem Role Content: #{ai_system_role_content}\nTemperature: #{ai_temp}"
37
+ puts "Analyzing source code using AI engine: #{engine}\nModel: #{model}\nSystem Role Content: #{system_role_content}\nTemperature: #{temp}"
51
38
  end
52
39
 
53
40
  # Calculate percentage of AI analysis based on the number of entries
54
41
  total_entries = results_hash[:data].sum { |entry| entry[:line_no_and_contents].size }
55
- puts "Total entries to analyze: #{total_entries}" if ai_engine
42
+ puts "Total entries to analyze: #{total_entries}" if engine
56
43
 
57
44
  percent_complete = 0.0
58
45
  entry_count = 0
@@ -79,37 +66,39 @@ module PWN
79
66
  response = nil
80
67
  author = src_detail[:author].to_s.scrub.chomp.strip
81
68
 
82
- case ai_engine
83
- when :grok
84
- response = PWN::AI::Grok.chat(
85
- base_uri: ai_base_uri,
86
- token: ai_key,
87
- model: ai_model,
88
- system_role_content: ai_system_role_content,
89
- temp: ai_temp,
90
- request: request.chomp,
91
- spinner: false
92
- )
93
- when :ollama
94
- response = PWN::AI::Ollama.chat(
95
- base_uri: ai_base_uri,
96
- token: ai_key,
97
- model: ai_model,
98
- system_role_content: ai_system_role_content,
99
- temp: ai_temp,
100
- request: request.chomp,
101
- spinner: false
102
- )
103
- when :openai
104
- response = PWN::AI::OpenAI.chat(
105
- base_uri: ai_base_uri,
106
- token: ai_key,
107
- model: ai_model,
108
- system_role_content: ai_system_role_content,
109
- temp: ai_temp,
110
- request: request.chomp,
111
- spinner: false
112
- )
69
+ if ai_instrospection
70
+ case engine
71
+ when :grok
72
+ response = PWN::AI::Grok.chat(
73
+ base_uri: base_uri,
74
+ token: key,
75
+ model: model,
76
+ system_role_content: system_role_content,
77
+ temp: temp,
78
+ request: request.chomp,
79
+ spinner: false
80
+ )
81
+ when :ollama
82
+ response = PWN::AI::Ollama.chat(
83
+ base_uri: base_uri,
84
+ token: key,
85
+ model: model,
86
+ system_role_content: system_role_content,
87
+ temp: temp,
88
+ request: request.chomp,
89
+ spinner: false
90
+ )
91
+ when :openai
92
+ response = PWN::AI::OpenAI.chat(
93
+ base_uri: base_uri,
94
+ token: key,
95
+ model: model,
96
+ system_role_content: system_role_content,
97
+ temp: temp,
98
+ request: request.chomp,
99
+ spinner: false
100
+ )
101
+ end
113
102
  end
114
103
 
115
104
  ai_analysis = nil
@@ -118,6 +107,7 @@ module PWN
118
107
  ai_analysis = response[:choices].last[:content] if response[:choices].last.keys.include?(:content)
119
108
  # puts "AI Analysis Progress: #{percent_complete}% Line: #{line_no} | Author: #{author} | AI Analysis: #{ai_analysis}\n\n\n" if ai_analysis
120
109
  end
110
+ # TODO: Make results prettier in the HTML report
121
111
  src_detail[:ai_analysis] = ai_analysis.to_s.scrub.chomp.strip
122
112
 
123
113
  spin.update(
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.428'
4
+ VERSION = '0.5.430'
5
5
  end
data/lib/pwn.rb CHANGED
@@ -12,19 +12,22 @@ module PWN
12
12
  autoload :AWS, 'pwn/aws'
13
13
  autoload :Banner, 'pwn/banner'
14
14
  autoload :Blockchain, 'pwn/blockchain'
15
+ autoload :Config, 'pwn/config'
15
16
  autoload :FFI, 'pwn/ffi'
16
17
  autoload :Plugins, 'pwn/plugins'
17
18
  autoload :Reports, 'pwn/reports'
18
19
  autoload :SAST, 'pwn/sast'
19
20
  autoload :WWW, 'pwn/www'
20
- # TODO: If pwn.yaml is present, attempt to decrypt it, and initialize the YAML config
21
- # Do this within PWN::Plugins::AuthenticationHelper?
22
21
 
23
22
  # Display a List of Every PWN Module
24
23
 
25
24
  public_class_method def self.help
26
25
  constants.sort
27
26
  end
27
+
28
+ # Initialize PWN configuration file
29
+ # PWN::CONFIG is the constant that stores the configuration data
30
+ PWN::Config.refresh
28
31
  rescue StandardError => e
29
32
  puts e.backtrace
30
33
  raise e
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PWN::Config do
6
+ it 'should return data for refresh method' do
7
+ config_response = PWN::Config.refresh
8
+ expect(config_response).not_to be_nil
9
+ end
10
+ end
@@ -438,6 +438,17 @@
438
438
  {"messages":[{"role":"user","content":"PWN::Banner::WhiteRabbit.authors Usage"},{"role":"assistant","content":"`PWN::Banner::WhiteRabbit.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
439
439
  {"messages":[{"role":"user","content":"PWN::Banner::WhiteRabbit.get Usage"},{"role":"assistant","content":"`PWN::Banner::WhiteRabbit.get`: Supported Method Parameters\n\nPWN::Banner::WhiteRabbit.get\n"}]}
440
440
  {"messages":[{"role":"user","content":"PWN::Banner::WhiteRabbit.help Usage"},{"role":"assistant","content":"`PWN::Banner::WhiteRabbit.help`: "}]}
441
+ {"messages":[{"role":"user","content":"PWN::Blockchain.help Usage"},{"role":"assistant","content":"`PWN::Blockchain.help`: "}]}
442
+ {"messages":[{"role":"user","content":"PWN::Blockchain::BTC.authors Usage"},{"role":"assistant","content":"`PWN::Blockchain::BTC.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
443
+ {"messages":[{"role":"user","content":"PWN::Blockchain::BTC.btc_rest_call Usage"},{"role":"assistant","content":"`PWN::Blockchain::BTC.btc_rest_call`: Supported Method Parameters\n\nbtc_rest_call(\n\nhttp_method: 'optional HTTP method (defaults to GET)\nrest_call: 'required rest call to make per the schema',\nparams: 'optional params passed in the URI or HTTP Headers',\nhttp_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',\ntimeout: 'optional timeout in seconds (defaults to 180)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
444
+ {"messages":[{"role":"user","content":"PWN::Blockchain::BTC.get_block_details Usage"},{"role":"assistant","content":"`PWN::Blockchain::BTC.get_block_details`: Supported Method Parameters\n\nPWN::Blockchain::BTC.get_block_details(\n\nheight: 'required - block height number',\ntoken: 'optional - API token for higher rate limits'\n\n)\n"}]}
445
+ {"messages":[{"role":"user","content":"PWN::Blockchain::BTC.get_latest_block Usage"},{"role":"assistant","content":"`PWN::Blockchain::BTC.get_latest_block`: Supported Method Parameters\n\nlatest_block = PWN::Blockchain::BTC.get_latest_block(\n\ntoken: 'optional - API token for higher rate limits'\n\n)\n"}]}
446
+ {"messages":[{"role":"user","content":"PWN::Blockchain::BTC.help Usage"},{"role":"assistant","content":"`PWN::Blockchain::BTC.help`: "}]}
447
+ {"messages":[{"role":"user","content":"PWN::Blockchain::ETH.authors Usage"},{"role":"assistant","content":"`PWN::Blockchain::ETH.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
448
+ {"messages":[{"role":"user","content":"PWN::Blockchain::ETH.eth_rest_call Usage"},{"role":"assistant","content":"`PWN::Blockchain::ETH.eth_rest_call`: Supported Method Parameters\n\neth_rest_call(\n\nhttp_method: 'optional HTTP method (defaults to GET)\nrest_call: 'required rest call to make per the schema',\nparams: 'optional params passed in the URI or HTTP Headers',\nhttp_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',\ntimeout: 'optional timeout in seconds (defaults to 180)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
449
+ {"messages":[{"role":"user","content":"PWN::Blockchain::ETH.get_block_details Usage"},{"role":"assistant","content":"`PWN::Blockchain::ETH.get_block_details`: Supported Method Parameters\n\nPWN::Blockchain::ETH.get_block_details(\n\nheight: 'required - block height number',\ntoken: 'optional - API token for higher rate limits'\n\n)\n"}]}
450
+ {"messages":[{"role":"user","content":"PWN::Blockchain::ETH.get_latest_block Usage"},{"role":"assistant","content":"`PWN::Blockchain::ETH.get_latest_block`: Supported Method Parameters\n\nlatest_block = PWN::Blockchain::ETH.get_latest_block(\n\ntoken: 'optional - API token for higher rate limits'\n\n)\n"}]}
451
+ {"messages":[{"role":"user","content":"PWN::Blockchain::ETH.help Usage"},{"role":"assistant","content":"`PWN::Blockchain::ETH.help`: "}]}
441
452
  {"messages":[{"role":"user","content":"PWN::FFI.help Usage"},{"role":"assistant","content":"`PWN::FFI.help`: "}]}
442
453
  {"messages":[{"role":"user","content":"PWN::FFI::Stdio.authors Usage"},{"role":"assistant","content":"`PWN::FFI::Stdio.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
443
454
  {"messages":[{"role":"user","content":"PWN::FFI::Stdio.help Usage"},{"role":"assistant","content":"`PWN::FFI::Stdio.help`: "}]}
@@ -1023,7 +1034,7 @@
1023
1034
  {"messages":[{"role":"user","content":"PWN::Plugins::Vault.encrypt Usage"},{"role":"assistant","content":"`PWN::Plugins::Vault.encrypt`: Supported Method Parameters\n\nPWN::Plugins::Vault.encrypt(\n\nfile: 'required - file to encrypt',\nkey: 'required - key to decrypt',\niv: 'required - iv to decrypt'\n\n)\n"}]}
1024
1035
  {"messages":[{"role":"user","content":"PWN::Plugins::Vault.file_encrypted? Usage"},{"role":"assistant","content":"`PWN::Plugins::Vault.file_encrypted?`: Supported Method Parameters\n\nPWN::Plugins::Vault.file_encrypted?(\n\nfile: 'required - file to check if encrypted'\n\n)\n"}]}
1025
1036
  {"messages":[{"role":"user","content":"PWN::Plugins::Vault.help Usage"},{"role":"assistant","content":"`PWN::Plugins::Vault.help`: "}]}
1026
- {"messages":[{"role":"user","content":"PWN::Plugins::Vault.refresh_config_for_repl Usage"},{"role":"assistant","content":"`PWN::Plugins::Vault.refresh_config_for_repl`: Supported Method Parameters\n\nPWN::Plugins::Vault.refresh_config_for_repl(\n\n yaml_config_path: 'required - full path to pwn.yaml file',\n pi: 'optional - Pry instance (default: Pry)',\n decryption_file: 'optional - full path to decryption YAML file'\n)\n"}]}
1037
+ {"messages":[{"role":"user","content":"PWN::Plugins::Vault.refresh_config_for_repl Usage"},{"role":"assistant","content":"`PWN::Plugins::Vault.refresh_config_for_repl`: Supported Method Parameters\n\nPWN::Plugins::Vault.refresh_config_for_repl(\n\n yaml_config_path: 'required - full path to pwn.yaml file',\n pi: 'optional - Pry instance (default: Pry)',\n yaml_decryptor_path: 'optional - full path to decryption YAML file'\n)\n"}]}
1027
1038
  {"messages":[{"role":"user","content":"PWN::Plugins::Vault.refresh_encryption_secrets Usage"},{"role":"assistant","content":"`PWN::Plugins::Vault.refresh_encryption_secrets`: Supported Method Parameters\n\nPWN::Plugins::Vault.refresh_encryption_secrets(\n\nfile: 'required - file to encrypt with new key and iv',\nkey: 'required - key to decrypt',\niv: 'required - iv to decrypt'\n\n)\n"}]}
1028
1039
  {"messages":[{"role":"user","content":"PWN::Plugins::Voice.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::Voice.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1029
1040
  {"messages":[{"role":"user","content":"PWN::Plugins::Voice.help Usage"},{"role":"assistant","content":"`PWN::Plugins::Voice.help`: "}]}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.428
4
+ version: 0.5.430
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -477,14 +477,14 @@ dependencies:
477
477
  requirements:
478
478
  - - '='
479
479
  - !ruby/object:Gem::Version
480
- version: 1.0.2
480
+ version: 2.0.0
481
481
  type: :runtime
482
482
  prerelease: false
483
483
  version_requirements: !ruby/object:Gem::Requirement
484
484
  requirements:
485
485
  - - '='
486
486
  - !ruby/object:Gem::Version
487
- version: 1.0.2
487
+ version: 2.0.0
488
488
  - !ruby/object:Gem::Dependency
489
489
  name: mail
490
490
  requirement: !ruby/object:Gem::Requirement
@@ -1832,6 +1832,7 @@ files:
1832
1832
  - lib/pwn/blockchain.rb
1833
1833
  - lib/pwn/blockchain/btc.rb
1834
1834
  - lib/pwn/blockchain/eth.rb
1835
+ - lib/pwn/config.rb
1835
1836
  - lib/pwn/ffi.rb
1836
1837
  - lib/pwn/ffi/stdio.rb
1837
1838
  - lib/pwn/plugins.rb
@@ -2180,6 +2181,7 @@ files:
2180
2181
  - spec/lib/pwn/blockchain/btc_spec.rb
2181
2182
  - spec/lib/pwn/blockchain/eth_spec.rb
2182
2183
  - spec/lib/pwn/blockchain_spec.rb
2184
+ - spec/lib/pwn/config_spec.rb
2183
2185
  - spec/lib/pwn/ffi/stdio_spec.rb
2184
2186
  - spec/lib/pwn/ffi_spec.rb
2185
2187
  - spec/lib/pwn/plugins/android_spec.rb