Skip to content

Commit

Permalink
Update --sandbox flag behavior to clear environment variables.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jemc committed Apr 16, 2024
1 parent 45ac611 commit 2fa1dcb
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 4 deletions.
8 changes: 8 additions & 0 deletions docs/content/manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ sections:
operations that would allow the filter code to access data other
than the input data that is explicitly specified in the invocation.
This flag also hides all environment variables from the enviroment
where jq was run by setting `$ENV` and `env` to be an empty object.
If you need to pass named arguments to a sandboxed jq filter, use the
`--arg` and/or `--argjson` options to pass them explicitly.
* `--binary` / `-b`:
Windows users using WSL, MSYS2, or Cygwin, should use this option
Expand Down Expand Up @@ -2019,6 +2024,9 @@ sections:
`env` outputs an object representing jq's current environment.
`$ENV` and `env` will be an empty object if jq was run with the
`--sandbox` flag.
At the moment there is no builtin for setting environment
variables.
Expand Down
6 changes: 6 additions & 0 deletions jq.1.prebuilt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,11 @@ extern char **environ;
static jv f_env(jq_state *jq, jv input) {
jv_free(input);
jv env = jv_object();

// A sandboxed filter doesn't have access to environment variables,
// so in such a case we return the empty object without using environ.
if (jq_is_sandbox(jq)) return env;

const char *var, *val;
for (char **e = environ; *e != NULL; e++) {
var = e[0];
Expand Down
10 changes: 8 additions & 2 deletions src/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1367,7 +1367,7 @@ static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args, jv
return errors;
}

int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args, int is_sandbox) {
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode));
bc->parent = 0;
bc->nclosures = 0;
Expand All @@ -1377,7 +1377,13 @@ int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
bc->globals->cfunctions = jv_mem_calloc(ncfunc, sizeof(struct cfunction));
bc->globals->cfunc_names = jv_array();
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null());
jv env = jv_invalid();

// When sandboxed, we don't want to expose environment vars to the program,
// so we create an empty object which is already valid. This prevents a
// later step from creating a populated `$ENV` object, because that step
// only does so if the current value for `env` is invalid.
jv env = is_sandbox ? jv_object() : jv_invalid();

int nerrors = compile(bc, b, lf, args, &env);
jv_free(args);
jv_free(env);
Expand Down
2 changes: 1 addition & 1 deletion src/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ block block_drop_unreferenced(block body);
jv block_take_imports(block* body);
jv block_list_funcs(block body, int omit_underscores);

int block_compile(block, struct bytecode**, struct locfile*, jv);
int block_compile(block, struct bytecode**, struct locfile*, jv, int is_sandbox);

void block_free(block);

Expand Down
2 changes: 1 addition & 1 deletion src/execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1246,7 +1246,7 @@ int jq_compile_args(jq_state *jq, const char* str, jv args) {
if (nerrors == 0) {
nerrors = builtins_bind(jq, &program);
if (nerrors == 0) {
nerrors = block_compile(program, &jq->bc, locations, args2obj(args));
nerrors = block_compile(program, &jq->bc, locations, args2obj(args), jq_is_sandbox(jq));
}
} else
jv_free(args);
Expand Down
22 changes: 22 additions & 0 deletions tests/shtest
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,28 @@ if $VALGRIND $Q $JQ -L ./tests/modules --sandbox -n 'import "a" as a; empty'; th
exit 1
fi

## Test environment variable access

if [ "$(FOO=foo $VALGRIND $Q $JQ -nr '$ENV.FOO')" != foo ]; then
echo "couldn't read an environment variable via \$ENV" 1>&2
exit 1
fi

if [ "$(FOO=foo $VALGRIND $Q $JQ --sandbox -nr '$ENV.FOO')" != null ]; then
echo "\$ENV should have been empty due to the sandbox flag" 1>&2
exit 1
fi

if [ "$(FOO=foo $VALGRIND $Q $JQ -nr 'env.FOO')" != foo ]; then
echo "couldn't read an environment variable via \env" 1>&2
exit 1
fi

if [ "$(FOO=foo $VALGRIND $Q $JQ --sandbox -nr 'env.FOO')" != null ]; then
echo "\env should have been empty due to the sandbox flag" 1>&2
exit 1
fi

## Halt

if ! $VALGRIND $Q $JQ -n halt; then
Expand Down

0 comments on commit 2fa1dcb

Please sign in to comment.