Review the safety boundary

Updated Oct 16, 2026

Netsuke reduces some common quoting mistakes, but it is not a sandbox:

  • {{ ins }} and {{ outs }} are quoted as path arguments.
  • Arbitrary Jinja values in command and script are not automatically shell-quoted.
  • On Windows, legacy recipes use the PowerShell contract above unless NETSUKE_WINDOWS_SHELL=bash selects the explicit Bash compatibility route. On Unix, scripts use /bin/sh -e.
  • shell, grep, fetch, filesystem helpers, and ordinary recipes interact with the host.
  • The file-reading filters (contents, linecount, hash, digest) process at most the configured byte budget, open the final path entry without following symlinks, and require the opened object to be a regular file. See Configure file reading limits.
  • glob restricts its filesystem metadata access to a capability handle scoped to the pattern's literal directory prefix, so it cannot inspect anything outside the subtree the pattern can match; the pattern match walk itself still uses ambient filesystem access.
  • Verbose glob tracing replaces every caller-controlled path field — patterns, prefixes, and sampled relative matches — with the stable <redacted> marker. Aggregate metrics retain only bounded status and reason data. Error messages may retain the original input so invalid patterns can be explained.
  • raw template output and handwritten shell fragments remain the manifest author's responsibility.
  • On Unix and in the explicit Windows Bash compatibility route, each command list entry is joined into a single shell chain; a later entry inherits the working directory, environment, and shell variables left by an earlier entry, and runs only when that earlier entry exits with status zero. A failed entry may still leave side effects behind before it halts the chain. The generated brace/eval boundary keeps comments and trailing control operators inside an entry from changing the chain's structure. An entry may start at most one background job; Netsuke waits for that job before moving to a later entry, and rejects an entry that starts more than one background job during Ninja generation. It also rejects an entry whose nested eval payload makes the background-job count dynamic because the wrapper cannot safely determine which jobs to wait for. A direct simple exec, optionally prefixed by shell assignments, is supervised so its success or failure retains the list's status semantics: a successful exec ends the remaining chain, while structured or nested exec forms are rejected during Ninja generation. Failure diagnostics include the action fingerprint and one-based entry position when Netsuke can attribute the failed list entry.
  • On Windows in the default PowerShell route, each command list shares one PowerShell process. Netsuke checks $LASTEXITCODE immediately after every generated list entry and stops before a later entry can overwrite a failure. Multiple native commands inside one entry are not individually instrumented; terminating PowerShell errors also stop the list. The POSIX brace-group, eval, background-job, and exec restrictions do not apply to this route.
  • Write shell dollar expressions normally. $PATH, $RUSTFLAGS, $in, $out, $ins, and $outs are literal shell variables in both command: and script: recipes; PowerShell routes use $name or $env:NAME. Netsuke escapes those dollars as $$ only in generated Ninja so the shell receives them unchanged. {{ ins }} and {{ outs }} are the only Netsuke markers for input and output paths. On POSIX and Bash routes, Netsuke encodes a marker for its unquoted, single-quoted, or double-quoted shell context, and rejects one in a command substitution or backticks. In PowerShell, use markers unquoted; Netsuke rejects quoted and command-substitution marker sites rather than risking a context escape. PowerShell uses backticks as its native escape syntax, so they do not suppress marker interpolation.
  • Build and default-target paths escape a literal space as a $ followed by a space, Ninja's own path escape. They reject | and control characters, which Ninja's path grammar cannot represent, and also reject $ and colon, which Ninja can escape but Netsuke does not yet accept.
  • Emitted metadata is escaped separately from paths. A literal dollar in a description, depfile, deps, or pool is doubled, so a depfile of $out.d is written as $$out.d and Ninja reads the literal name rather than a variable reference. Generation rejects newline, carriage-return, and NUL characters in those fields.
  • Because that dollar is a literal, a description does not expand Ninja variables. A description of CC $out prints CC $out, not the output path. Dynamic metadata has no replacement mechanism yet; a description that names its output is follow-up work.
  • Migration: replace the historical manifest spelling $$PATH with $PATH. On POSIX and Bash routes, $$ is the shell's process identifier; PowerShell interprets $$ as its automatic variable containing the last token received by the session. Keeping the extra dollar can therefore change the command's result. Replace any former $in or $out path placeholder with {{ ins }} or {{ outs }} respectively. Those are the only Netsuke markers; $in, $out, $ins, and $outs are shell variables.

The recipe interpolation terms have distinct roles:

  • Marker: a Netsuke-owned {{ ins }} or {{ outs }} placeholder that Netsuke rewrites to input or output paths.
  • Internal token: the INS_TOKEN or OUTS_TOKEN sentinel that exists only between the first rendering stage and the later interpolation stage.
  • Shell variable: handwritten shell text such as $PATH, $in, $out, $ins, or $outs, which Netsuke leaves unchanged before the Ninja backend doubles the dollar sign.

Do not run an untrusted Netsukefile. Prefer explicit inputs, avoid embedding secrets in commands or URLs, and pin dependencies used by recipes.