feat(proxy): PreToolUse-Hook blockiert Bash-curl wenn Skill existiert
Variante A endlich umgesetzt: echter Hard-Block vor Bash-Ausfuehrung.
Anders als 14 seed_rules + Bypass-Lehre, die ARIA ignorieren kann,
ist das ein technisch erzwungener Reject auf claude-CLI-Ebene.
Komponenten:
1. aria-brain main.py: neuer Endpoint POST /skills/can-bash-host
Bekommt {command}, parst https-URLs raus, prueft gegen aktive Skills
(stem-match: 'spotify' im Hostname 'api.spotify.com'). Returnt
{block, host, skill, safe_tool} wenn ein Skill den Host abdeckt.
2. proxy-patches/pre-tool-bash-block.js: Node-Script das vom claude-CLI
als PreToolUse-Hook fuer das Bash-Tool aufgerufen wird. Liest Tool-
Use-Payload via stdin, ruft Brain-Endpoint mit kurzem Timeout (3s),
bei block=true → exit 2 mit Stderr-Message. claude-CLI gibt Stderr
als tool_use_error an das LLM zurueck — echter Fehler, nicht
ignorierbar.
Fail-open bei Brain-Down / Timeout / JSON-Fehler: kein Lockout.
3. proxy-patches/managed-settings.json: claude-CLI Hook-Config mit
PreToolUse-Matcher 'Bash' der das Node-Script ausfuehrt.
/etc/claude-code/managed-settings.json hat Vorrang vor User-Settings
und betrifft NICHT Stefans Host-~/.claude/settings.json.
4. docker-compose.yml: proxy-Command erweitert um
`mkdir -p /etc/claude-code && cp managed-settings.json dorthin`
damit beim Container-Start die Hook-Config aktiv ist.
Beobachtung die das motiviert: 14 seed_rules + Bypass-Lehre +
Auto-Scaffold + Safe-Names. ARIA hat trotzdem letzten Test mit 2
verschachtelten Bash-curls bedient statt run_spotify zu rufen
(content_len=73, tool_calls=0). Prompt-Engineering ausgereizt.
ARIA bekommt jetzt:
🚨 BASH GEGEN api.spotify.com BLOCKIERT.
Es existiert bereits ein Skill 'spotify' fuer diesen Host. ...
Konkret: nutze JETZT `run_spotify` mit den passenden Parametern
(method/path/body) statt curl.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -805,6 +805,52 @@ class SkillScaffold(BaseModel):
|
||||
author: str = "stefan"
|
||||
|
||||
|
||||
class SkillCanBashHostIn(BaseModel):
|
||||
command: str
|
||||
|
||||
|
||||
@app.post("/skills/can-bash-host")
|
||||
def skills_can_bash_host(body: SkillCanBashHostIn):
|
||||
"""Prueft ob ein Bash-Command gegen einen Host laufen will fuer den
|
||||
bereits ein matching Skill existiert. Wird vom claude-CLI PreToolUse-
|
||||
Hook im aria-proxy gefragt — wenn block=True, weist der Hook den
|
||||
Bash-Call mit Fehlermeldung zurueck und ARIA muss `run_<skill>` nehmen.
|
||||
|
||||
Antwort: {block: bool, host?: str, skill?: str, safe_tool?: str}
|
||||
"""
|
||||
import re as _re
|
||||
cmd = (body.command or "").strip()
|
||||
if not cmd:
|
||||
return {"block": False}
|
||||
skills = skills_mod.list_skills(active_only=False)
|
||||
if not skills:
|
||||
return {"block": False}
|
||||
|
||||
# Stem-Map: jeder Skill-Name als potentieller Hostname-Match
|
||||
# (yt_dlp_download → 'yt_dlp_download', 'spotify' → 'spotify' etc.)
|
||||
stem_to_skill = {}
|
||||
for s in skills:
|
||||
sname = (s.get("name") or "").lower()
|
||||
if sname:
|
||||
stem_to_skill[sname] = sname
|
||||
# Underscore-Variante auch als Stem akzeptieren
|
||||
stem_to_skill[sname.replace("_", "-")] = sname
|
||||
|
||||
# Alle https-URLs im Command einsammeln + matchen
|
||||
for url_host in _re.findall(r'https?://([a-zA-Z0-9.\-]+)', cmd):
|
||||
host_lower = url_host.lower()
|
||||
for stem, skill_name in stem_to_skill.items():
|
||||
if stem and stem in host_lower:
|
||||
safe_tool = "run_" + _re.sub(r"[^a-zA-Z0-9_]", "_", skill_name)
|
||||
return {
|
||||
"block": True,
|
||||
"host": url_host,
|
||||
"skill": skill_name,
|
||||
"safe_tool": safe_tool,
|
||||
}
|
||||
return {"block": False}
|
||||
|
||||
|
||||
@app.get("/skills/templates")
|
||||
def skills_templates_list():
|
||||
"""Liste der verfuegbaren Templates — fuer UI und Dokumentation."""
|
||||
|
||||
Reference in New Issue
Block a user