Skip to content

Commit 0f9f44f

Browse files
dblockCopilot
andauthored
Use create_additions: false in Grape::Json.load (#2759)
When MultiJson is not available, Grape::Json falls back to stdlib JSON. JSON.load honours the json_class key by calling json_create on the named class, allowing a remote caller to instantiate arbitrary Ruby objects already loaded in the process. Pass create_additions: false to disable this behaviour. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d79a12b commit 0f9f44f

3 files changed

Lines changed: 59 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
* [#2703](https://github.com/ruby-grape/grape/pull/2703): Catch exceptions raised inside `rescue_from` blocks; new `rescue_from :internal_grape_exceptions` opt-in for unrecognised internal errors (resolves [#2482](https://github.com/ruby-grape/grape/issues/2482)) - [@ericproulx](https://github.com/ericproulx).
7373
* [#2706](https://github.com/ruby-grape/grape/pull/2706): Fix `optional :foo, message: 'oops'` raising `UnknownValidator` - [@ericproulx](https://github.com/ericproulx).
7474
* [#2751](https://github.com/ruby-grape/grape/pull/2751): Fix structured error messages leaking the raw i18n key for an undefined optional step such as `summary` (closes #2748) - [@ericproulx](https://github.com/ericproulx).
75+
* [#2759](https://github.com/ruby-grape/grape/pull/2759): Use `create_additions: false` in `Grape::Json.load` to prevent object instantiation via the `json_class` key when using the stdlib JSON fallback - [@dblock](https://github.com/dblock).
7576
* Your contribution here.
7677

7778
### 3.2.1 (2026-04-16)

lib/grape/json.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@ module Grape
44
if defined?(::MultiJson)
55
Json = ::MultiJson
66
else
7-
Json = ::JSON
8-
Json::ParseError = Json::ParserError
7+
module Json
8+
ParseError = ::JSON::ParserError
9+
10+
def self.load(str)
11+
::JSON.load(str, nil, create_additions: false)
12+
end
13+
14+
def self.dump(obj, *)
15+
::JSON.dump(obj, *)
16+
end
17+
end
918
end
1019
end

spec/grape/parser/json_spec.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
describe Grape::Parser::Json, if: !defined?(MultiJson) do
4+
include Rack::Test::Methods
5+
6+
let(:app) do
7+
Class.new(Grape::API) do
8+
format :json
9+
10+
post '/data' do
11+
params.to_h
12+
end
13+
end
14+
end
15+
16+
# Verify that json_class payloads are treated as plain data and do not
17+
# trigger Ruby object instantiation via JSON.load's create_additions mechanism.
18+
context 'when the request body contains a json_class key' do
19+
let(:triggered) { [] }
20+
21+
before do
22+
t = triggered
23+
stub_const('JsonClassTarget', Class.new do
24+
define_singleton_method(:json_create) do |_data|
25+
t << true
26+
new
27+
end
28+
end)
29+
end
30+
31+
it 'does not instantiate the named class' do
32+
body = JSON.dump('json_class' => 'JsonClassTarget', 'data' => { 'x' => 1 })
33+
post '/data', body, 'CONTENT_TYPE' => 'application/json'
34+
35+
expect(triggered).to be_empty
36+
end
37+
38+
it 'returns the payload as a plain hash' do
39+
body = JSON.dump('json_class' => 'JsonClassTarget', 'data' => { 'x' => 1 })
40+
post '/data', body, 'CONTENT_TYPE' => 'application/json'
41+
42+
expect(last_response.status).to eq(201)
43+
parsed = JSON.parse(last_response.body)
44+
expect(parsed['json_class']).to eq('JsonClassTarget')
45+
end
46+
end
47+
end

0 commit comments

Comments
 (0)