mirror of
https://github.com/hanzoai/datastore.git
synced 2026-10-30 14:48:37 +00:00
Complete the filename-level debrand the 26.6 merge surfaced: 238 files (test .sql/.reference pairs, ci/jobs + workflows, utils, docs, package specs) renamed clickhouse->datastore (all case variants incl clickHouse), with references updated. Build-critical product files were already done. Excludes contrib (third-party).
91 lines
3.6 KiB
Python
91 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""PreToolUse hook for the investigate-ci reduced-prompt profile.
|
|
|
|
Auto-approves ONLY the exact safe command shapes the skill runs every time, so
|
|
they need no allow-list entry (whose wildcard tail cannot be constrained) and no
|
|
prompt. Two families:
|
|
|
|
1. The play.clickhouse.com read-only SELECT history query.
|
|
2. node .claude/tools/fetch_ci_report.js against known CI-report hosts, with
|
|
only read/report flags (NO file-writing forms).
|
|
|
|
The hook never auto-approves a write: --download-logs and stdout redirection are
|
|
deliberately excluded, because a string-pinned path cannot be made symlink-safe
|
|
(a planted tmp/investigate symlink would redirect the write outside). Those write
|
|
forms, and everything else, fall through to the normal permission prompt -- this
|
|
is an allowlist (default-deny). Both families also reject shell composition/
|
|
substitution, so the dangerous variants (curl --data-binary @<file>, $(...), a
|
|
second piped command, an arbitrary URL/host) prompt instead of running.
|
|
|
|
Output contract: print a PreToolUse "allow" decision only on a match; otherwise
|
|
print nothing and exit 0 so normal handling (the prompt) runs. Never emits "deny".
|
|
"""
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
# --- Family 1: the play.clickhouse.com read-only SELECT query -----------------
|
|
# Quote-delimited body that must start with SELECT and contain no " $ ` -- so
|
|
# @file, a second --data-binary, and $(...)/backtick substitution all fail.
|
|
PLAY = re.compile(
|
|
r"""curl -sS 'https://play\.clickhouse\.com/\?user=play' --data-binary "\s*SELECT[^"$`]*\""""
|
|
)
|
|
|
|
# --- Family 2: fetch_ci_report.js against known CI-report hosts ---------------
|
|
# Full-match allowlist. The URL is quote-delimited and excludes " $ ` so it
|
|
# cannot break out or inject substitution, even though it may contain & ? = (a
|
|
# query string). Flags are an allowlist; --download-logs and stdout redirect are
|
|
# pinned to tmp/investigate. Anything outside this exact structure -- chaining,
|
|
# pipes, extra redirects, other hosts/flags -- fails to match and prompts.
|
|
FETCH = re.compile(
|
|
r"node \.claude/tools/fetch_ci_report\.js "
|
|
r'"(?:https://s3\.amazonaws\.com/datastore-test-reports/'
|
|
r"|https://d1k2gkhrlfqv31\.cloudfront\.net/datastore-test-reports-private/"
|
|
r'|https://github\.com/ClickHouse/ClickHouse/(?:pull|issues)/)[^"$`]*"'
|
|
r"(?: (?:--failed|--cidb|--all|--links"
|
|
r"|--report [0-9]+"
|
|
r"|--credentials [^\s\"$`;|&<>]+))*"
|
|
r"(?: 2>&1| 2>/dev/null)?"
|
|
)
|
|
|
|
|
|
def play_ok(command: str) -> bool:
|
|
return bool(PLAY.fullmatch(command))
|
|
|
|
|
|
def fetch_ok(command: str) -> bool:
|
|
# Reject path traversal: the tmp/investigate path class permits '.', so '..'
|
|
# would otherwise let --download-logs / > escape the scratch dir. Legit fetch
|
|
# commands (URL + flags + tmp paths) never contain '..'.
|
|
if ".." in command:
|
|
return False
|
|
return bool(FETCH.fullmatch(command))
|
|
|
|
|
|
def main() -> None:
|
|
try:
|
|
data = json.load(sys.stdin)
|
|
except Exception:
|
|
return # malformed input -> no decision -> prompt
|
|
|
|
if data.get("tool_name") != "Bash":
|
|
return
|
|
|
|
command = data.get("tool_input", {}).get("command", "").strip()
|
|
if play_ok(command) or fetch_ok(command):
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "PreToolUse",
|
|
"permissionDecision": "allow",
|
|
"permissionDecisionReason": "exact read-only investigate-ci command shape",
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|