Skip to content

Commit 2fa1dcb

Browse files
committed
Update --sandbox flag behavior to clear environment variables.
In a security-sensitive environment where the `--sandbox` flag can be used to mitigate some categories of threats from untrusted filter code and/or untrusted JSON data, it is also desirable to prevent leaking environment variable values (which often can include secrets in some environments). This commit does so by updating the behavior of `--sandbox` to also clear the environment variables seen by the jq filter code in the `$ENV` value and `env` builtin.
1 parent 45ac611 commit 2fa1dcb

File tree

7 files changed

+51
-4
lines changed

7 files changed

+51
-4
lines changed

docs/content/manual/manual.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ sections:
297297
operations that would allow the filter code to access data other
298298
than the input data that is explicitly specified in the invocation.
299299
300+
This flag also hides all environment variables from the enviroment
301+
where jq was run by setting `$ENV` and `env` to be an empty object.
302+
If you need to pass named arguments to a sandboxed jq filter, use the
303+
`--arg` and/or `--argjson` options to pass them explicitly.
304+
300305
* `--binary` / `-b`:
301306
302307
Windows users using WSL, MSYS2, or Cygwin, should use this option
@@ -2019,6 +2024,9 @@ sections:
20192024
20202025
`env` outputs an object representing jq's current environment.
20212026
2027+
`$ENV` and `env` will be an empty object if jq was run with the
2028+
`--sandbox` flag.
2029+
20222030
At the moment there is no builtin for setting environment
20232031
variables.
20242032

jq.1.prebuilt

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/builtin.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,11 @@ extern char **environ;
11371137
static jv f_env(jq_state *jq, jv input) {
11381138
jv_free(input);
11391139
jv env = jv_object();
1140+
1141+
// A sandboxed filter doesn't have access to environment variables,
1142+
// so in such a case we return the empty object without using environ.
1143+
if (jq_is_sandbox(jq)) return env;
1144+
11401145
const char *var, *val;
11411146
for (char **e = environ; *e != NULL; e++) {
11421147
var = e[0];

src/compile.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,7 +1367,7 @@ static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args, jv
13671367
return errors;
13681368
}
13691369

1370-
int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
1370+
int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args, int is_sandbox) {
13711371
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode));
13721372
bc->parent = 0;
13731373
bc->nclosures = 0;
@@ -1377,7 +1377,13 @@ int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
13771377
bc->globals->cfunctions = jv_mem_calloc(ncfunc, sizeof(struct cfunction));
13781378
bc->globals->cfunc_names = jv_array();
13791379
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null());
1380-
jv env = jv_invalid();
1380+
1381+
// When sandboxed, we don't want to expose environment vars to the program,
1382+
// so we create an empty object which is already valid. This prevents a
1383+
// later step from creating a populated `$ENV` object, because that step
1384+
// only does so if the current value for `env` is invalid.
1385+
jv env = is_sandbox ? jv_object() : jv_invalid();
1386+
13811387
int nerrors = compile(bc, b, lf, args, &env);
13821388
jv_free(args);
13831389
jv_free(env);

src/compile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ block block_drop_unreferenced(block body);
7979
jv block_take_imports(block* body);
8080
jv block_list_funcs(block body, int omit_underscores);
8181

82-
int block_compile(block, struct bytecode**, struct locfile*, jv);
82+
int block_compile(block, struct bytecode**, struct locfile*, jv, int is_sandbox);
8383

8484
void block_free(block);
8585

src/execute.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,7 @@ int jq_compile_args(jq_state *jq, const char* str, jv args) {
12461246
if (nerrors == 0) {
12471247
nerrors = builtins_bind(jq, &program);
12481248
if (nerrors == 0) {
1249-
nerrors = block_compile(program, &jq->bc, locations, args2obj(args));
1249+
nerrors = block_compile(program, &jq->bc, locations, args2obj(args), jq_is_sandbox(jq));
12501250
}
12511251
} else
12521252
jv_free(args);

tests/shtest

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,28 @@ if $VALGRIND $Q $JQ -L ./tests/modules --sandbox -n 'import "a" as a; empty'; th
396396
exit 1
397397
fi
398398

399+
## Test environment variable access
400+
401+
if [ "$(FOO=foo $VALGRIND $Q $JQ -nr '$ENV.FOO')" != foo ]; then
402+
echo "couldn't read an environment variable via \$ENV" 1>&2
403+
exit 1
404+
fi
405+
406+
if [ "$(FOO=foo $VALGRIND $Q $JQ --sandbox -nr '$ENV.FOO')" != null ]; then
407+
echo "\$ENV should have been empty due to the sandbox flag" 1>&2
408+
exit 1
409+
fi
410+
411+
if [ "$(FOO=foo $VALGRIND $Q $JQ -nr 'env.FOO')" != foo ]; then
412+
echo "couldn't read an environment variable via \env" 1>&2
413+
exit 1
414+
fi
415+
416+
if [ "$(FOO=foo $VALGRIND $Q $JQ --sandbox -nr 'env.FOO')" != null ]; then
417+
echo "\env should have been empty due to the sandbox flag" 1>&2
418+
exit 1
419+
fi
420+
399421
## Halt
400422

401423
if ! $VALGRIND $Q $JQ -n halt; then

0 commit comments

Comments
 (0)