pwn 0.5.793 → 0.5.794
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/documentation/Backup-Retention.md +6 -4
- data/lib/pwn/backups.rb +56 -15
- data/lib/pwn/plugins/ai_sandbox.rb +9 -1
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/backups_spec.rb +16 -3
- data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +9 -0
- data/third_party/pwn_rdoc.jsonl +5 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d7f95442e7e518256ffa6e9f9ce4e6856559430d475621446b07192f22088ad
|
|
4
|
+
data.tar.gz: 3030f3616da6d651f08eae290b4a979633074904d001c3bfd16c6ddd81c20de5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed97966a4b047588627e1e3864471171a565e8095fc2c9091b418756d8dcb02ead69db214d18d8d4db2b470ac6e1455dc9307c37ffddcc0b4f36feb45ae93a0e
|
|
7
|
+
data.tar.gz: 1c349c364aeed6a41a3ec8b25f21bc4cbadd100e5312df2fc9c0b5ed8916c3410602ad4f5d2ebe6a3edb3dcd8e51aa8ab0d58782679f7822c32a8b498351bfb5
|
data/Gemfile
CHANGED
|
@@ -20,7 +20,7 @@ gem 'base32', '0.3.4'
|
|
|
20
20
|
gem 'bitcoin-ruby', '0.0.20'
|
|
21
21
|
gem 'brakeman', '8.1.0'
|
|
22
22
|
gem 'bson', '5.2.0'
|
|
23
|
-
gem 'bundler', '>=4.0.
|
|
23
|
+
gem 'bundler', '>=4.0.24'
|
|
24
24
|
gem 'bundler-audit', '>=0.9.3'
|
|
25
25
|
gem 'bunny', '3.5.0'
|
|
26
26
|
gem 'colorize', '1.1.0'
|
|
@@ -88,7 +88,7 @@ gem 'ruby-audio', '1.6.1'
|
|
|
88
88
|
gem 'ruby-nmap', '1.0.3'
|
|
89
89
|
gem 'ruby-saml', '1.18.1'
|
|
90
90
|
gem 'rvm', '1.11.3.9'
|
|
91
|
-
gem 'savon', '2.
|
|
91
|
+
gem 'savon', '2.18.1'
|
|
92
92
|
gem 'selenium-devtools', '0.156.0'
|
|
93
93
|
gem 'selenium-webdriver', '4.52.0'
|
|
94
94
|
gem 'slack-ruby-client', '3.3.0'
|
|
@@ -40,10 +40,12 @@ that has no interrupted generation record, and an unpublished `.partial-`
|
|
|
40
40
|
copy that was never renamed into a restore name. Those are not certified as
|
|
41
41
|
complete; they are only eligible so one verified snapshot can be published.
|
|
42
42
|
Prepared/interrupted generations, retention-policy pins and current literal
|
|
43
|
-
evidence references remain protected.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
evidence references remain protected. A reference file that is not valid JSON
|
|
44
|
+
does not abort admission. A generation path found in that unreadable text stays
|
|
45
|
+
pinned; valid JSON still ignores a path that only appears inside quoted prose.
|
|
46
|
+
If protected folders still occupy the seven-folder limit, the next producer fails
|
|
47
|
+
before modifying its source. Unknown folder names still count toward capacity
|
|
48
|
+
and are not silently expired.
|
|
47
49
|
|
|
48
50
|
Transactions publish `prepared` before payload/source changes and `complete`
|
|
49
51
|
only after successful return. Exceptions, interrupted/blocked results and failed
|
data/lib/pwn/backups.rb
CHANGED
|
@@ -242,7 +242,15 @@ module PWN
|
|
|
242
242
|
policy = File.join(root, '.retention-policy.json')
|
|
243
243
|
raise IOError, 'Unsafe retention policy' if File.symlink?(policy)
|
|
244
244
|
|
|
245
|
-
pins =
|
|
245
|
+
pins = []
|
|
246
|
+
if File.file?(policy)
|
|
247
|
+
begin
|
|
248
|
+
parsed = JSON.parse(File.binread(policy))
|
|
249
|
+
pins = parsed.fetch('pins', [])
|
|
250
|
+
rescue JSON::ParserError, EncodingError
|
|
251
|
+
pin_raw_paths(text: File.binread(policy), root: root, pins: pins)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
246
254
|
raise IOError, 'Invalid backup pins' unless pins.is_a?(Array) && pins.all? { |name| name.is_a?(String) && name.match?(/\A\d{8}_\d{6}(?:_[a-f0-9]{8})?\z/) }
|
|
247
255
|
|
|
248
256
|
pins = pins.dup
|
|
@@ -260,23 +268,56 @@ module PWN
|
|
|
260
268
|
admission = Thread.current[:pwn_maintenance_admission]
|
|
261
269
|
raise IOError, 'Backup reference budget exhausted' if admission && !admission.call(stat.size + 1)
|
|
262
270
|
|
|
263
|
-
|
|
264
|
-
values.select! { |value| value.is_a?(Hash) && (%w[prepared interrupted].include?(value['state']) || value['pinned'] == true) } if name == 'maintenance'
|
|
265
|
-
until values.empty?
|
|
266
|
-
value = values.pop
|
|
267
|
-
case value
|
|
268
|
-
when Hash then values.concat(value.values)
|
|
269
|
-
when Array then values.concat(value)
|
|
270
|
-
when String
|
|
271
|
-
match = value.match(%r{\A(?:#{Regexp.escape(root)}/|backup/)(\d{8}_\d{6}(?:_[a-f0-9]{8})?)(?:/|\z)})
|
|
272
|
-
pins << match[1] if match
|
|
273
|
-
end
|
|
274
|
-
end
|
|
271
|
+
collect_references(file: file, root: root, pins: pins, maintenance: name == 'maintenance')
|
|
275
272
|
end
|
|
276
273
|
end
|
|
277
274
|
pins.uniq
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
private_class_method def self.collect_references(opts = {})
|
|
278
|
+
file = opts[:file]
|
|
279
|
+
body = File.binread(file)
|
|
280
|
+
if file.end_with?('.jsonl')
|
|
281
|
+
body.split("\n".b).each do |line|
|
|
282
|
+
next if line.strip.empty?
|
|
283
|
+
|
|
284
|
+
begin
|
|
285
|
+
walk_reference(value: JSON.parse(line), root: opts[:root], pins: opts[:pins], maintenance: opts[:maintenance])
|
|
286
|
+
rescue JSON::ParserError, EncodingError
|
|
287
|
+
pin_raw_paths(text: line, root: opts[:root], pins: opts[:pins])
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
else
|
|
291
|
+
begin
|
|
292
|
+
walk_reference(value: JSON.parse(body), root: opts[:root], pins: opts[:pins], maintenance: opts[:maintenance])
|
|
293
|
+
rescue JSON::ParserError, EncodingError
|
|
294
|
+
pin_raw_paths(text: body, root: opts[:root], pins: opts[:pins])
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
private_class_method def self.walk_reference(opts = {})
|
|
300
|
+
values = [opts[:value]]
|
|
301
|
+
values.select! { |value| value.is_a?(Hash) && (%w[prepared interrupted].include?(value['state']) || value['pinned'] == true) } if opts[:maintenance]
|
|
302
|
+
root = opts[:root]
|
|
303
|
+
pins = opts[:pins]
|
|
304
|
+
until values.empty?
|
|
305
|
+
value = values.pop
|
|
306
|
+
case value
|
|
307
|
+
when Hash then values.concat(value.values)
|
|
308
|
+
when Array then values.concat(value)
|
|
309
|
+
when String
|
|
310
|
+
match = value.match(%r{\A(?:#{Regexp.escape(root)}/|backup/)(\d{8}_\d{6}(?:_[a-f0-9]{8})?)(?:/|\z)})
|
|
311
|
+
pins << match[1] if match
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
private_class_method def self.pin_raw_paths(opts = {})
|
|
317
|
+
text = opts[:text].to_s.b
|
|
318
|
+
root = opts[:root].to_s.b
|
|
319
|
+
pattern = Regexp.new("(?:#{Regexp.escape(root)}/|backup/)(\\d{8}_\\d{6}(?:_[a-f0-9]{8})?)(?:/|\\z)")
|
|
320
|
+
text.scan(pattern).each { |row| opts[:pins] << row[0] }
|
|
280
321
|
end
|
|
281
322
|
|
|
282
323
|
# Unreferenced complete generations, published pre-generation snapshots, and
|
|
@@ -87,7 +87,7 @@ module PWN
|
|
|
87
87
|
attach_function :openat, %i[int string int uint], :int
|
|
88
88
|
attach_function :mkdirat, %i[int string uint], :int
|
|
89
89
|
end
|
|
90
|
-
flags =
|
|
90
|
+
flags = directory_open_flags
|
|
91
91
|
identities = protected_roots.map do |path|
|
|
92
92
|
stat = File.stat(path)
|
|
93
93
|
[stat.dev, stat.ino]
|
|
@@ -114,6 +114,14 @@ module PWN
|
|
|
114
114
|
directory&.close unless directory&.closed?
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
+
# O_DIRECTORY is 1<<16 on x86_64 and 1<<14 on aarch64/arm. 1<<16 is O_DIRECT
|
|
118
|
+
# there, and open("/", O_DIRECT) returns EINVAL.
|
|
119
|
+
private_class_method def self.directory_open_flags(opts = {})
|
|
120
|
+
platform = opts[:platform] || RUBY_PLATFORM
|
|
121
|
+
directory = platform.match?(/\Aaarch64|\Aarm-/) ? (1 << 14) : (1 << 16)
|
|
122
|
+
File::RDONLY | File::NOFOLLOW | directory | (1 << 19)
|
|
123
|
+
end
|
|
124
|
+
|
|
117
125
|
# Mandatory repository boundary, independent of the optional profile.
|
|
118
126
|
# The block runs only in a child; cwd and restrictions never affect the UI.
|
|
119
127
|
public_class_method def self.protect_call(opts = {})
|
data/lib/pwn/version.rb
CHANGED
|
@@ -58,11 +58,24 @@ describe PWN::Backups do
|
|
|
58
58
|
expect(File).not_to exist(copies[1])
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
it '
|
|
61
|
+
it 'admits a snapshot when a reference file is not valid JSON' do
|
|
62
62
|
copies = 7.times.map { described_class.capture(path: @source) }
|
|
63
|
+
generations = copies.map { |path| File.dirname(path, 2) }
|
|
64
|
+
generations.each_with_index { |dir, index| File.utime(Time.at(1_600_000_300 + index), Time.at(1_600_000_300 + index), dir) }
|
|
63
65
|
File.write(File.join(@home, '.pwn', 'learning.jsonl'), '{broken')
|
|
64
|
-
|
|
65
|
-
expect(
|
|
66
|
+
described_class.capture(path: @source)
|
|
67
|
+
expect(File).not_to exist(generations.first)
|
|
68
|
+
expect(copies[1..].all? { |path| File.file?(path) }).to be(true)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'keeps a generation named inside unreadable reference text' do
|
|
72
|
+
copies = 7.times.map { described_class.capture(path: @source) }
|
|
73
|
+
generations = copies.map { |path| File.dirname(path, 2) }
|
|
74
|
+
generations.each_with_index { |dir, index| File.utime(Time.at(1_600_000_400 + index), Time.at(1_600_000_400 + index), dir) }
|
|
75
|
+
File.write(File.join(@home, '.pwn', 'learning.jsonl'), "{not-json #{copies[1]}")
|
|
76
|
+
described_class.capture(path: @source)
|
|
77
|
+
expect(File).to exist(copies[1])
|
|
78
|
+
expect(File).not_to exist(generations.first)
|
|
66
79
|
end
|
|
67
80
|
|
|
68
81
|
it 'uses one generation for nested producers and preserves failed result recovery' do
|
|
@@ -224,6 +224,15 @@ describe PWN::Plugins::AISandbox do
|
|
|
224
224
|
end
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
+
it 'uses aarch64 directory open bits instead of the x86_64 O_DIRECT bit' do
|
|
228
|
+
aarch64 = described_class.send(:directory_open_flags, platform: 'aarch64-linux')
|
|
229
|
+
x86_64 = described_class.send(:directory_open_flags, platform: 'x86_64-linux')
|
|
230
|
+
expect(aarch64.anybits?(1 << 14)).to be(true)
|
|
231
|
+
expect(aarch64.anybits?(1 << 16)).to be(false)
|
|
232
|
+
expect(x86_64.anybits?(1 << 16)).to be(true)
|
|
233
|
+
expect(x86_64.anybits?(1 << 14)).to be(false)
|
|
234
|
+
end
|
|
235
|
+
|
|
227
236
|
it 'pins controller directories across pathname replacement and rejects source ancestry' do
|
|
228
237
|
Dir.mktmpdir('pwn-control-fd') do |root|
|
|
229
238
|
source = File.join(root, 'source')
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -1898,6 +1898,7 @@
|
|
|
1898
1898
|
{"messages":[{"role":"user","content":"PWN::Backups.active? Usage"},{"role":"assistant","content":"`PWN::Backups.active?`: Supported Method Parameters\n\nPWN::Backups.active?\n"}]}
|
|
1899
1899
|
{"messages":[{"role":"user","content":"PWN::Backups.authors Usage"},{"role":"assistant","content":"`PWN::Backups.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
1900
1900
|
{"messages":[{"role":"user","content":"PWN::Backups.capture Usage"},{"role":"assistant","content":"`PWN::Backups.capture`: Supported Method Parameters\n\nPWN::Backups.capture(path: ‘/path/to/source’, label: ‘learning’)\n"}]}
|
|
1901
|
+
{"messages":[{"role":"user","content":"PWN::Backups.collect_references Usage"},{"role":"assistant","content":"`PWN::Backups.collect_references`: "}]}
|
|
1901
1902
|
{"messages":[{"role":"user","content":"PWN::Backups.configure Usage"},{"role":"assistant","content":"`PWN::Backups.configure`: Supported Method Parameters\n\nPWN::Backups.configure(root: nil)\n"}]}
|
|
1902
1903
|
{"messages":[{"role":"user","content":"PWN::Backups.directory Usage"},{"role":"assistant","content":"`PWN::Backups.directory`: Supported Method Parameters\n\nPWN::Backups.directory(label: ‘maintenance’) Caller must hold transaction through the final source mutation and ledger.\n"}]}
|
|
1903
1904
|
{"messages":[{"role":"user","content":"PWN::Backups.expirable_name? Usage"},{"role":"assistant","content":"`PWN::Backups.expirable_name?`: "}]}
|
|
@@ -1905,11 +1906,13 @@
|
|
|
1905
1906
|
{"messages":[{"role":"user","content":"PWN::Backups.legacy_inventory Usage"},{"role":"assistant","content":"`PWN::Backups.legacy_inventory`: "}]}
|
|
1906
1907
|
{"messages":[{"role":"user","content":"PWN::Backups.legacy_unreferenced Usage"},{"role":"assistant","content":"`PWN::Backups.legacy_unreferenced`: "}]}
|
|
1907
1908
|
{"messages":[{"role":"user","content":"PWN::Backups.owned_directory? Usage"},{"role":"assistant","content":"`PWN::Backups.owned_directory?`: "}]}
|
|
1909
|
+
{"messages":[{"role":"user","content":"PWN::Backups.pin_raw_paths Usage"},{"role":"assistant","content":"`PWN::Backups.pin_raw_paths`: "}]}
|
|
1908
1910
|
{"messages":[{"role":"user","content":"PWN::Backups.reconcile Usage"},{"role":"assistant","content":"`PWN::Backups.reconcile`: Supported Method Parameters\n\nPWN::Backups.reconcile(generation: ‘name’, expected_sha256: ‘digest’, confirmed: true)\n"}]}
|
|
1909
1911
|
{"messages":[{"role":"user","content":"PWN::Backups.reference_pins Usage"},{"role":"assistant","content":"`PWN::Backups.reference_pins`: "}]}
|
|
1910
1912
|
{"messages":[{"role":"user","content":"PWN::Backups.relocate_legacy Usage"},{"role":"assistant","content":"`PWN::Backups.relocate_legacy`: Supported Method Parameters\n\nPWN::Backups.relocate_legacy(paths: [], dry_run: true, writers_stopped: false)\n"}]}
|
|
1911
1913
|
{"messages":[{"role":"user","content":"PWN::Backups.safe_directory Usage"},{"role":"assistant","content":"`PWN::Backups.safe_directory`: "}]}
|
|
1912
1914
|
{"messages":[{"role":"user","content":"PWN::Backups.transaction Usage"},{"role":"assistant","content":"`PWN::Backups.transaction`: Supported Method Parameters\n\nPWN::Backups.transaction(root: nil) { … }\n"}]}
|
|
1915
|
+
{"messages":[{"role":"user","content":"PWN::Backups.walk_reference Usage"},{"role":"assistant","content":"`PWN::Backups.walk_reference`: "}]}
|
|
1913
1916
|
{"messages":[{"role":"user","content":"PWN::Backups.write_state Usage"},{"role":"assistant","content":"`PWN::Backups.write_state`: "}]}
|
|
1914
1917
|
{"messages":[{"role":"user","content":"PWN::Banner.authors Usage"},{"role":"assistant","content":"`PWN::Banner.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
|
|
1915
1918
|
{"messages":[{"role":"user","content":"PWN::Banner.get Usage"},{"role":"assistant","content":"`PWN::Banner.get`: Supported Method Parameters\n\nPWN::Banner.get(\n\nindex: 'optional - defaults to random banner index'\n\n)\n"}]}
|
|
@@ -2465,6 +2468,7 @@
|
|
|
2465
2468
|
{"messages":[{"role":"user","content":"PWN::Plugins::AISandbox.collect_worker Usage"},{"role":"assistant","content":"`PWN::Plugins::AISandbox.collect_worker`: "}]}
|
|
2466
2469
|
{"messages":[{"role":"user","content":"PWN::Plugins::AISandbox.control_directory Usage"},{"role":"assistant","content":"`PWN::Plugins::AISandbox.control_directory`: "}]}
|
|
2467
2470
|
{"messages":[{"role":"user","content":"PWN::Plugins::AISandbox.controller_state_roots Usage"},{"role":"assistant","content":"`PWN::Plugins::AISandbox.controller_state_roots`: "}]}
|
|
2471
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::AISandbox.directory_open_flags Usage"},{"role":"assistant","content":"`PWN::Plugins::AISandbox.directory_open_flags`: "}]}
|
|
2468
2472
|
{"messages":[{"role":"user","content":"PWN::Plugins::AISandbox.env_mode Usage"},{"role":"assistant","content":"`PWN::Plugins::AISandbox.env_mode`: "}]}
|
|
2469
2473
|
{"messages":[{"role":"user","content":"PWN::Plugins::AISandbox.eval_ruby Usage"},{"role":"assistant","content":"`PWN::Plugins::AISandbox.eval_ruby`: "}]}
|
|
2470
2474
|
{"messages":[{"role":"user","content":"PWN::Plugins::AISandbox.exec Usage"},{"role":"assistant","content":"`PWN::Plugins::AISandbox.exec`: "}]}
|
|
@@ -4439,6 +4443,7 @@
|
|
|
4439
4443
|
{"messages":[{"role":"user","content":"PWN::Plugins::YubiKey::PIV::Session.info Usage"},{"role":"assistant","content":"`PWN::Plugins::YubiKey::PIV::Session.info`: "}]}
|
|
4440
4444
|
{"messages":[{"role":"user","content":"PWN::Plugins::YubiKey::PIV::Session.metadata Usage"},{"role":"assistant","content":"`PWN::Plugins::YubiKey::PIV::Session.metadata`: "}]}
|
|
4441
4445
|
{"messages":[{"role":"user","content":"PWN::Plugins::YubiKey::PIV::Session.move_key Usage"},{"role":"assistant","content":"`PWN::Plugins::YubiKey::PIV::Session.move_key`: "}]}
|
|
4446
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::YubiKey::PIV::Session.pkcs1_unpad Usage"},{"role":"assistant","content":"`PWN::Plugins::YubiKey::PIV::Session.pkcs1_unpad`: "}]}
|
|
4442
4447
|
{"messages":[{"role":"user","content":"PWN::Plugins::YubiKey::PIV::Session.read_certificate Usage"},{"role":"assistant","content":"`PWN::Plugins::YubiKey::PIV::Session.read_certificate`: "}]}
|
|
4443
4448
|
{"messages":[{"role":"user","content":"PWN::Plugins::YubiKey::PIV::Session.read_object Usage"},{"role":"assistant","content":"`PWN::Plugins::YubiKey::PIV::Session.read_object`: "}]}
|
|
4444
4449
|
{"messages":[{"role":"user","content":"PWN::Plugins::YubiKey::PIV::Session.reset Usage"},{"role":"assistant","content":"`PWN::Plugins::YubiKey::PIV::Session.reset`: "}]}
|
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.
|
|
4
|
+
version: 0.5.794
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -141,14 +141,14 @@ dependencies:
|
|
|
141
141
|
requirements:
|
|
142
142
|
- - ">="
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: 4.0.
|
|
144
|
+
version: 4.0.24
|
|
145
145
|
type: :development
|
|
146
146
|
prerelease: false
|
|
147
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
|
149
149
|
- - ">="
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: 4.0.
|
|
151
|
+
version: 4.0.24
|
|
152
152
|
- !ruby/object:Gem::Dependency
|
|
153
153
|
name: bundler-audit
|
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -1037,14 +1037,14 @@ dependencies:
|
|
|
1037
1037
|
requirements:
|
|
1038
1038
|
- - '='
|
|
1039
1039
|
- !ruby/object:Gem::Version
|
|
1040
|
-
version: 2.
|
|
1040
|
+
version: 2.18.1
|
|
1041
1041
|
type: :runtime
|
|
1042
1042
|
prerelease: false
|
|
1043
1043
|
version_requirements: !ruby/object:Gem::Requirement
|
|
1044
1044
|
requirements:
|
|
1045
1045
|
- - '='
|
|
1046
1046
|
- !ruby/object:Gem::Version
|
|
1047
|
-
version: 2.
|
|
1047
|
+
version: 2.18.1
|
|
1048
1048
|
- !ruby/object:Gem::Dependency
|
|
1049
1049
|
name: selenium-devtools
|
|
1050
1050
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -7405,7 +7405,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
7405
7405
|
- !ruby/object:Gem::Version
|
|
7406
7406
|
version: '0'
|
|
7407
7407
|
requirements: []
|
|
7408
|
-
rubygems_version: 4.0.
|
|
7408
|
+
rubygems_version: 4.0.24
|
|
7409
7409
|
specification_version: 4
|
|
7410
7410
|
summary: Automated Security Testing for CI/CD Pipelines & Beyond
|
|
7411
7411
|
test_files: []
|