# Agent tool temporary files: find and clean secret traces

Agent tool temporary files deserve the same scrutiny as a source repository. A coding agent may create a request body, run a command, retry it with verbose logging, copy output into a cache, and leave the original working directory looking clean. The sensitive material is still on the machine, often in locations nobody included in the review.

The bad assumption is that a secret only leaks when an agent prints it in a chat transcript. In practice, the more ordinary leak is a credential-bearing request copied to a temp file so a helper can send it, or a failed command recorded in a log because somebody enabled debug mode last week. Cleanup matters, but prevention matters more: do not give an agent raw credentials if another process can perform the action.

## Agent tools create copies outside the working tree

An agent run can leave data in several layers even when its project directory contains no obvious trace. The working tree is only one write location, and developers often inspect it because it is familiar. The agent, its runtime, shell, package manager, editor, terminal, and operating system each have their own places to write.

Start by separating three kinds of material. Payload files contain what the agent intended to send, such as JSON request bodies, SQL batches, prompt exports, patch files, or SSH configuration. Output files contain what the remote system returned, such as API responses, command output, database exports, and error pages. Diagnostic material contains the surrounding evidence: command arguments, environment details, stack traces, retries, and traces.

All three can carry sensitive data. A payload may include a token because a script injected it before sending. Output may contain a customer record or a deployment secret. Diagnostics may capture both in the same line, which is why a debug log often causes more damage than the failed command itself.

A useful inventory includes these places:

- The process temporary directory and `/private/tmp` on macOS.
- Application support, cache, and log directories under `~/Library`.
- Shell history, terminal scrollback exports, and command wrappers.
- Project-local folders such as `.cache`, `tmp`, `logs`, `.agent`, and test fixture directories.
- Container writable layers, bind mounts, CI workspaces, and uploaded build artifacts.

Do not assume an agent uses one predictable directory. Different versions, plugins, language runtimes, and error paths make different choices. A tool may use the directory named by `TMPDIR`; a helper invoked by that tool may use `/tmp`; a library may put a cache under the current project. The only defensible answer comes from observing your own run.

The same applies to command output. A shell redirection like `command > result.txt` is obvious. Less obvious are terminal multiplexer logs, package-manager debug archives, HTTP client trace files, and editor recovery files written after the agent modifies a document. If an agent can call several tools, assume each tool has an independent retention habit until you test it.

## Map actual write locations before you try to clean them

You cannot clean a path you have guessed wrong. Run an agent against a harmless, unique marker and then search for that marker across the places the run could write. Use fake data that looks enough like the real thing to travel through the same code paths, but never use a production token for this exercise.

On macOS, begin by recording the temp directory inherited by the shell that launches the agent:

```sh
echo "TMPDIR=$TMPDIR"
ls -ld "$TMPDIR" /private/tmp /tmp
```

macOS commonly gives each user a directory below `/var/folders`, and `/tmp` points to `/private/tmp`. The exact generated path is not a security boundary and can change. Record the value rather than hard-coding a path in a cleanup script.

Create a marker that would be easy to find and then make the agent execute a representative action that passes it through a request body, a command argument, and command output. This example deliberately uses a non-secret string:

```sh
export AGENT_TRACE_MARKER='TEMP-PAYLOAD-CANARY-9f2a7c'
printf '%s\n' "$AGENT_TRACE_MARKER" > /tmp/agent-canary.txt
```

After the run, search likely per-user locations. `grep` can encounter binary files and permission errors, so use it as a discovery tool, not as proof that nothing exists.

```sh
grep -RIl --exclude='*.sqlite*' \
  'TEMP-PAYLOAD-CANARY-9f2a7c' \
  "$TMPDIR" /private/tmp \
  "$HOME/Library/Caches" \
  "$HOME/Library/Logs" \
  "$HOME/Library/Application Support" 2>/dev/null
```

The output shape should be a list of file paths. For each path, answer four questions: which process wrote it, what content category it contains, who can read it, and when it disappears. If you cannot connect a file to a process, inspect its modification time and run the test again with a fresh marker. Do not delete unexplained files first. You may erase the clue that tells you which component needs reconfiguration.

Use `fs_usage` when a file appears only briefly. It can show filesystem activity from a process while the agent runs:

