Conversation
…a shell
ScriptNotifier substituted task data into an operator-supplied template and ran the result through a shell (subprocess.run(..., shell=True)). A task's goal reaches {title}/{message}, and POST /api/run requires no authentication, so an unauthenticated task submission could be interpreted as shell syntax and executed as the ARTEMIS server user (CWE-78).
Execute the template as an argument vector instead: shlex.split() resolves the operator's own quoting, placeholders are substituted per token, and the command is launched with shell=False, so task data can no longer contribute shell syntax.
Quoting the substituted values would not have been sufficient: the template may already wrap the placeholder in quotes, in which case the added quotes cancel out and the value is parsed as shell syntax again.
Behavior change: shell features in the template (pipes, &&, redirection, $VAR expansion, globbing) no longer apply; move that logic into the script itself. A missing command now makes notify() return False rather than True.
Adds two regression tests in tests/unit/mcp/test_notifiers.py; both fail on main without this change.
…preted Document the behaviour change introduced by executing the template as an argument vector: shell features in the template no longer apply, so that logic belongs in the script itself. Also note that quotes around a placeholder no longer change how the value is passed.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ScriptNotifierbuilds its command line by substituting placeholders withstr.replace()and executed the result through a shell (shell=True). The template is operator-supplied, but the substituted values are task data: a task'sgoalreaches{title}and{message}(apps/admin_console/services/task_queue_service.py:788-796) and is fully controlled by whoever submits the task, which requires no authentication (POST /api/run).Because the command line was handed to
/bin/sh, that task data could be interpreted as shell syntax. The documented example (mcp_server/README.md:48) was not safe either: the notification message embeds single quotes around the goal, so inserting it into the template's quoted{message}placeholder closed that quote early and left the goal unquoted.Reported through the Google OSS VRP.
Changes
mcp_server/notifiers/script.py— run the template as an argument vector instead of through a shell:shlex.split(cmd_template)resolves the operator's own quoting, placeholders are substituted per token, then the command is launched withshell=False. Task data can no longer contribute shell syntax because no shell is involved.mcp_server/notifiers/script.py— document the behaviour in the class docstring.mcp_server/README.md— note it in the notification-channel table.tests/unit/mcp/test_notifiers.py— two regression tests: substituted values must arrive as exactly one literal argument each, and task data containing shell metacharacters must not execute.Quoting the substituted values (e.g.
shlex.quote) would not have been sufficient: the template may already wrap the placeholder in quotes, in which case the added quotes cancel out and the value is parsed as shell syntax again. I verified this — the first version of this patch usedshlex.quoteand the regression test caught it.Compatibility
The intended feature is preserved: an operator can still configure a command and receive the notification data, and the documented usage keeps working unchanged —
shlex.splitresolves the operator's quoting, somy-script --title '{title}' --message '{message}'still passes both values as single arguments.Two deliberate behaviour changes, both documented in the docstring and the README:
&&, redirection,$VARexpansion and globbing. An operator relying on those should move that logic into their script and reference it as a single command.notify()now returnsFalse. Previously the shell reported the failure butnotify()still returnedTrue.Verification
make typecheck— 0 errors, 0 warningsuv run ruff check/ruff format --checkon the changed files — cleanuv run pytest tests/unit/mcp/test_notifiers.py -v— 11 passedmainwithout this change (temporarily reverted the patch to confirm): the end-to-end test's failure output shows the injected command actually running, so it is a real regression test rather than an empty assertionmake test— pass/fail counts unchanged versusmain(2107 → 2109 passed; the two additional passes are the new tests, the pre-existing unrelated failures are identical)