Follow-up from the review of #98 (sentinel-mcp).
run_kql rejects Kusto control commands by checking the whole-query prefix:
query.startswith(".") after stripping leading whitespace and non-printable
characters. A dot-command on a second line therefore reaches client.query():
print 1
.show diagnostics
Confirmed reaching the API in both the newline form and the ;-separated form
(Heartbeat | take 1; .drop table X).
Likely unexploitable today. The Log Analytics /v1/workspaces/{id}/query
endpoint is query-only — control commands go to a different API — so a non-leading
.drop should land as a KQL syntax error rather than an execution. But the server's
charter is reject before dispatch, and "the backend probably won't run it" is a
weaker guarantee than "we never sent it." This is also the one place a model-supplied
free-text string reaches the API directly, which CLAUDE.md flags as needing a guided,
validated helper.
The newline vector is cheap to close. Reject the query if any line's strip()
starts with .. No legitimate KQL line begins with a dot. Note this is not the
same as rejecting ;, which would break legitimate let statements — an earlier
triage conflated the two vectors and deferred more than was necessary.
The same-line ; form needs more thought and can stay open.
- Add the per-line check in
servers/sentinel-mcp/f0_sentinel_mcp/tools.py (run_kql).
- Test both the newline vector and that ordinary multi-line KQL still dispatches.
- Never test this against a live tenant.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XYTy7da8Z5ZHhkwcCpjojZ
Follow-up from the review of #98 (sentinel-mcp).
run_kqlrejects Kusto control commands by checking the whole-query prefix:query.startswith(".")after stripping leading whitespace and non-printablecharacters. A dot-command on a second line therefore reaches
client.query():Confirmed reaching the API in both the newline form and the
;-separated form(
Heartbeat | take 1; .drop table X).Likely unexploitable today. The Log Analytics
/v1/workspaces/{id}/queryendpoint is query-only — control commands go to a different API — so a non-leading
.dropshould land as a KQL syntax error rather than an execution. But the server'scharter is reject before dispatch, and "the backend probably won't run it" is a
weaker guarantee than "we never sent it." This is also the one place a model-supplied
free-text string reaches the API directly, which CLAUDE.md flags as needing a guided,
validated helper.
The newline vector is cheap to close. Reject the query if any line's
strip()starts with
.. No legitimate KQL line begins with a dot. Note this is not thesame as rejecting
;, which would break legitimateletstatements — an earliertriage conflated the two vectors and deferred more than was necessary.
The same-line
;form needs more thought and can stay open.servers/sentinel-mcp/f0_sentinel_mcp/tools.py(run_kql).🤖 Generated with Claude Code
https://claude.ai/code/session_01XYTy7da8Z5ZHhkwcCpjojZ