Path traversal in the trajectory inspector reads off-path trajectory/JSON files (unauthenticated, all-interfaces bind, wildcard CORS)
Describe the bug
reported via email on 12 June 2026 - no response, trying here:
Affected Versions: confirmed on v1.1.0 (commit c53556f)
Summary
SWE-agent's trajectory inspector (sweagent inspector) is an HTTP server that serves trajectory files from a directory. Its /trajectory/ handler joins the request path to the trajectory directory with no parent-directory rejection, so a request with .. segments escapes the directory and reads files elsewhere on the host. The server binds all interfaces with wildcard CORS and no authentication. The read sink parses the file as a trajectory JSON, so the disclosure is limited to JSON files shaped like a trajectory, but any off-path SWE-agent trajectory (which can contain repository secrets, API keys, and command output) is readable across directories by an unauthenticated client. Confirmed against the inspector: an off-path trajectory-shaped JSON was returned over HTTP with no auth.
Details
sweagent/inspector/server.py, do_GET (~lines 259 to 261) handles /trajectory/ before delegating to SimpleHTTPRequestHandler (so its built-in .. sanitization is bypassed):
file_path = self.path[len("/trajectory/"):]
# serve_file_content (~lines 240-246):
Path(self.traj_dir) / file_path # no `..` rejection
# load_content (~lines 168-170): open(file_name) + json.load(...)Path(traj_dir) / "../../etc/x" resolves outside traj_dir. The server binds ("", port) = 0.0.0.0 (main ~line 322), adds Access-Control-Allow-Origin: * (~lines 290 to 292), and has no authentication. Because load_content does json.load and the post-processing expects trajectory keys (history, trajectory, info), the readable targets are JSON files with that shape; non-trajectory files (for example /etc/passwd) raise and return a 500.
Steps/commands/code to Reproduce
PoC
# raw HTTP with literal `..` (curl --path-as-is, so the client does not collapse the path)
curl --path-as-is 'http://TARGET:8231/trajectory/../../../../../../home/user/other_run/run.traj'Validated against the sweagent inspector (booted on 0.0.0.0:8231): a planted off-path /tmp/victim_secret.json (trajectory-shaped) was returned with HTTP 200 and Access-Control-Allow-Origin: *, its secret value appearing in the body, with no auth header. ss confirmed the 0.0.0.0:8231 bind. The attack requires the client to send literal .. segments (trivial with raw HTTP / --path-as-is; a browser/curl without it collapses the path and 404s).
Impact
An unauthenticated client on the network (or a malicious web page, via the wildcard CORS) reads trajectory/JSON files outside the served directory. SWE-agent trajectories record the full agent run, including repository contents, command output, and frequently secrets/API keys from the environment or the solved repository, so cross-directory trajectory disclosure leaks those. The disclosure is constrained to trajectory-shaped JSON (it is not an arbitrary-file read of any path).
Remediation
Resolve the requested path and assert it stays within traj_dir (Path(traj_dir).joinpath(file_path).resolve() must be relative to Path(traj_dir).resolve(); reject .. and absolute paths) before opening it. Bind the inspector to loopback by default, drop the wildcard CORS (or restrict it to localhost), and consider requiring a token.
Error message/results
System Information
Checklist
- I'm running with the latest docker container/on the latest development version (i.e., I ran
git pull)) - I have copied the full command/code that I ran (as text, not as screenshot!)
- If applicable: I have copied the full log file/error message that was the result (as text, not as screenshot!)
- I have enclosed code/log messages in triple backticks (docs) and clicked "Preview" to make sure it's displayed correctly.
Source: SWE-agent/SWE-agent