fix(tools): flag bun/deno inline script execution for approval
This commit is contained in:
70
tests/tools/test_approval_bun_deno_eval.py
Normal file
70
tests/tools/test_approval_bun_deno_eval.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Bun and Deno evaluate inline scripts through forms the interpreter flag
|
||||
tables did not know: ``bun -e/--eval``, and Deno's bare ``eval`` subcommand
|
||||
(``deno eval "code"``). They must reach the same approval classification as
|
||||
``node -e`` / ``python -c``. See issue #116012.
|
||||
"""
|
||||
|
||||
from tools.approval import detect_dangerous_command
|
||||
|
||||
|
||||
class TestBunDenoInlineScriptExecution:
|
||||
def test_inline_eval_detected(self):
|
||||
for cmd in (
|
||||
'bun -e "console.log(1)"',
|
||||
'bun --eval "console.log(1)"',
|
||||
'deno eval "console.log(1)"',
|
||||
'deno -e "console.log(1)"',
|
||||
'deno --eval "console.log(1)"',
|
||||
# Command-position wrappers must not shield the interpreter.
|
||||
'sudo bun -e "console.log(1)"',
|
||||
'sudo deno eval "console.log(1)"',
|
||||
):
|
||||
dangerous, key, _ = detect_dangerous_command(cmd)
|
||||
assert dangerous is True, cmd
|
||||
assert key == "script execution via -e/-c flag", cmd
|
||||
|
||||
def test_windows_executable_spelling_detected(self):
|
||||
# Windows resolves executable names case-insensitively; `_interpreter_family`
|
||||
# lowercases the basename, so BUN.EXE / Deno.exe must be classified too.
|
||||
for cmd in (
|
||||
'BUN.EXE -e "console.log(1)"',
|
||||
'Deno.exe eval "console.log(1)"',
|
||||
):
|
||||
dangerous, key, _ = detect_dangerous_command(cmd)
|
||||
assert dangerous is True, cmd
|
||||
assert key == "script execution via -e/-c flag", cmd
|
||||
|
||||
def test_interpreter_heredoc_detected(self):
|
||||
for cmd in (
|
||||
'bun << "EOF"\nconsole.log("pwned")\nEOF',
|
||||
"deno << 'EOF'\nconsole.log(\"pwned\")\nEOF",
|
||||
):
|
||||
dangerous, key, _ = detect_dangerous_command(cmd)
|
||||
assert dangerous is True, cmd
|
||||
assert key == "script execution via heredoc", cmd
|
||||
|
||||
def test_malformed_quoting_fails_closed(self):
|
||||
# An unterminated quote on a bun/deno inline script must fail closed like the
|
||||
# other interpreter families, not silently skip the family.
|
||||
dangerous, key, _ = detect_dangerous_command('bun -e "console.log(1)')
|
||||
assert dangerous is True
|
||||
assert key == "command parser limit or malformed executable payload"
|
||||
|
||||
def test_plain_bun_deno_invocations_not_flagged(self):
|
||||
"""Everyday subcommands that run project files must stay safe."""
|
||||
for cmd in (
|
||||
"bun install",
|
||||
"bun run build",
|
||||
"deno run server.ts",
|
||||
"deno test",
|
||||
"deno lint",
|
||||
# A file literally named eval.ts is a `run` operand, not the eval subcommand.
|
||||
"deno run eval.ts",
|
||||
):
|
||||
dangerous, _, _ = detect_dangerous_command(cmd)
|
||||
assert dangerous is False, cmd
|
||||
|
||||
def test_quoted_prose_about_deno_eval_not_flagged(self):
|
||||
# `deno eval` inside quotes is data for echo, not a command position.
|
||||
dangerous, _, _ = detect_dangerous_command("echo 'use deno eval for that'")
|
||||
assert dangerous is False
|
||||
@@ -581,10 +581,12 @@ _INTERPRETER_NAME_RES = tuple((family, re.compile(pattern)) for family, pattern
|
||||
("python", r"py(?:\.exe)?|python[23]?(?:\.\d+)*(?:\.exe)?"), ("node", r"node(?:js)?(?:\.exe)?"),
|
||||
("perl", r"perl[0-9]*(?:\.\d+)*(?:\.exe)?"), ("ruby", r"ruby[0-9.]*(?:\.exe)?"), ("php", r"php(?:\.exe)?"),
|
||||
("powershell", r"powershell(?:\.exe)?|pwsh(?:\.exe)?"),
|
||||
("bun", r"bun(?:\.exe)?"), ("deno", r"deno(?:\.exe)?"),
|
||||
))
|
||||
_INTERPRETER_EXEC_FLAGS = {
|
||||
"python": {"-c"}, "node": {"-e", "--eval", "-p", "--print"}, "perl": {"-e", "--eval"}, "ruby": {"-e"},
|
||||
"php": {"-r"}, "powershell": {"-command", "-c", "-file", "-f"},
|
||||
"bun": {"-e", "--eval"}, "deno": {"eval", "-e", "--eval"},
|
||||
}
|
||||
_INTERPRETER_WITH_ARG = {
|
||||
"python": {"-W", "-X", "--check-hash-based-pycs"},
|
||||
@@ -594,6 +596,7 @@ _INTERPRETER_WITH_ARG = {
|
||||
"php": {"-c", "-d", "-z"},
|
||||
"powershell": {"-configurationname", "-custompipename", "-executionpolicy", "-inputformat", "-outputformat",
|
||||
"-settingsfile", "-version", "-windowstyle", "-workingdirectory"},
|
||||
"bun": {"--config", "--cwd", "--env-file", "--preload", "--require"}, "deno": set(),
|
||||
}
|
||||
_READ_TOOL_EXEC_FLAGS = {
|
||||
"sort": {"--compress-program"}, "rg": {"--pre", "--hostname-bin"}, "ag": {"--pager"},
|
||||
@@ -840,6 +843,10 @@ def _iter_top_level_shell_segments(command: str):
|
||||
def _interpreter_exec_flag(family: str, args: list[str]) -> str | None:
|
||||
"""Return an execution-bearing interpreter option, if present."""
|
||||
flags, with_arg = _INTERPRETER_EXEC_FLAGS[family], _INTERPRETER_WITH_ARG[family]
|
||||
# Deno evaluates inline scripts via a bare `eval` subcommand rather than a dash flag, and
|
||||
# only as the first argument; a later positional `eval` stays data.
|
||||
if family == "deno" and args and args[0].lower() == "eval":
|
||||
return "eval"
|
||||
powershell = family == "powershell"
|
||||
skip_value = False
|
||||
for token in args:
|
||||
|
||||
Reference in New Issue
Block a user