```sh
sudo fs_usage -w -f filesystem | grep -iE 'agent|tmp|cache|log'
```

This command is noisy. Run it for a short test, save the observed paths outside a shared project directory, and stop it afterward. A process that writes and deletes a file within a second will not appear in a later directory listing, yet its content may still have reached a backup, a watcher, or another log collector.

## Request construction is where credentials usually spill

The most dangerous temporary file is often created before the network call. Many scripts build a request in a file because quoting JSON in a shell is unpleasant. The file begins harmlessly, then somebody adds an `Authorization` field, a cookie, or a full connection string to make the request work. It becomes a durable secret container by accident.

Avoid this pattern:

```sh
cat > /tmp/request.json <<'EOF'
{"endpoint":"https://api.example.invalid/export","token":"$PRODUCTION_TOKEN"}
EOF
```

The single-quoted heredoc above does not expand the variable, which may appear safe. A later edit often changes the delimiter or uses a different construction method. More importantly, the design still teaches people to put a credential in a request artifact. A debug dump of the completed request will expose it.

Put credentials in the transport layer when the protocol permits it, and ensure the transport does not log headers. HTTP authorization headers are preferable to a token in a query string, but they are not magically safe. Verbose clients, proxy settings, exception handlers, and custom retry code can still record them.

The HTTP Semantics specification, RFC 9110, says user agents should not send a URI containing sensitive information in a Referer header. That warning reflects a wider fact: URLs travel farther than people expect. They end up in access logs, browser history, copied terminal commands, support tickets, and analytics systems. Do not put bearer tokens, signed URLs with broad authority, passwords, or database connection strings in a URL unless the protocol leaves no alternative and the credential has a very short lifetime.

Shell arguments need the same care. On Unix-like systems, another local process may be able to observe arguments depending on permissions and platform settings. Arguments also land in shell history if a human copies them, in a task runner's log, and in an agent's tool transcript. Environment variables reduce some of those paths but create others, including inherited child processes and diagnostic reports. Neither is a safe vault.

The better boundary is simple: the agent requests an operation by naming the destination and the non-sensitive inputs. A separate credential holder adds authentication immediately before the call. The agent receives the response or a redacted error, not the credential used to obtain it.

Sallyport follows that boundary for HTTP and SSH actions: the credential remains in its encrypted vault and the agent asks the local app to perform the action. That removes the raw secret from agent context, but it does not make response bodies or agent-created debug files harmless. You still need to control what the action returns and where the agent writes it.

## Debug mode turns routine failures into records of secrets

Debug logging is useful while diagnosing a broken integration. It is also engineered to preserve the evidence that ordinary logging omits. That commonly includes headers, complete request and response bodies, command lines, environment-derived settings, retry state, and stack traces.

The mistake is leaving a broad debug flag enabled because the problem occurred once. The flag then applies to an unrelated job weeks later, when somebody runs an export or a deployment with real authority. The resulting file may sit under a cache directory that nobody knows belongs to the tool.

Treat diagnostics as a different data class with a short, explicit lifetime. Before you enable a trace, decide what question it must answer. If the question is whether DNS resolves, capture resolver output. If it is whether the server rejects a JSON field, log the status code and a redacted response excerpt. Full wire logging should be the exception because it records material you did not need to solve the problem.

Build redaction at the writer, not after the fact. A cleanup job that searches logs for `Authorization:` will miss custom headers, JSON fields, URL parameters, multiline values, base64 blobs, and response content containing secrets. Once a log collector or backup service copies the unredacted file, a later scrub of the original does little.

A safe diagnostic wrapper has an allowlist of fields it may write. For example, record the HTTP method, host, path without query parameters, status code, duration, response byte count, and a request identifier. Do not record every header and claim you will mask the bad ones. Credential formats change faster than cleanup scripts do.

This distinction matters with tools that show a command before running it. Displaying a command is not the same as logging the command's effective environment, and neither is the same as a packet trace. Ask which representation the tool saves. A tool can redact its console display yet leave a verbose log untouched.

Check failure paths deliberately. Cancel a request halfway through. Send malformed JSON. Trigger an authentication failure with a synthetic credential. Cause a timeout to exercise retries. These paths create temporary request files and exceptions that successful calls may not create. They are also the paths an attacker may provoke to make a system disclose more than normal operation does.

