[SECURITY] Prompt injection audit — attack surfaces and mitigations #131
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Prompt Injection Audit — Grand Timmy Architecture
Auditor: Ezra
Date: 2026-03-31
Scope: All surfaces where untrusted text reaches an LLM prompt or triggers code execution
ATTACK SURFACE MAP
CRITICAL: Task Router is an Open Injection Surface
File:
uni-wizard/daemons/task_router.pySeverity: CRITICAL
The task router polls Gitea for issues assigned to Timmy and executes them. The issue title and body are parsed directly as instructions:
Attack: Anyone with write access to Gitea can create an issue assigned to Timmy with a body containing:
The current keyword matching (
"pull" in body.lower()) is naive — it doesn't distinguish between "please pull" and "do NOT pull." But the real risk is the_handle_genericpath, which is a catch-all that currently just acknowledges the issue. If this evolves to pass issue body text to the LLM as a prompt, it becomes a direct injection channel.Mitigation:
if issue['user']['login'] not in ALLOWED_AUTHORS: skipHIGH: Service Control Accepts Arbitrary Service Names
File:
uni-wizard/tools/system_tools.pyline 169Severity: HIGH
The
actionparameter is enum-validated (start/stop/restart/enable/disable), butservice_nameis not validated at all. If the LLM is tricked into callingservice_control("../../etc/shadow", "status"), systemctl will handle it gracefully, but a more creative injection intoservice_namecould be dangerous.Mitigation:
ALLOWED_SERVICES = {"llama-server", "timmy-agent", "timmy-health", "timmy-task-router", "syncthing"}/,.., or shell metacharacters.HIGH: Git Tools Pass User-Controlled Paths to subprocess
File:
uni-wizard/tools/git_tools.pySeverity: HIGH
repo_pathflows directly tocwdand git arguments. If an LLM is convinced to callgit_commit(repo_path="/etc", message="pwned"), it would attempt to run git in /etc. Thesubprocess.runwith a list (not shell=True) prevents shell injection, but path traversal is still possible.git_pushis particularly dangerous — it could push to arbitrary remotes if the repo has been configured with a malicious remote.Mitigation:
ALLOWED_REPOS = ["/root/timmy/timmy-home", "/root/timmy/timmy-config"]repo_pathis under a known safe directory.git_push, validate the remote URL before pushing.HIGH: HTTP Tools Have No SSRF Protection
File:
uni-wizard/tools/network_tools.pySeverity: HIGH
The
http_get(url)andhttp_post(url, body)tools accept arbitrary URLs with ZERO validation. The Hermes agent itself has SSRF protection (tools/url_safety.pyadded in PR #59), but the uni-wizard tools bypass this entirely — they use rawurllib.requestwith no IP checks.Attack: LLM is convinced to call
http_get("http://169.254.169.254/latest/meta-data/")to fetch cloud metadata, orhttp_get("http://127.0.0.1:8081/slots")to probe internal services.Mitigation:
url_safety.pyto the uni-wizard HTTP tools.MEDIUM: Cloud Backend Responses as Injection Vectors
Severity: MEDIUM (novel attack vector per #101 research)
When Timmy routes a task to a cloud backend (Claude, Kimi, GPT), the response comes back as text that Timmy integrates into his context. A compromised or adversarial backend could return:
Timmy's local model would see this in its context window. If the integration layer doesn't sanitize backend responses, the injected text could influence subsequent tool calls.
Mitigation:
<backend_response>...</backend_response>MEDIUM: Cron Job Prompt is Static but References Dynamic Data
Severity: MEDIUM
The morning report cron reads from Gitea API (issue titles, bodies, comments) and feeds them to the LLM as context. If a malicious issue title contains injection text like:
The cron LLM session would see this as part of its "Gitea activity" context and could potentially follow the instruction.
Mitigation:
<gitea_data>...</gitea_data>MEDIUM: Gitea Credentials in .git-credentials
Severity: MEDIUM
Allegro's box has plaintext credentials in
/root/.git-credentials:If the LLM is tricked into reading this file (
read_file("/root/.git-credentials")), the token is exposed. Same applies to/root/.gitea_token,/root/wizards/allegro/home/.env.Mitigation:
BLOCKED_PATHS = [".git-credentials", ".env", "*.token", "*.key", "*secret*"]LOW: System Prompt Suffix is Readable
The
system_prompt_suffixin config.yaml is readable by the LLM (it's in the config file, and the LLM has file read tools). An attacker who can influence tool calls could have the LLM read its own config to understand the system prompt and craft targeted injections.Mitigation: This is inherent to the architecture and low risk. The system prompt isn't a secret — Timmy's soul is public on Gitea. Accept the risk.
SUMMARY TABLE
RECOMMENDED PRIORITY
🏷️ Automated Triage Check
Timestamp: 2026-03-31T04:45:03.754693
Agent: Allegro Heartbeat
This issue has been identified as needing triage:
Checklist
Context
Automated triage from Allegro 15-minute heartbeat