pwn 0.5.479 → 0.5.481

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: 46bdd92940f5057f6b82e630831a4093fab6663faf52a0ec132bd76ee51d4e7b
4
- data.tar.gz: 15d6da128800eccfdcb442ba83f2d2b590ea78824c27afdb5cdb5185798857b8
3
+ metadata.gz: f8f0ace17ae36a1a8e60fd015ad3c0a37dcaa22fd3490806b4fc033bd974ae70
4
+ data.tar.gz: 4a3b5eb17658a0a7c63e6e49bd87cad5740112ed1c2d77207fd0cde9949a50d6
5
5
  SHA512:
6
- metadata.gz: 1f85fef86b43520d167d4ee6fd2270aff7c665b21c9011691aab17fb944f52ee1bbc9a66e6daa2fefaae83c05dbca5631f636b82a62169d4db42cb7f21ae4ebf
7
- data.tar.gz: 964c7ac4f2b29c3b8807dc29e23a90ad6a1ce60ca2d99f4bffd49fc3104aa2abed45f97bd42a04e44784e0c8c05a3918e6e9438e4261f3dbd9277d6407fd271a
6
+ metadata.gz: d97493c22667ef54489045fd1bdd5ff8940701fa266e923849f8784feacc546be605154bc215a15823b9c424a4ba025e6bd95d3a1b3e86eba3d70987bc55c6c0
7
+ data.tar.gz: 0636daf478dff9780efe1b14478d40d06e109927c955d8d8c50f8c1c8e5c1bd7faaad9cfc8f98254084f4f384cdaa90c4698d563e57d386be38449193600c2e4
data/.rubocop.yml CHANGED
@@ -6,7 +6,7 @@ Layout/LineLength:
6
6
  Lint/UselessRescue:
7
7
  Enabled: false
8
8
  Metrics/AbcSize:
9
- Max: 537.6
9
+ Max: 552.3
10
10
  Metrics/BlockLength:
11
11
  Max: 292
12
12
  Metrics/BlockNesting:
data/Gemfile CHANGED
@@ -48,7 +48,7 @@ gem 'json_schemer', '2.4.0'
48
48
  gem 'jwt', '3.1.2'
49
49
  gem 'libusb', '0.7.2'
50
50
  gem 'luhn', '3.0.0'
51
- gem 'mail', '2.8.1'
51
+ gem 'mail', '2.9.0'
52
52
  gem 'meshtastic', '0.0.132'
53
53
  gem 'metasm', '1.0.5'
54
54
  gem 'mongo', '2.21.3'
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.479]:001 >>> PWN.help
40
+ pwn[v0.5.481]: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.479]:001 >>> PWN.help
55
+ pwn[v0.5.481]: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.479]:001 >>> PWN.help
65
+ pwn[v0.5.481]: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:
@@ -220,65 +220,85 @@ module PWN
220
220
  epic_name = opts[:epic_name]
221
221
  raise 'ERROR: epic_name cannot be nil when issue_type is :epic.' if issue_type == :epic && epic_name.nil?
222
222
 