## Deletion on macOS needs containment, not magical shredding

On modern macOS storage, secure deletion does not mean repeatedly overwriting a filename. SSD wear leveling, copy-on-write behavior, snapshots, cloud synchronization, and backups mean software cannot reliably promise that an overwrite touched every physical remnant. The old habit of running a shred utility makes people feel finished without addressing the copies that matter.

Use deletion to remove live, accessible files. Use containment to limit where sensitive material can exist in the first place. Use encryption and credential rotation when you suspect exposure.

For an agent-specific temporary directory, create it with restrictive permissions before the run and remove it after the run:

```sh
run_dir="$(mktemp -d "${TMPDIR%/}/agent-run.XXXXXX")" || exit 1
chmod 700 "$run_dir"
trap 'rm -rf "$run_dir"' EXIT HUP INT TERM
export TMPDIR="$run_dir"

# launch the agent from this same shell
# agent-command
```

This gives the run a known scratch area and ensures ordinary exit paths remove it. It does not force every dependency to obey `TMPDIR`, and it does not remove content copied to another directory. That is why the discovery exercise comes first.

Avoid `rm -rf /tmp/*` and similar broad cleanup commands. They can break other processes, delete material you need for an investigation, and create a false belief that `/tmp` was the only place to inspect. Delete only directories that your launcher created and names that your cleanup code can prove it owns.

If a secret may have escaped, rotate or revoke it before beginning a long cleanup campaign. A live API token copied into an unknown log is a current access path. Its filename is less urgent than the authority it grants. Then find downstream copies in backup tooling, shared drives, CI artifacts, log aggregation, and endpoint search systems. Deleting the original without addressing copies leaves the exposure intact.

Encrypted local storage reduces risk from a lost device, but it does not protect against another process running under the same unlocked user account. File permissions still matter. A directory mode of `700` says other local accounts should not browse it. It does not stop an agent process that already runs as you, and it does not stop a sync client you authorized to read your home directory.

## A credential boundary removes the most toxic payloads

Cleanup has a ceiling. If the agent receives a production token in a prompt, environment variable, config file, or command output, it can place that token in any file it can write. You can reduce the aftermath, but you cannot make that architecture safe through careful housekeeping.

Keep secret ownership outside the agent process. The agent should express intent such as "send this deployment request to this approved endpoint" or "run this SSH command using this named connection." A local credential holder should decide whether to authorize the action, inject the credential, execute it, and record the result. The agent never needs a placeholder token if it does not need the token itself.

This also makes temporary-file review less vague. You can inspect agent scratch directories for user inputs, generated code, and returned data. You do not have to assume each file might contain every production secret available to the agent.

A fixed approval model has an operational advantage over a pile of custom rules. People can explain it during an incident. Sallyport keeps its vault locked until local authentication opens it, asks for authorization when a new agent process first acts, and can require approval for each use of selected credentials. That is a clear control on authority, rather than an attempt to guess whether a particular generated command looks suspicious.

Do not confuse action authorization with data minimization. An approved call can return a sensitive response body, and the agent may write that response into a project file, a cache, or a transcript. Design responses for the smallest useful result. If a task only needs a deployment identifier and status, do not return an entire configuration document. If an API supports server-side filtering, use it.

SSH deserves special attention because remote command output is unconstrained. A command that reads a configuration file, prints environment variables after an error, or runs a verbose deployment utility can send secrets back to the agent. Treat SSH output as data that requires a destination, retention period, and review rule. The credential may be protected while the returned output is still dangerous.

## Give every run an owner, a directory, and an expiry

A cleanup policy succeeds when it attaches files to a specific run. A generic nightly script cannot tell whether a temporary directory belongs to an active process, a failed job worth investigating, or an unrelated application. An agent launcher can.

Use a run identifier in the scratch directory name, record the start time, and keep a small manifest outside the scratch directory with only non-sensitive metadata. The manifest should say which directory the launcher created, which process owns it, when it should expire, and whether the run completed. Do not put command arguments, request bodies, or environment values in that manifest.

A practical lifecycle has four actions:

