marshalsea 0.1.0 → 0.1.1

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: 9f58aa46de7c004e82fdc4a843bbfbd1cfd7acaaa96f52f17910514e9bca2e74
4
- data.tar.gz: a4a0c1dda4375226aa310d566df474c428f8cf3189aa2f07395d9c9e1b365d22
3
+ metadata.gz: 1ee9dd24e014347afe27eae586c8766ed110e51dc12420219f06e7b78395137c
4
+ data.tar.gz: 0fc164a3530dc19efd6b0feb26d521d2f99325d566afc07b4d2b9c85a65fe0f0
5
5
  SHA512:
6
- metadata.gz: 8a32262bb2dc40532209c57af865c40d95fab8c98e9750fa91cf990bf36b3b0f089ed8f11653824217060daef8fd2409d230398c6d38bec333759bcf26a839a9
7
- data.tar.gz: 39c3e4f067a99c0656f9f5451275fdb2b552b9bf4196a03ce3f60a1eff1388d5a1d9892e1cc9de68b14a66ea36afe1cefdb3a8dbc478d9889f5025d056bc18b9
6
+ metadata.gz: 276e8899a8baea7b417672b7f427df277cbb86a9a2fc0e2a57ab2f2398f79c2c0531c50596aced9d45da6655c9c218fa123d31b6fd138fc32135c99dd8bd9b31
7
+ data.tar.gz: 1331c714827c1a858b40e66989f27ca3081d34665df9be3d7f791c18396c4b05540df16de7d3dc1b159b0621c7caefbaaa34237df136f1fd1635fa339b0b9864
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- ©AngelaMos | 2026 -->
2
2
  <!-- README.md -->
3
3
 
4
- ```json
4
+ ```ruby
5
5
  ███╗ ███╗ █████╗ ██████╗ ███████╗██╗ ██╗ █████╗ ██╗ ███████╗███████╗ █████╗
6
6
  ████╗ ████║██╔══██╗██╔══██╗██╔════╝██║ ██║██╔══██╗██║ ██╔════╝██╔════╝██╔══██╗
7
7
  ██╔████╔██║███████║██████╔╝███████╗███████║███████║██║ ███████╗█████╗ ███████║
@@ -15,7 +15,7 @@
15
15
  [![Gem](https://img.shields.io/badge/gem-marshalsea-E9573F?style=flat&logo=rubygems&logoColor=white)](https://rubygems.org/gems/marshalsea)
16
16
  [![Formats](https://img.shields.io/badge/formats-Marshal%20%2B%20YAML-6D4AFF?style=flat)](#the-two-allowlists)
17
17
  [![CVE](https://img.shields.io/badge/CVE--2026--41316-CVSS%208.1-4457E8?style=flat)](https://www.ruby-lang.org/en/news/2026/04/21/erb-cve-2026-41316/)
18
- [![Tests](https://img.shields.io/badge/tests-267-8B5CF6?style=flat)](#build-and-test)
18
+ [![Tests](https://img.shields.io/badge/tests-268-8B5CF6?style=flat)](#build-and-test)
19
19
  [![License: AGPLv3](https://img.shields.io/badge/License-AGPL_v3-purple.svg)](https://www.gnu.org/licenses/agpl-3.0)
20
20
 
21
21
  > A Ruby object-deserialization security lab. It reads `Marshal` and YAML bytes without ever reviving them, tells you which classes are in there and which methods those bytes would fire, hunts your loaded code for the classes that make usable gadgets, builds a working payload for a real 2026 CVE, and then stands up a deliberately vulnerable Sinatra target so you can watch the exploit land over HTTP and watch the defense stop it. It ships as a gem plus a container, and every defense in it comes with a written statement of what it cannot do.
@@ -30,7 +30,7 @@ It is also the bug class the industry most consistently gets wrong in retellings
30
30
 
31
31
  ## What it is
32
32
 
33
- Not a stub. Every capability below is exercised by 267 tests across seven suites and a six-stage gate that runs real containers, with 78 assertions that must all pass.
33
+ Not a stub. Every capability below is exercised by 268 tests across seven suites and a six-stage gate that runs real containers, with 79 assertions that must all pass.
34
34
 
35
35
  **A reader that never loads (Marshal)**
36
36
  - Parses the Marshal binary format without calling `Marshal.load`: version bytes, type tags, instance variables, object links, symbols, floats
@@ -146,7 +146,7 @@ Every defense here is a trade, and the code says so out loud rather than in a fo
146
146
 
147
147
  - **A stream that passes inspection is not a safe stream.** `proceed?` means the bytes matched a policy. It does not mean the payload is harmless, and `LIMITATION_NOTICE` says exactly that. The published CVE chain produces **zero sink tags**, so sink detection alone never catches it; only class allowlisting does.
148
148
  - **The runtime guard is defense in depth, not a boundary.** Its cost is not a multiplier. Enabling a `TracePoint` costs a near-constant ~46 microseconds per load, so it is 1.0x on a 488 KB document and **40x on a 45-byte session cookie**, and a cookie is what this lab deserializes. It also covers the load window only: a class carrying no hook at all is built freely and fires whenever the application next touches it.
149
- - **The scanner sees only what is loaded.** `ObjectSpace` cannot report a class nobody has required yet. On a stock image it narrows 119 ungated candidates to 29 reachable, and 135 of its candidates are C-defined with no Ruby source at all, which it reports as `unanalysable` rather than scoring as inert.
149
+ - **The scanner sees only what is loaded.** `ObjectSpace` cannot report a class nobody has required yet. On a stock `ruby:4.0-slim` it narrows 124 ungated candidates to 28 reachable, and 142 of its candidates are C-defined with no Ruby source at all, which it reports as `unanalysable` rather than scoring as inert. Those figures are a statement about what one process had loaded, not about Ruby; re-run `just scan` rather than quoting them.
150
150
  - **The gem floor is `>= 3.4` and it was measured, not chosen.** `Marshal.load` did not validate the bignum sign byte until 3.4. The parser accepts `+` and `-` only, so it models 3.4 and newer; run it on 3.3 and it disagrees with the interpreter it exists to model. `just package` re-proves that in both directions on every run.
151
151
 
152
152
  ## Architecture
@@ -19,6 +19,10 @@ module Marshalsea
19
19
  REASON = "deserialization hook %s#%s is not permitted"
20
20
  ANONYMOUS_OWNER = "(class with no name)"
21
21
 
22
+ CLASS_OF = ::Object.instance_method(:class).freeze
23
+ KIND_OF = ::Object.instance_method(:is_a?).freeze
24
+ NAME_OF = ::Module.instance_method(:name).freeze
25
+
22
26
  LIMITATION_NOTICE = <<~NOTICE
23
27
  SECURITY LIMITATION
24
28
 
@@ -109,8 +113,8 @@ module Marshalsea
109
113
  end
110
114
 
111
115
  def owner_name(receiver)
112
- owner = receiver.is_a?(Module) ? receiver : receiver.class
113
- name = owner.name
116
+ owner = KIND_OF.bind_call(receiver, ::Module) ? receiver : CLASS_OF.bind_call(receiver)
117
+ name = NAME_OF.bind_call(owner)
114
118
  name if name.is_a?(String) && !name.empty?
115
119
  rescue StandardError
116
120
  nil
@@ -3,5 +3,5 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  module Marshalsea
6
- VERSION = "0.1.0"
6
+ VERSION = "0.1.1"
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marshalsea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carter Perez