● glob (/* in .)
● read_file (/home/sam/Source/term-llm/AGENTS.md:1-200)
● read_file (/home/sam/Source/term-llm/internal/tools/permissions.go:1-200)
● shell (ls -la /home/sam/Source/term-llm)
● grep (/exec.Command|exec.Command…/ in /home/sam/Source/term-llm (/*.go))
● read_file (/home/sam/Source/term-llm/internal/tools/permissions.go:1-220)
● shell (ls)
● read_file (/home/sam/Source/term-llm/internal/tools/permissions.go:1-260)
● shell (rg -n "exec.Command|CommandContext|http.Clien…)
● read_file (/home/sam/Source/term-llm/internal/tools/approval.go:240-260)
● read_file (/home/sam/Source/term-llm/internal/tools/approval.go:240-260)
● read_file (/home/sam/Source/term-llm/internal/update/upgrade.go:1-240)
Below is a security-focused review of the term-llm repository. I focused on tool execution,
configuration resolution, update/download flows, and external request handling. Findings
include concrete risks, evidence, and actionable mitigations.
Summary
term-llm is a CLI that exposes powerful local tools (read/write/edit/shell, etc.), supports
multiple LLM providers, and can auto-update itself. The design includes explicit approvals
and allowlists, which is good, but there are several areas where trust boundaries can be
crossed or where untrusted input can reach command execution or network requests without
sufficient validation. The most significant risks are:
• Command execution from config values (shell evaluation).
• Command execution via shell tool approval model (approval bypass patterns and non‑TTY
execution).
• Unverified binary upgrade downloads (no signature/hash verification).
• SSRF-style URL fetching in some tools/providers without strict allowlisting.
High Severity Findings
1) Config values allow arbitrary shell execution
Evidence: internal/config/resolve.go
case strings.HasPrefix(value, “$(”) && strings.HasSuffix(value, “)”):
return resolveCommand(value[2 : len(value)-1])
…
func resolveCommand(cmd string) (string, error) {
output, err := exec.Command(“sh”, “-c”, cmd).Output()
Risk: Any config value can embed $(…) and will execute arbitrary shell commands when
config is loaded. This turns configuration into an execution vector. If configuration can be
influenced (user, automation, malicious file in repo or homedir, or supply chain), it
becomes a trivial local RCE.
Mitigations:
• Remove $(…) support entirely or gate it behind an explicit opt‑in flag/environment
variable.
• If kept, restrict to a safe allowlist of commands or run via exec.Command with args, not
sh -c.
• Emit warnings and require interactive confirmation for command-based resolution.
2) Self-upgrade downloads unsigned binaries
Evidence: internal/update/upgrade.go
downloadFile(ctx, downloadURL, archivePath)
…
if err := extractBinaryTo(archivePath, tempFile); err != nil { … }
Risk: The update flow downloads a tarball over HTTPS but does not verify signature or
checksum. If GitHub release artifacts are tampered with or a MITM/local proxy is present, an
attacker can replace the binary.
Mitigations:
• Publish and verify checksums or signatures (e.g., SHA256 + signature or sigstore).
• Pin TLS with checksum verification for assets.
• Consider prompting user and showing checksum for manual verification.
Medium Severity Findings
3) Shell tool relies on approvals but lacks robust non‑TTY hardening
Evidence: internal/tools/shell.go, internal/tools/config.go
cmd := exec.CommandContext(execCtx, shell, “-c”, a.Command)
The approval system relies on:
• ShellAllow patterns
• ShellAutoRun + env variables
• Interactive prompt
But shell tool can run in non‑TTY contexts if ShellNonTTYEnv is set. If that env var is set
in automation, it becomes a headless RCE surface via model output.
Risk: If any tool call can be coerced or untrusted input is routed to shell, it becomes a
powerful execution channel.
Mitigations:
• In non‑TTY mode, require explicit --shell-allow allowlist and disable ad‑hoc approvals.
• Require per-command approval even with ShellAutoRun unless a strict allowlist match is
present.
• Log and persist shell tool usage with the command for auditability.
4) SSRF potential via URL tools/providers
Evidence: internal/llm/read_url_tool.go, provider code (OpenAI/Claude/xAI/etc.)
jinaURL := “https://r.jina.ai/” + url
req, err := http.NewRequestWithContext(ctx, “GET”, jinaURL, nil)
Multiple places accept arbitrary URLs or user-provided endpoints. Some are raw HTTP client
requests with no allowlist or blocked internal IP ranges.
Risk: If LLM prompts or user inputs feed URLs, the tool can request arbitrary destinations.
This can be used for SSRF (internal network access), metadata access (cloud instance
metadata), or targeted scanning.
Mitigations:
• Validate URLs and block internal/reserved IP ranges.
• Allowlist protocols (https only) and optionally domains.
• Add optional “safe mode” to disable arbitrary URL fetches.
Low Severity / Hardening Opportunities
5) WriteFileTool temp file pattern could be abused via symlink races
Evidence: internal/tools/write.go
tempFile := absPath + “.tmp”
os.WriteFile(tempFile, …)
os.Rename(tempFile, absPath)
Risk: If an attacker controls the target directory, they can create symlinks or replace .tmp
to target an arbitrary file. This is a classic TOCTOU issue when using predictable temp
filenames.
Mitigations:
• Use os.CreateTemp in the same directory and os.Rename to avoid predictable temp names.
• Use O_EXCL style creation if possible.
6) ReadFileTool accepts arbitrary path once approved
Evidence: internal/tools/read.go, approval.go
The approval model grants directory-level access. Once a directory is approved, any file in
it can be read. This is expected but becomes sensitive if directory approvals are too broad
(e.g., home directory).
Mitigations:
• Provide clearer warnings about approving large directory scopes.
• Optionally support file-level approvals in addition to directory approvals.
Notable Good Practices
• Tool allowlists and approval flow: ToolPermissions and ApprovalManager are good guardrails.
• Path canonicalization and symlink handling: avoids common path traversal issues.
• Output truncation: prevents tool output flooding.
Recommendations (Prioritized)
- Remove or strictly gate $(…) command execution in config.
- Implement upgrade binary verification (hash/signature).
- Harden shell tool for non‑TTY usage with strict allowlists only.
- Add URL validation and SSRF protection in read_url_tool and any open URL fetches.
- Harden write tool temp-file handling with CreateTemp.
If you want, I can provide a patch plan or concrete code changes for any of the above.
(the first item is actually on purpose to support 1password, and it is a sharp tool after all, but I will certainly ponder about this)