Conversation
The backend can ask the frontend to run a JupyterLab command by id. No code checked that id, so any command the application had registered was reachable from the response stream. RUN_UI_COMMAND_ALLOWLIST holds the ids run_ui_command actually sends. RESPONSE_BUTTON_COMMAND_ALLOWLIST is deliberately narrower, because the only ButtonData the backend constructs offers the settings dialog. executeResponseStreamCommand is a single chokepoint, so the sinks cannot drift into checking the policy inconsistently. A refused id returns an error string on the callback rather than leaving the caller waiting. The five chat-user-input callers keep an ungated path on purpose. Gating them would refuse a form the user had already answered and leave the backend future unresolved. One limit worth stating plainly: run-command-in-terminal remains allowlisted because the backend sends it, so shell reachability is unchanged by this patch. The gate filters command ids, not arguments.
This was referenced Sep 14, 2026
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.
Closes #441
What this adds
The backend can ask the frontend to run a JupyterLab command by id, through
run_ui_commandand through theButtonDataa response stream can render. Nothing validated that id, so any command registered in the application was reachable from the response stream.This adds two allowlists in
src/command-ids.ts:RUN_UI_COMMAND_ALLOWLIST: the ids thatrun_ui_commandactually sends, including the bare JupyterLab idsdocmanager:openanddocmanager:save.RESPONSE_BUTTON_COMMAND_ALLOWLIST: onlyopenConfigurationDialog, since that is the oneButtonDatathe backend constructs.executeResponseStreamCommandis a single chokepoint, so the sinks cannot drift into checking the policy inconsistently. A refused id returns an error string on the callback rather than leaving the caller waiting.Scope limit, stated plainly
notebook-intelligence:run-command-in-terminalstays allowlisted because the backend sends it. Shell execution is therefore exactly as reachable after this patch as before. The gate filters command ids, not arguments. Narrowing that path means validating arguments, which is a separate change.Tests
tests/ts/response-stream-command-allowlist.test.ts: 18 tests over the predicate, both list contents, and the refusal path of the wrapper.tests/test_run_ui_command_allowlist_parity.py: cross-checks the allowlist against the Python call sites in both directions, so a newrun_ui_commandid that is not allowlisted fails the suite instead of silently breaking a tool in the browser.Verified: pytest 1834 passed, tsc clean, jest 441 passed, stylelint and eslint clean. Seven mutants, including deleting a wrapper call site, gutting the gate, and widening either list, each fail a named test.
Follow-ups and known limits
run_ui_commandcall sites as text. It is blind to a keyword-argument or variable id shape, so it asserts that every call site it finds is readable and fails loudly if a new shape appears, rather than dropping it in silence.chat-user-inputcallers keep an ungated path on purpose. Gating them would refuse a form the user had already answered and leave the backend future unresolved.