223
- http_body = {
224
- fields: {
225
- project: {
226
- key: project_key
227
- },
228
- summary: summary,
229
- issuetype: {
230
- name: issue_type.to_s.capitalize
231
- },
232
- "#{epic_name_field_key}": epic_name,
233
- description: description
223
+ # Attempt create, dynamically omit unlicensed fields on failure
224
+ create_attempts, get_issue_attempts = 1
225
+ max_create_attempts, max_get_issue_attempts = 7
226
+ created_issue = nil
227
+ begin
228
+ http_body = {
229
+ fields: {
230
+ project: {
231
+ key: project_key
232
+ },
233
+ summary: summary,
234
+ issuetype: {
235
+ name: issue_type.to_s.capitalize
236
+ },
237
+ "#{epic_name_field_key}": epic_name,
238
+ description: description
239
+ }
234
240
  }
235
- }
236
-
237
- http_body[:fields].merge!(additional_fields[:fields])
238
-
239
- issue_resp = rest_call(
240
- http_method: :post,
241
- rest_call: 'issue',
242
- http_body: http_body
243
- )
244
- issue = issue_resp[:key]
245
241
 
246
- if attachments.any?
247
- attachments.each do |attachment|
248
- raise "ERROR: #{attachment} not found." unless File.exist?(attachment)
242
+ http_body[:fields].merge!(additional_fields[:fields])
249
243
 
250
- http_body = {
251
- multipart: true,
252
- file: File.new(attachment, 'rb')
253
- }
244
+ issue_resp = rest_call(
245
+ http_method: :post,
246
+ rest_call: 'issue',
247
+ http_body: http_body
248
+ )
249
+ issue = issue_resp[:key]
250
+
251
+ if attachments.any?
252
+ attachments.each do |attachment|
253
+ raise "ERROR: #{attachment} not found." unless File.exist?(attachment)
254
+
255
+ http_body = {
256
+ multipart: true,
257
+ file: File.new(attachment, 'rb')
258
+ }
259
+
260
+ rest_call(
261
+ http_method: :post,
262
+ rest_call: "issue/#{issue}/attachments",
263
+ http_body: http_body
264
+ )
265
+ end
266
+ end
254
267
 
255
- rest_call(
256
- http_method: :post,
257
- rest_call: "issue/#{issue}/attachments",
258
- http_body: http_body
268
+ if comment
269
+ issue_comment(
270
+ issue: issue,
271
+ comment_action: :add,
272
+ comment: comment
259
273
  )
260
274
  end
261
- end
262
275
 
263
- if comment
264
- issue_comment(
265
- issue: issue,
266
- comment_action: :add,
267
- comment: comment
268
- )
269
- end
276
+ begin
277
+ created_issue = get_issue(issue: issue)
278
+ rescue RuntimeError
279
+ raise 'ERROR: Max attempts reached for retrieving created issue.' if get_issue_attempts > max_get_issue_attempts
270
280
 
271
- created_issue = nil
272
- get_issue_attempts = 0
273
- max_get_issue_attempts = 7
274
- begin
275
- created_issue = get_issue(issue: issue)
276
- rescue RuntimeError
277
- raise 'ERROR: Max attempts reached for retrieving created issue.' if get_issue_attempts > max_get_issue_attempts
281
+ get_issue_attempts += 1
282
+ sleep 3
283
+ retry
284
+ end
285
+ rescue RestClient::ExceptionWithResponse => e
286
+ raise e if create_attempts >= max_create_attempts || e.response.body.to_s.empty?
278
287
 
279
- get_issue_attempts += 1
280
- sleep 7
281
- retry
288
+ json = JSON.parse(e.response.body, symbolize_names: true)
289
+ errors_hash = json[:errors] if json.is_a?(Hash)
290
+ if errors_hash.is_a?(Hash)
291
+ unlicensed_field_keys = errors_hash.select { |_fk, msg| msg.to_s =~ /unlicensed/i }.keys
292
+ if unlicensed_field_keys.any?
293
+ unlicensed_field_keys.each do |fk|
294
+ additional_fields[:fields].delete(fk.to_sym)
295
+ additional_fields[:fields].delete(fk.to_s)
296
+ end
297
+ @@logger.warn("Omitting unlicensed fields: #{unlicensed_field_keys.join(', ')} (attempt #{create_attempts}/#{max_create_attempts}). Retrying issue creation.") if defined?(@@logger)
298
+ create_attempts += 1
299
+ retry
300
+ end
301
+ end
282
302
  end
283
303
 
284
304
  created_issue
@@ -530,40 +550,15 @@ module PWN
530
550
  end
531
551
  end
532
552
 
533
- # Attempt create, dynamically omit unlicensed fields on failure
534
- attempt = 1
535
- max_attempts = 5
536
- cloned_issue = nil
537
- begin
538
- cloned_issue = create_issue(
539
- project_key: project_key,
540
- summary: summary,
541
- issue_type: issue_type,
542
- epic_name: epic_name,
543
- description: description,
544
- additional_fields: additional_fields,
545
- attachments: attachments
546
- )
547
- rescue RestClient::ExceptionWithResponse => e
548
- raise e if attempt >= max_attempts || e.response.body.to_s.empty?
549
-
550
- json = JSON.parse(e.response.body, symbolize_names: true)
551
- errors_hash = json[:errors] if json.is_a?(Hash)
552
- if errors_hash.is_a?(Hash)
553
- unlicensed_field_keys = errors_hash.select { |_fk, msg| msg.to_s =~ /unlicensed/i }.keys
554
- if unlicensed_field_keys.any?
555
- unlicensed_field_keys.each do |fk|
556
- additional_fields[:fields].delete(fk.to_sym)
557
- additional_fields[:fields].delete(fk.to_s)
558
- end
559
- @@logger.warn("Omitting unlicensed fields: #{unlicensed_field_keys.join(', ')} (attempt #{attempt}/#{max_attempts}). Retrying clone.") if defined?(@@logger)
560
- attempt += 1
561
- retry
562
- end
563
- end
564
- end
565
-
566
- cloned_issue
553
+ create_issue(
554
+ project_key: project_key,
555
+ summary: summary,
556
+ issue_type: issue_type,
557
+ epic_name: epic_name,
558
+ description: description,
559
+ additional_fields: additional_fields,
560
+ attachments: attachments
561
+ )
567
562
  rescue StandardError => e
568
563
  raise e
569
564
  end
@@ -64,19 +64,10 @@ module PWN
64
64
  end
65
65
  end
66
66
 
67
- if pi.config.pwn_mesh
68
- active_channel = PWN::Env[:plugins][:meshtastic][:channel][:active].to_s.to_sym
69
- region = PWN::Env[:plugins][:meshtastic][:channel][active_channel][:region]
70
- topic = PWN::Env[:plugins][:meshtastic][:channel][active_channel][:topic]
71
- channel_num = PWN::Env[:plugins][:meshtastic][:channel][active_channel][:channel_num]
67
+ ps1_proc = "#{name}[#{version}]:#{line_count} #{dchars} ".to_s.scrub
68
+ ps1_proc = '' if pi.config.pwn_mesh
72
69
 
73
- pi.config.prompt_name = "pwn.mesh:#{region}/#{topic}:#{channel_num}"
74
- name = "\001\e[1m\002\001\e[32m\002#{pi.config.prompt_name}\001\e[0m\002"
75
- dchars = "\001\e[32m\002>>>\001\e[33m\002"
76
- dchars = "\001\e[33m\002***\001\e[33m\002" if mode == :splat
77
- end
78
-
79
- "#{name}[#{version}]:#{line_count} #{dchars} ".to_s.scrub
70
+ ps1_proc
80
71
  end
81
72
  rescue StandardError => e
82
73
  raise e
@@ -472,24 +463,77 @@ module PWN
472
463
  # Init ncurses UI (idempotent) with separate RX (top) and TX (bottom) panes
473
464
  Curses.init_screen
474
465
  Curses.curs_set(0)
475
- # Curses.crmode
476
- # Curses.ESCDELAY = 0
466
+ Curses.noecho
467
+ Curses.cbreak
468
+ Curses.crmode
469
+ Curses.ESCDELAY = 0
477
470
  Curses.start_color
478
471
  Curses.use_default_colors
479
472
 
473
+ mesh_highlight_colors = [
474
+ { fg: Curses::COLOR_RED, bg: Curses::COLOR_WHITE },
475
+ { fg: Curses::COLOR_GREEN, bg: Curses::COLOR_BLACK },
476
+ { fg: Curses::COLOR_YELLOW, bg: Curses::COLOR_BLACK },
477
+ { fg: Curses::COLOR_BLUE, bg: Curses::COLOR_WHITE },
478
+ { fg: Curses::COLOR_CYAN, bg: Curses::COLOR_BLACK },
479
+ { fg: Curses::COLOR_MAGENTA, bg: Curses::COLOR_WHITE },
480
+ { fg: Curses::COLOR_WHITE, bg: Curses::COLOR_BLUE }
481
+ ]
482
+ mesh_highlight_colors.each_with_index do |hash, idx|
483
+ color_id = idx + 1
484
+ color_fg = hash[:fg]
485
+ color_bg = hash[:bg]
486
+ Curses.init_pair(color_id, color_fg, color_bg)
487
+ end
488
+ PWN.const_set(:MeshColors, (1..mesh_highlight_colors.length).to_a)
489
+ PWN.const_set(:MeshLastPair, PWN::MeshColors.sample)
490
+
491
+ mesh_ui_colors = []
492
+ mesh_highlight_colors.each_with_index do |hl_hash, idx|
493
+ ui_hash = {
494
+ color_id: idx + 10,
495
+ fg: hl_hash[:fg],
496
+ bg: -1
497
+ }
498
+ Curses.init_pair(ui_hash[:color_id], ui_hash[:fg], ui_hash[:bg])
499
+ mesh_ui_colors.push(ui_hash)
500
+ end
501
+
502
+ red = mesh_ui_colors[0][:color_id]
503
+ green = mesh_ui_colors[1][:color_id]
504
+ yellow = mesh_ui_colors[2][:color_id]
505
+ blue = mesh_ui_colors[3][:color_id]
506
+ cyan = mesh_ui_colors[4][:color_id]
507
+ magenta = mesh_ui_colors[5][:color_id]
508
+ white = mesh_ui_colors[6][:color_id]
509
+
480
510
  rx_height = Curses.lines - 4
481
511
  rx_win = Curses::Window.new(rx_height, Curses.cols, 0, 0)
482
512
  rx_win.scrollok(true)
483
513
  rx_win.nodelay = true
484
- rx_win.box('|', "\u2500")
485
- rx_win.addstr("#{host}:#{port} >>> #{region}/#{topic}:#{channel_num} >>> Messages >>>\n")
514
+ rx_win.attron(Curses.color_pair(cyan) | Curses::A_BOLD)
515
+
516
+ # Make rx_header bold and green
517
+ rx_win.attron(Curses.color_pair(green) | Curses::A_BOLD)
518
+ rx_header = "<<< #{host}:#{port} | #{region}/#{topic} | ch:#{channel_num} >>>"
519
+ rx_header_len = rx_header.length
520
+ rx_header_pos = (Curses.cols / 2) - (rx_header_len / 2)
521
+ rx_win.setpos(1, rx_header_pos)
522
+ rx_win.addstr(rx_header)
523
+ rx_win.attroff(Curses.color_pair(green) | Curses::A_BOLD)
524
+ # Jump two lines below header before messages begin
525
+ rx_win.setpos(2, 1)
526
+ rx_win.attron(Curses.color_pair(cyan) | Curses::A_BOLD)
527
+ rx_header_bottom_line = "\u2014" * (Curses.cols - 2)
528
+ rx_header_bottom_line_pos = (Curses.cols / 2) - (rx_header_bottom_line.length / 2)
529
+ rx_win.addstr(rx_header_bottom_line)
530
+ rx_win.attroff(Curses.color_pair(cyan) | Curses::A_BOLD)
531
+
486
532
  rx_win.refresh
487
533
 
488
534
  tx_win = Curses::Window.new(4, Curses.cols, rx_height, 0)
489
535
  tx_win.scrollok(false)
490
536
  tx_win.nodelay = true
491
- tx_win.box('|', "\u2500")
492
- tx_win.addstr("TX Pane (echo while typing, last sent shown after Enter)\n")
493
537
  tx_win.refresh
494
538
 
495
539
  PWN.const_set(:MeshRxWin, rx_win)
@@ -498,24 +542,61 @@ module PWN
498
542
  PWN.const_set(:MeshPi, pi)
499
543
 
500
544
  # Live typing echo thread (idempotent)
501
- tx_prompt = "#{region}/#{topic}:#{channel_num} >>> "
545
+ tx_prompt = "#{region}/#{topic} >>>"
502
546
  echo_thread = Thread.new do
547
+ last_drawn = nil
503
548
  loop do
504
549
  break unless pi.config.pwn_mesh
505
550
 
506
551
  tx_win = PWN.const_get(:MeshTxWin)
507
552
  mutex = PWN.const_get(:MeshMutex)
508
553
  msg_input = pi.input.line_buffer.to_s
509
- ts = Time.now.strftime('%H:%M:%S')
510
- mutex.synchronize do
511
- # Only show draft if after_read hook hasn't just written final TX (heuristic)
512
- tx_win.clear
513
- tx_win.box('|', "\u2500")
514
- tx_win.setpos(1, 1)
515
- tx_win.addstr("[#{ts}] [TX] #{tx_prompt} #{msg_input}")
516
- tx_win.refresh
554
+ ts = Time.now.strftime('%H:%M:%S%z')
555
+ cursor_pos = (Readline.respond_to?(:point) ? Readline.point : msg_input.length)
556
+ prefix = "[#{ts}] [TX] #{tx_prompt} "
557
+ base_line = "#{prefix}#{msg_input}"
558
+ cursor_abs_index = prefix.length + cursor_pos
559
+ current_line = base_line
560
+ if current_line != last_drawn
561
+ mutex.synchronize do
562
+ # Only show draft if after_read hook hasn't just written final TX (heuristic)
563
+ tx_win.clear
564
+ tx_win.attron(Curses.color_pair(red) | Curses::A_BOLD)
565
+ tx_header_bottom_line = "\u2014" * (Curses.cols - 2)
566
+ tx_header_bottom_line_pos = (Curses.cols / 2) - (rx_header_bottom_line.length / 2)
567
+ tx_win.addstr(rx_header_bottom_line)
568
+ tx_win.attroff(Curses.color_pair(red) | Curses::A_BOLD)
569
+ tx_win.attron(Curses.color_pair(yellow) | Curses::A_BOLD)
570
+
571
+ inner_width = Curses.cols - 2
572
+ segments = current_line.chars.each_slice(inner_width).map(&:join)
573
+ available_rows = tx_win.maxy - 2
574
+ segments.first(available_rows).each_with_index do |seg, idx|
575
+ tx_win.setpos(1 + idx, 1)
576
+ start_index = idx * inner_width
577
+ end_index = start_index + inner_width - 1
578
+ if cursor_abs_index.between?(start_index, end_index)
579
+ cursor_col = cursor_abs_index - start_index
580
+ (0..inner_width).each do |col|
581
+ ch = seg[col] || ' '
582
+ if col == cursor_col
583
+ tx_win.attron(Curses.color_pair(red) | Curses::A_REVERSE | Curses::A_BOLD)
584
+ tx_win.addch(ch)
585
+ tx_win.attroff(Curses.color_pair(red) | Curses::A_REVERSE | Curses::A_BOLD)
586
+ else
587
+ tx_win.addch(ch)
588
+ end
589
+ end
590
+ else
591
+ tx_win.addstr(seg.ljust(inner_width))
592
+ end
593
+ end
594
+ tx_win.attroff(Curses.color_pair(yellow) | Curses::A_BOLD)
595
+ tx_win.refresh
596
+ end
597
+ last_drawn = current_line
517
598
  end
518
- sleep 0.01
599
+ sleep 0.005
519
600
  end
520
601
  end
521
602
  echo_thread.abort_on_exception = false
@@ -545,22 +626,38 @@ module PWN
545
626
  mutex = PWN.const_get(:MeshMutex)
546
627
 
547
628
  from = packet[:node_id_from]
548
- absolute_topic = "#{region}/#{topic.gsub('#', from)}:#{channel_num}"
629
+ absolute_topic = "#{region}/#{topic.gsub('#', from)}"
549
630
  to = packet[:node_id_to]
550
631
  rx_text = decoded[:payload]
551
- ts = Time.now.strftime('%H:%M:%S')
632
+ ts = Time.now.strftime('%H:%M:%S%z')
633
+
634
+ # Select a random color pair different from the last used one
635
+ colors_arr = PWN.const_get(:MeshColors)
636
+ last_pair = PWN.const_get(:MeshLastPair)
637
+ PWN.send(:remove_const, :MeshLastPair)
638
+ pair_choices = colors_arr.reject { |c| c == last_pair }
639
+ pair = pair_choices.sample
640
+ PWN.const_set(:MeshLastPair, pair)
641
+ rx_win.attron(Curses.color_pair(pair) | Curses::A_REVERSE)
642
+
643
+ current_line = "\n[#{ts}] [RX] #{absolute_topic} >>> #{rx_text}"
644
+ current_line = "\n[#{ts}] [RX][DM INTERCEPTED] #{absolute_topic} to: #{to} >>> #{rx_text}" unless to == '!ffffffff'
645
+
552
646
  mutex.synchronize do
553
- if to == '!ffffffff'
554
- rx_win.addstr("[#{ts}] [RX] #{absolute_topic}: #{rx_text}\n")
555
- else
556
- rx_win.addstr("[#{ts}] [RX][DM INTERCEPTED] #{absolute_topic} >>> #{to}: #{rx_text}\n")
647
+ inner_width = Curses.cols - 2
648
+ content = current_line.sub(/\A\n/, '')
649
+ segments = content.scan(/.{1,#{inner_width}}/)
650
+ segments.each do |seg|
651
+ rx_win.addstr("\n")
652
+ rx_win.setpos(rx_win.cury, 1)
653
+ line = seg.ljust(inner_width)
654
+ rx_win.addstr(line)
557
655
  end
558
- # rx_win.addstr("#{msg.inspect}\n\n\n")
559
656
  rx_win.refresh
560
657
  end
658
+ rx_win.attroff(Curses.color_pair(pair) | Curses::A_REVERSE)
561
659
  end
562
660
  end
563
- PWN.const_set(:MqttSubThread, true)
564
661
  rescue StandardError => e
565
662
  raise e
566
663
  end
@@ -648,6 +745,8 @@ module PWN
648
745
  PWN.const_get(:MeshTxWin).close
649
746
  PWN.send(:remove_const, :MeshTxWin)
650
747
  end
748
+ PWN.send(:remove_const, :MeshColors) if PWN.const_defined?(:MeshColors)
749
+ PWN.send(:remove_const, :MeshLastPair) if PWN.const_defined?(:MeshLastPair)
651
750
  PWN.send(:remove_const, :MeshPi) if PWN.const_defined?(:MeshPi)
652
751
  PWN.send(:remove_const, :MeshMutex) if PWN.const_defined?(:MeshMutex)
653
752
  PWN.send(:remove_const, :MqttSubThread) if PWN.const_defined?(:MqttSubThread)
@@ -805,21 +904,6 @@ module PWN
805
904
  text: tx_text,
806
905
  psks: psks
807
906
  )
808
-
809
- # Update TX pane (bottom) with last sent message
810
- if PWN.const_defined?(:MeshTxWin)
811
- tx_win = PWN.const_get(:MeshTxWin)
812
- mutex = PWN.const_get(:MeshMutex)
813
- absolute_topic = "#{region}/#{topic.gsub('#', from)}:#{channel_num}"
814
- ts = Time.now.strftime('%H:%M:%S')
815
- mutex.synchronize do
816
- tx_win.clear
817
- tx_win.box('|', "\u2500")
818
- tx_win.setpos(1, 1)
819
- tx_win.addstr("[#{ts}] [TX] #{absolute_topic} >>> #{to}: #{tx_text}")
820
- tx_win.refresh
821
- end
822
- end
823
907
  end
824
908
  end
825
909
  rescue StandardError => e
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.479'
4
+ VERSION = '0.5.481'
5
5
  end
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.479
4
+ version: 0.5.481
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -519,14 +519,14 @@ dependencies:
519
519
  requirements:
520
520
  - - '='
521
521
  - !ruby/object:Gem::Version
522
- version: 2.8.1
522
+ version: 2.9.0
523
523
  type: :runtime
524
524
  prerelease: false
525
525
  version_requirements: !ruby/object:Gem::Requirement
526
526
  requirements:
527
527
  - - '='
528
528
  - !ruby/object:Gem::Version
529
- version: 2.8.1
529
+ version: 2.9.0
530
530
  - !ruby/object:Gem::Dependency
531
531
  name: meshtastic
532
532
  requirement: !ruby/object:Gem::Requirement