jq 1.0.0 → 1.1.0
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/CHANGELOG.md +19 -0
- data/Gemfile.lock +1 -1
- data/ext/jq/extconf.rb +1 -0
- data/ext/jq/jq_ext.c +19 -4
- data/ext/jq/patches/0001-add-sandbox-mode.patch +156 -0
- data/jq.gemspec +1 -1
- data/lib/jq.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e79fcd6b493cfe8b9c153f73b09ca0e53298b24d6fcd0215efd956be60d965f
|
|
4
|
+
data.tar.gz: c3c3dd6b922e064fad34935cacef4d4c16799b62eb531965605ef9b4db132184
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c8b6b43f7c2cb0d9c2d31724827487f1bb615afac9f5c9f491ed7ec3eeefaab79eb8b08e4219abeda712ae098ea88507ed9f055c708a6875d08d9471281e2ef
|
|
7
|
+
data.tar.gz: ee075e8b777bd6ad20cd37b4c15b98baa0f543d8ed1c7b4d1d2ccdf7696e29220b4631d50d25a8582e294efa172dd2f8f301aa9eff3216b1d09278201a52c6bf
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.0] - 2026-02-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Sandbox mode for `JQ.filter` (enabled by default) that blocks access to
|
|
13
|
+
environment variables (`env`, `$ENV`) and file imports (`include`, `import`)
|
|
14
|
+
- New `:sandbox` option on `JQ.filter` (default: `true`). Pass `sandbox: false`
|
|
15
|
+
to allow environment variable access and file imports.
|
|
16
|
+
- `JQ.validate_filter!` always runs in sandbox mode
|
|
17
|
+
- Patch system for bundled jq source via `mini_portile2` `patch_files`
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Breaking**: `env` and `$ENV` now return empty objects by default. Pass
|
|
22
|
+
`sandbox: false` to `JQ.filter` to restore access to environment variables.
|
|
23
|
+
- **Breaking**: `include` and `import` statements now raise `JQ::Error` by
|
|
24
|
+
default.
|
|
25
|
+
|
|
8
26
|
## [1.0.0] - 2026-01-26
|
|
9
27
|
|
|
10
28
|
### Added
|
|
@@ -46,4 +64,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
46
64
|
- Filter expressions cannot execute system commands (safe by design)
|
|
47
65
|
- Tested with large inputs, deeply nested structures, and malicious filters
|
|
48
66
|
|
|
67
|
+
[1.1.0]: https://github.com/persona-id/jq-ruby/releases/tag/v1.1.0
|
|
49
68
|
[1.0.0]: https://github.com/persona-id/jq-ruby/releases/tag/v1.0.0
|
data/Gemfile.lock
CHANGED
data/ext/jq/extconf.rb
CHANGED
|
@@ -15,6 +15,7 @@ class JQRecipe < MiniPortile
|
|
|
15
15
|
url: "https://github.com/jqlang/jq/releases/download/jq-#{JQ_VERSION}/jq-#{JQ_VERSION}.tar.gz",
|
|
16
16
|
sha256: JQ_SHA256
|
|
17
17
|
}
|
|
18
|
+
self.patch_files = Dir[File.join(__dir__, 'patches', '*.patch')].sort
|
|
18
19
|
self.configure_options += [
|
|
19
20
|
# "--enable-shared",
|
|
20
21
|
"--enable-static",
|
data/ext/jq/jq_ext.c
CHANGED
|
@@ -15,7 +15,8 @@ static VALUE jv_to_json_string(jv value, int raw, int compact, int sort);
|
|
|
15
15
|
static void raise_jq_error(jv error_value, VALUE exception_class);
|
|
16
16
|
static VALUE rb_jq_filter_impl(const char *json_str, const char *filter_str,
|
|
17
17
|
int raw_output, int compact_output,
|
|
18
|
-
int sort_keys, int multiple_outputs
|
|
18
|
+
int sort_keys, int multiple_outputs,
|
|
19
|
+
int sandbox);
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Convert a jv value to a Ruby JSON string
|
|
@@ -91,11 +92,13 @@ static void raise_jq_error(jv error_value, VALUE exception_class) {
|
|
|
91
92
|
* @param compact_output If true, output compact JSON (jq -c)
|
|
92
93
|
* @param sort_keys If true, sort object keys (jq -S)
|
|
93
94
|
* @param multiple_outputs If true, return array of all results
|
|
95
|
+
* @param sandbox If true, enable sandbox mode (blocks env/include/import)
|
|
94
96
|
* @return Ruby string or array of strings
|
|
95
97
|
*/
|
|
96
98
|
static VALUE rb_jq_filter_impl(const char *json_str, const char *filter_str,
|
|
97
99
|
int raw_output, int compact_output,
|
|
98
|
-
int sort_keys, int multiple_outputs
|
|
100
|
+
int sort_keys, int multiple_outputs,
|
|
101
|
+
int sandbox) {
|
|
99
102
|
jq_state *jq = NULL;
|
|
100
103
|
jv input = jv_invalid();
|
|
101
104
|
VALUE results = Qnil;
|
|
@@ -107,6 +110,10 @@ static VALUE rb_jq_filter_impl(const char *json_str, const char *filter_str,
|
|
|
107
110
|
rb_raise(rb_eJQError, "Failed to initialize jq");
|
|
108
111
|
}
|
|
109
112
|
|
|
113
|
+
if (sandbox) {
|
|
114
|
+
jq_set_sandbox(jq);
|
|
115
|
+
}
|
|
116
|
+
|
|
110
117
|
// Compile filter
|
|
111
118
|
if (!jq_compile(jq, filter_str)) {
|
|
112
119
|
jv error = jq_get_error_message(jq);
|
|
@@ -202,6 +209,7 @@ static VALUE rb_jq_filter_impl(const char *json_str, const char *filter_str,
|
|
|
202
209
|
* [:compact_output (Boolean)] Output compact JSON on a single line. Default: true (set to false for pretty output)
|
|
203
210
|
* [:sort_keys (Boolean)] Sort object keys alphabetically (equivalent to jq -S). Default: false
|
|
204
211
|
* [:multiple_outputs (Boolean)] Return array of all results instead of just the first. Default: false
|
|
212
|
+
* [:sandbox (Boolean)] Enable sandbox mode to block access to environment variables and file imports. Default: true
|
|
205
213
|
*
|
|
206
214
|
* === Returns
|
|
207
215
|
*
|
|
@@ -259,9 +267,10 @@ VALUE rb_jq_filter(int argc, VALUE *argv, VALUE self) {
|
|
|
259
267
|
const char *json_cstr = StringValueCStr(json_str);
|
|
260
268
|
const char *filter_cstr = StringValueCStr(filter_str);
|
|
261
269
|
|
|
262
|
-
// Parse options (default to compact output)
|
|
270
|
+
// Parse options (default to compact output, sandbox enabled)
|
|
263
271
|
int raw_output = 0, compact_output = 1;
|
|
264
272
|
int sort_keys = 0, multiple_outputs = 0;
|
|
273
|
+
int sandbox = 1;
|
|
265
274
|
|
|
266
275
|
if (!NIL_P(opts)) {
|
|
267
276
|
Check_Type(opts, T_HASH);
|
|
@@ -278,11 +287,15 @@ VALUE rb_jq_filter(int argc, VALUE *argv, VALUE self) {
|
|
|
278
287
|
|
|
279
288
|
opt = rb_hash_aref(opts, ID2SYM(rb_intern("multiple_outputs")));
|
|
280
289
|
if (RTEST(opt)) multiple_outputs = 1;
|
|
290
|
+
|
|
291
|
+
opt = rb_hash_aref(opts, ID2SYM(rb_intern("sandbox")));
|
|
292
|
+
if (!NIL_P(opt)) sandbox = RTEST(opt) ? 1 : 0;
|
|
281
293
|
}
|
|
282
294
|
|
|
283
295
|
return rb_jq_filter_impl(json_cstr, filter_cstr,
|
|
284
296
|
raw_output, compact_output,
|
|
285
|
-
sort_keys, multiple_outputs
|
|
297
|
+
sort_keys, multiple_outputs,
|
|
298
|
+
sandbox);
|
|
286
299
|
}
|
|
287
300
|
|
|
288
301
|
/*
|
|
@@ -344,6 +357,8 @@ VALUE rb_jq_validate_filter(VALUE self, VALUE filter) {
|
|
|
344
357
|
rb_raise(rb_eJQError, "Failed to initialize jq");
|
|
345
358
|
}
|
|
346
359
|
|
|
360
|
+
jq_set_sandbox(jq);
|
|
361
|
+
|
|
347
362
|
if (!jq_compile(jq, filter_cstr)) {
|
|
348
363
|
jv error = jq_get_error_message(jq);
|
|
349
364
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
diff -ruN a/src/builtin.c b/src/builtin.c
|
|
2
|
+
--- a/src/builtin.c 2026-02-20 23:06:20
|
|
3
|
+
+++ b/src/builtin.c 2026-02-20 23:09:28
|
|
4
|
+
@@ -1229,6 +1229,11 @@
|
|
5
|
+
static jv f_env(jq_state *jq, jv input) {
|
|
6
|
+
jv_free(input);
|
|
7
|
+
jv env = jv_object();
|
|
8
|
+
+
|
|
9
|
+
+ // A sandboxed filter doesn't have access to environment variables,
|
|
10
|
+
+ // so in such a case we return the empty object without using environ.
|
|
11
|
+
+ if (jq_is_sandbox(jq)) return env;
|
|
12
|
+
+
|
|
13
|
+
const char *var, *val;
|
|
14
|
+
for (char **e = environ; *e != NULL; e++) {
|
|
15
|
+
var = e[0];
|
|
16
|
+
diff -ruN a/src/compile.c b/src/compile.c
|
|
17
|
+
--- a/src/compile.c 2026-02-20 23:06:20
|
|
18
|
+
+++ b/src/compile.c 2026-02-20 23:09:16
|
|
19
|
+
@@ -1346,7 +1346,7 @@
|
|
20
|
+
return errors;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
-int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
|
|
24
|
+
+int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args, int is_sandbox) {
|
|
25
|
+
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode));
|
|
26
|
+
bc->parent = 0;
|
|
27
|
+
bc->nclosures = 0;
|
|
28
|
+
@@ -1356,7 +1356,12 @@
|
|
29
|
+
bc->globals->cfunctions = jv_mem_calloc(MAX(ncfunc, 1), sizeof(struct cfunction));
|
|
30
|
+
bc->globals->cfunc_names = jv_array();
|
|
31
|
+
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null());
|
|
32
|
+
- jv env = jv_invalid();
|
|
33
|
+
+
|
|
34
|
+
+ // When sandboxed, we don't want to expose environment vars to the program,
|
|
35
|
+
+ // so we create an empty object which is already valid. This prevents a
|
|
36
|
+
+ // later step from creating a populated `$ENV` object, because that step
|
|
37
|
+
+ // only does so if the current value for `env` is invalid.
|
|
38
|
+
+ jv env = is_sandbox ? jv_object() : jv_invalid();
|
|
39
|
+
int nerrors = compile(bc, b, lf, args, &env);
|
|
40
|
+
jv_free(args);
|
|
41
|
+
jv_free(env);
|
|
42
|
+
diff -ruN a/src/compile.h b/src/compile.h
|
|
43
|
+
--- a/src/compile.h 2026-02-20 23:06:20
|
|
44
|
+
+++ b/src/compile.h 2026-02-20 23:09:07
|
|
45
|
+
@@ -79,7 +79,7 @@
|
|
46
|
+
jv block_take_imports(block* body);
|
|
47
|
+
jv block_list_funcs(block body, int omit_underscores);
|
|
48
|
+
|
|
49
|
+
-int block_compile(block, struct bytecode**, struct locfile*, jv);
|
|
50
|
+
+int block_compile(block, struct bytecode**, struct locfile*, jv, int is_sandbox);
|
|
51
|
+
|
|
52
|
+
void block_free(block);
|
|
53
|
+
|
|
54
|
+
diff -ruN a/src/execute.c b/src/execute.c
|
|
55
|
+
--- a/src/execute.c 2026-02-20 23:06:20
|
|
56
|
+
+++ b/src/execute.c 2026-02-20 23:08:55
|
|
57
|
+
@@ -41,6 +41,7 @@
|
|
58
|
+
unsigned next_label;
|
|
59
|
+
|
|
60
|
+
int halted;
|
|
61
|
+
+ int sandbox;
|
|
62
|
+
jv exit_code;
|
|
63
|
+
jv error_message;
|
|
64
|
+
|
|
65
|
+
@@ -1059,6 +1060,7 @@
|
|
66
|
+
jq->curr_frame = 0;
|
|
67
|
+
jq->error = jv_null();
|
|
68
|
+
|
|
69
|
+
+ jq->sandbox = 0;
|
|
70
|
+
jq->halted = 0;
|
|
71
|
+
jq->exit_code = jv_invalid();
|
|
72
|
+
jq->error_message = jv_invalid();
|
|
73
|
+
@@ -1237,7 +1239,7 @@
|
|
74
|
+
if (nerrors == 0) {
|
|
75
|
+
nerrors = builtins_bind(jq, &program);
|
|
76
|
+
if (nerrors == 0) {
|
|
77
|
+
- nerrors = block_compile(program, &jq->bc, locations, args2obj(args));
|
|
78
|
+
+ nerrors = block_compile(program, &jq->bc, locations, args2obj(args), jq_is_sandbox(jq));
|
|
79
|
+
}
|
|
80
|
+
} else
|
|
81
|
+
jv_free(args);
|
|
82
|
+
@@ -1312,6 +1314,14 @@
|
|
83
|
+
void jq_get_stderr_cb(jq_state *jq, jq_msg_cb *cb, void **data) {
|
|
84
|
+
*cb = jq->stderr_cb;
|
|
85
|
+
*data = jq->stderr_cb_data;
|
|
86
|
+
+}
|
|
87
|
+
+
|
|
88
|
+
+void jq_set_sandbox(jq_state *jq) {
|
|
89
|
+
+ jq->sandbox = 1;
|
|
90
|
+
+}
|
|
91
|
+
+
|
|
92
|
+
+int jq_is_sandbox(jq_state *jq) {
|
|
93
|
+
+ return jq->sandbox;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void
|
|
97
|
+
diff -ruN a/src/jq.h b/src/jq.h
|
|
98
|
+
--- a/src/jq.h 2026-02-20 23:06:20
|
|
99
|
+
+++ b/src/jq.h 2026-02-20 23:08:28
|
|
100
|
+
@@ -30,6 +30,8 @@
|
|
101
|
+
jv jq_next(jq_state *);
|
|
102
|
+
void jq_teardown(jq_state **);
|
|
103
|
+
|
|
104
|
+
+void jq_set_sandbox(jq_state *);
|
|
105
|
+
+int jq_is_sandbox(jq_state *);
|
|
106
|
+
void jq_halt(jq_state *, jv, jv);
|
|
107
|
+
int jq_halted(jq_state *);
|
|
108
|
+
jv jq_get_exit_code(jq_state *);
|
|
109
|
+
diff -ruN a/src/linker.c b/src/linker.c
|
|
110
|
+
--- a/src/linker.c 2026-02-20 23:06:20
|
|
111
|
+
+++ b/src/linker.c 2026-02-20 23:09:45
|
|
112
|
+
@@ -251,6 +251,15 @@
|
|
113
|
+
i--;
|
|
114
|
+
jv dep = jv_array_get(jv_copy(deps), i);
|
|
115
|
+
|
|
116
|
+
+ // Loading dependencies is not allowed when running in sandbox mode.
|
|
117
|
+
+ if (jq_is_sandbox(jq)) {
|
|
118
|
+
+ jq_report_error(jq, jv_string("jq: error: Loading dependencies (with import or include) is not allowed in sandbox mode"));
|
|
119
|
+
+ jv_free(deps);
|
|
120
|
+
+ jv_free(jq_origin);
|
|
121
|
+
+ jv_free(lib_origin);
|
|
122
|
+
+ return 1;
|
|
123
|
+
+ }
|
|
124
|
+
+
|
|
125
|
+
const char *as_str = NULL;
|
|
126
|
+
int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE;
|
|
127
|
+
int raw = 0;
|
|
128
|
+
@@ -420,16 +429,18 @@
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
- jv home = get_home();
|
|
133
|
+
- if (jv_is_valid(home)) {
|
|
134
|
+
- /* Import ~/.jq as a library named "" found in $HOME or %USERPROFILE% */
|
|
135
|
+
- block import = gen_import_meta(gen_import("", NULL, 0),
|
|
136
|
+
- gen_const(JV_OBJECT(
|
|
137
|
+
- jv_string("optional"), jv_true(),
|
|
138
|
+
- jv_string("search"), home)));
|
|
139
|
+
- program = BLOCK(import, program);
|
|
140
|
+
- } else { // silently ignore if home dir not determined
|
|
141
|
+
- jv_free(home);
|
|
142
|
+
+ if (!jq_is_sandbox(jq)) {
|
|
143
|
+
+ jv home = get_home();
|
|
144
|
+
+ if (jv_is_valid(home)) {
|
|
145
|
+
+ /* Import ~/.jq as a library named "" found in $HOME or %USERPROFILE% */
|
|
146
|
+
+ block import = gen_import_meta(gen_import("", NULL, 0),
|
|
147
|
+
+ gen_const(JV_OBJECT(
|
|
148
|
+
+ jv_string("optional"), jv_true(),
|
|
149
|
+
+ jv_string("search"), home)));
|
|
150
|
+
+ program = BLOCK(import, program);
|
|
151
|
+
+ } else { // silently ignore if home dir not determined
|
|
152
|
+
+ jv_free(home);
|
|
153
|
+
+ }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
nerrors = process_dependencies(jq, jq_get_jq_origin(jq), jq_get_prog_origin(jq), &program, &lib_state);
|
data/jq.gemspec
CHANGED
data/lib/jq.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Persona Identities
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-02-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: mini_portile2
|
|
@@ -89,6 +89,7 @@ files:
|
|
|
89
89
|
- ext/jq/extconf.rb
|
|
90
90
|
- ext/jq/jq_ext.c
|
|
91
91
|
- ext/jq/jq_ext.h
|
|
92
|
+
- ext/jq/patches/0001-add-sandbox-mode.patch
|
|
92
93
|
- jq.gemspec
|
|
93
94
|
- lib/jq.rb
|
|
94
95
|
- sig/jq.rbs
|