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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad2a2e3fe7efc1387b2e1e74af3eb75a7f843fa48f6256e86670ffde794f091d
4
- data.tar.gz: b48527c558f948fc39b09c8ac03a6fe90ef4be80ea042dcbe77c4f33cce00a08
3
+ metadata.gz: 2d7f95442e7e518256ffa6e9f9ce4e6856559430d475621446b07192f22088ad
4
+ data.tar.gz: 3030f3616da6d651f08eae290b4a979633074904d001c3bfd16c6ddd81c20de5
5
5
  SHA512:
6
- metadata.gz: '08697b10f3966be151cca3d55d72f0c852256989bcbeb7ee3887a160d231e7463ea140e0498562783a052f05a802e35f028362e918fe2b50220a0f9f2d55e9de'
7
- data.tar.gz: 0a88ece3b27161c451ddae0e20e9d6edf90328f66f0027abce7bc502117b7f999aa1b5361e423b9630c950d463ed4aa5bb4283225c9e6748256eaae6a9a58c51
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'
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.17.5'
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. Malformed reference data fails closed
44
- when expiry would be needed. If those protected folders occupy the seven-folder
45
- limit, the next producer fails before modifying its source. Unknown folder
46
- names still count toward capacity and are not silently expired.
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 = File.file?(policy) ? JSON.parse(File.binread(policy)).fetch('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
- values = file.end_with?('.jsonl') ? File.foreach(file).reject { |line| line.strip.empty? }.map { |line| JSON.parse(line) } : [JSON.parse(File.binread(file))]
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
- rescue JSON::ParserError, KeyError => e
279
- raise IOError, "Unreadable backup references: #{e.class}"
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 = File::RDONLY | File::NOFOLLOW | 0x10000 | 0x80000 # O_DIRECTORY | O_CLOEXEC
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.793'
4
+ VERSION = '0.5.794'
5
5
  end
@@ -58,11 +58,24 @@ describe PWN::Backups do
58
58
  expect(File).not_to exist(copies[1])
59
59
  end
60
60
 
61
- it 'fails admission on malformed reference data without losing any recovery copy' do
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
- expect { described_class.capture(path: @source) }.to raise_error(IOError, /references/)
65
- expect(copies.all? { |path| File.file?(path) }).to be(true)
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')
@@ -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.793
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.23
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.23
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.17.5
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.17.5
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.23
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: []