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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85a8def8b4728ce33cb6bf3322969e08d95abd40dec1efd317a7a0de764f333a
4
- data.tar.gz: 96a55036c41973603dba2bbcea2970ebd6312fa2334c1afb79feba3327015228
3
+ metadata.gz: 4e79fcd6b493cfe8b9c153f73b09ca0e53298b24d6fcd0215efd956be60d965f
4
+ data.tar.gz: c3c3dd6b922e064fad34935cacef4d4c16799b62eb531965605ef9b4db132184
5
5
  SHA512:
6
- metadata.gz: 301b55c4c77ec72aca48de6cf73d8ef421710b0cea7c6aa041191c0d39cbbe1763530920bdcf98c333d80c07ce4677c5fad78f3841ec8448e7ce2db3571a9148
7
- data.tar.gz: 6d3c8b437eff186dc30e24f6add659312c37c4f0c4f73c602f172d93655c2eb02362ed128d21dd59f6b47bbb2f14805c1214b20ef71348eae92d4710aebd7b6d
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jq (1.0.0)
4
+ jq (1.1.0)
5
5
  mini_portile2 (~> 2.8)
6
6
 
7
7
  GEM
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "jq"
5
- spec.version = "1.0.0"
5
+ spec.version = "1.1.0"
6
6
  spec.authors = ["Persona Identities"]
7
7
  spec.email = ["rubygems@withpersona.com"]
8
8
 
data/lib/jq.rb CHANGED
@@ -45,7 +45,7 @@ module JQ
45
45
  ##
46
46
  # The gem version number
47
47
  #
48
- VERSION = "1.0.0"
48
+ VERSION = "1.1.0"
49
49
 
50
50
  ##
51
51
  # Base exception class for all jq-related errors.
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.0.0
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-01-30 00:00:00.000000000 Z
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