1. Create a private scratch directory before the agent starts.
2. Set temporary-path environment variables for the agent and helpers you control.
3. Remove that directory on normal exit and mark the manifest complete.
4. Have a scheduled job flag abandoned directories that outlive a defined threshold for human review.

The scheduled job should flag, not automatically erase, directories from incomplete runs. A process may still be writing. A failed deployment may require evidence. Once a human has confirmed that the directory is abandoned and not needed for an incident, remove it using the same ownership rule.

Do not put the scratch directory inside a repository. Project search, version control status commands, IDE indexing, file watchers, and backup clients all pay attention to repositories. A sibling directory under a user-controlled temporary location is usually easier to exclude from development tooling and easier to destroy as one unit.

Be careful with automatic cleanup launched by the agent itself. An agent that can choose arbitrary cleanup paths can delete source files or evidence. The launcher should build the path, retain it in its own state, and only delete paths that match its generated naming pattern after canonicalizing them. Shell interpolation in cleanup code causes enough accidents without adding autonomous text generation to it.

The aim is not to retain nothing. You need enough operational evidence to answer which run made a request and whether it succeeded. Keep that record separate from raw payloads and full output, and make its fields deliberately boring.

## Logs and backups need a retention decision

A temporary file becomes a retained record the moment another system copies it. Backup software, cloud sync folders, endpoint protection tools, crash reporting, CI artifact upload, and centralized logging can all extend its life. The original path may disappear while the useful copy remains elsewhere.

List every process that can read the directories used by agent runs. On a development machine, this often includes a backup client, an editor indexer, a source control interface, a terminal recorder, and a malware scanner. Some copies are desirable. The point is to choose them and set their retention, rather than discovering them after a token appears in a restore archive.

Keep raw diagnostic output out of directories that sync automatically. This includes desktop folders, shared project folders, and any workspace that a CI client packages as an artifact. If a tool must produce a large response for review, store it in a private directory with restricted permissions, name an expiry, and exclude it from routine synchronization where your tooling supports exclusions.

A hash-chained audit record is different from a debug dump. An audit record should answer who requested an action, when it ran, and what result category occurred without duplicating secrets. Sallyport projects its Sessions and Activity journals from an encrypted audit log, and `sp audit verify` can verify the hash chain offline without a vault key. That makes it possible to retain evidence of agent activity without treating every raw payload as an audit requirement.

Retention must cover exceptions. During an incident, do not let the normal cleanup timer destroy the evidence needed to understand what happened. Restrict access, preserve the relevant files deliberately, and document their location. Then rotate affected credentials and remove the preserved material when the incident process no longer needs it. Incident retention is not an excuse to dump all ordinary agent output into permanent storage.

Also examine backups after changing the policy. A newly excluded scratch directory only affects future backup runs. Existing snapshots may still contain old material until their provider's retention schedule expires. Record that fact in the exposure response rather than pretending an `rm` command rewrote history.

## Test cleanup like a curious local attacker

A cleanup policy that has never faced a search test is a promise, not a control. Test it using a fake canary that resembles the strings you fear losing, then inspect the machine as though another local process wanted to find it.

Run the test under normal conditions first. Put the canary into an input file, a request field, and simulated command output. Run the agent to completion, wait for its cleanup hook, and search the scratch location, caches, logs, project directory, shell history, and likely support directories. Record every hit and explain it.

Then run the awkward cases. Force a process crash. Cancel it during a network request. Turn on the tool's most verbose supported logging. Run it from an IDE rather than a terminal. Use a container with a host bind mount. Each variation can route output to a different place.

Search by the marker rather than by filename. A copied payload might acquire a random filename, enter a SQLite database, or be compressed into an archive. If a binary or database contains the canary, identify the writer and decide whether it needs configuration, exclusion, redaction, or a different execution boundary.

Keep a small test record with the agent version, operating system version, enabled plugins, commands exercised, paths found, and the cleanup result. Update it when you add a tool that can execute commands or send requests. This is mundane work, but it catches regressions that a security review of source code will miss.

Do the first test with a harmless marker today. If it appears in a place you did not expect, fix the writer before you build a more elaborate deletion script. The cleanest temporary file is the one that never received a credential or sensitive response in the first place.
