TL;DR: The handler has command injection (model text in a shell string), SQL injection (model text concatenated into a query), path traversal (a model-chosen path outside the user's directory) and missing authorization (no check that the file belongs to the user). Model-generated tool arguments are untrusted input, because injected content can steer them. Fix with an argument array and no shell, parameterised SQL, filename validation plus path confinement to the user's directory, and identity from the session, not the model.
How to approach it
Read the handler as an entry point whose inputs come from the model, and remind the interviewer why that makes them untrusted. Then trace each argument to its sink, name the bug, and fix them one at a time. Run or reason through the fixed version against the attack inputs.
A strong answer
Command injection
Model text in a shell string
- Fix: no shell; argument list to a named program
SQL injection
Model text concatenated
- Fix: parameterised query
Path traversal
Model-chosen path
- Fix: resolve, then check inside the user's root
Missing authorisation
No owner check
- Fix: verify the file belongs to the session user
The code under review. An agent can archive a user's report:
import os
def archive_report(db, args):
# args is the JSON the model produced for the tool call
os.system(f"gzip -k /srv/reports/{args['user']}/{args['filename']}")
db.execute(
f"INSERT INTO jobs (owner, title) "
f"VALUES ('{args['user']}', '{args['title']}')"
)
Findings.
| # | Bug | How it is reached | Impact |
|---|---|---|---|
| 1 | Command injection | filename contains shell syntax such as ; or $( ) | Arbitrary commands as the service user |
| 2 | SQL injection | title contains a quote and SQL | Read or modify the jobs table and more |
| 3 | Path traversal | filename like ../other-user/file | Compress or touch other users' files |
| 4 | Broken authorization | user comes from the model | The agent acts on any user's directory |
Each is reachable without the user being malicious: a document or email the agent read earlier can steer the model into producing these arguments.
Fixed handler.
import re
import sqlite3
import subprocess
from pathlib import Path
NAME = re.compile(r"[A-Za-z0-9._-]{1,100}")
def archive_report(
db: sqlite3.Connection, base: Path, user_id: str, args: dict
) -> None:
"""Tool handler: args come from the model, so treat them as untrusted."""
name = str(args.get("filename", ""))
if not NAME.fullmatch(name) or name.startswith("."):
raise ValueError("invalid filename")
user_dir = (base / user_id).resolve()
path = (user_dir / name).resolve()
if not path.is_relative_to(user_dir) or not path.is_file():
raise ValueError("file not found")
# No shell: the path is one argument; "--" ends option parsing.
subprocess.run(["gzip", "-k", "--", str(path)], check=True, timeout=30)
title = str(args.get("title", ""))[:200]
db.execute(
"INSERT INTO jobs (owner, title, file) VALUES (?, ?, ?)",
(user_id, title, name),
)
What changed: user_id comes from the authenticated session and is passed in by the application, never read from the model; the filename is allowlisted and the resolved path must stay inside the user's directory; the command runs without a shell with -- before the path; SQL uses placeholders; the title is length-capped. Tested against ../bob/secret.txt, q3.txt; rm -rf /, -rf, .bashrc and /etc/passwd: all are rejected, and a title containing SQL is stored as plain text.
Defence in depth. Run the tool in a container or sandbox with access only to the reports volume, a database role that can only insert into jobs, and a per-session rate limit on tool calls. Prefer a library (Python's gzip module) over a subprocess where possible.
What interviewers probe next
- "Why not ask the model to only produce safe filenames?" Instructions lower the rate; they cannot be the control, because injected text can override them.
- "How would you catch this class of bug across many tools?" A shared tool-argument schema layer with strict types and validators, plus a review checklist for every handler.
- "What if the user really is malicious?" Same fixes; the handler must be safe against its own user, too.
What if?
What if · interview follow-ups
Hold your answer as the constraints change
A feature lets an LLM generate commands and file paths that run on a build server, with results looked up in a database by the model-chosen name.
Start here
What vulnerabilities would you expect?
Which interpreters does model text reach?
Command injection if model text goes into a shell string, SQL injection if it is concatenated into a query, path traversal if a model-chosen path escapes the user's directory, and missing authorisation if nothing checks the file belongs to the user.
Carry it forwardModel text reaching interpreters brings classic injection.
Weak answerAssuming the model will produce safe strings.
Constraint changes
Why is model output untrusted?
What influenced the model's output?
Injected content in repositories, tickets or documents can steer what the model generates. Even without an attacker, models make mistakes. Treat generated arguments like user input.
Carry it forwardModel-generated arguments are untrusted input.
Weak answerTrusting output because your own prompt produced it.
Constraint changes
How do you fix command execution?
Does the model need arbitrary commands?
Use an argument array with no shell, and better, map model intent to a fixed set of allowed commands with validated parameters. Avoid free-form command generation entirely where possible.
Carry it forwardFixed commands with validated arguments, no shell.
Weak answerSanitising shell strings.
Constraint changes
How do you fix file paths and queries?
Where could the path end up?
Validate filenames, resolve the final path and confirm it stays inside the user's directory, and use parameterised SQL. Check that the requesting user may access the file.
Carry it forwardPath confinement, parameterised SQL and authorisation.
Weak answerRejecting '..' strings only.
Constraint changes
Where should the commands run?
What does the build server hold?
In a sandbox per job with no production credentials, limited network and resource limits, not on a shared build server holding release secrets. Then a mistake is contained.
Carry it forwardRun generated actions in a sandbox.
Weak answerExecuting on infrastructure with release credentials.
Now defend it
How would you fix the handler?
Map model intent to allowed commands run as argument arrays without a shell; validate filenames and confine resolved paths to the user's directory; parameterise SQL; authorise file access per user; and run execution in per-job sandboxes without credentials, with logs of each generated action.
How it's scored
Weak: Finds the shell bug only, or "fixes" it by escaping strings or prompting the model.
Hire: Finds command and SQL injection and fixes both structurally.
Strong Hire: Also finds path traversal and model-supplied identity, fixes all four, explains why model output is untrusted, and adds sandboxing and least privilege.
Common mistakes
- Trusting
useror any identity field from the model. - Using
shlex.quotewithshell=Trueinstead of removing the shell. - Checking for
..in the string instead of confining the resolved path.