5 min read

Crash report sensitive data: stop agent context leaks

Crash report sensitive data can expose agent prompts, request bodies, and tokens. Learn how to minimize collection, test exports, and retain diagnostics safely.

Crash report sensitive data: stop agent context leaks

Crash reporting can quietly become the widest data-export path in an agent-enabled application. Teams lock down production credentials, then let an exception SDK collect breadcrumbs, HTTP context, process details, and copied tool input before shipping the bundle to a vendor account. The crash was local. The evidence did not stay local.

The fix is not to abandon crash reports. You need them when an agent integration fails in a way that ordinary logs cannot explain. The job is to decide which debugging facts leave the machine, stop collecting raw request material by default, and prove that the collector obeys those limits. I have seen incident reviews derail because the stack trace was harmless but a "useful" context field contained the entire failing request.

Crash reports are evidence packages, not just stack traces

A crash report is often a bundle assembled across several layers. The operating system may create a native diagnostic report. Your application may attach exception metadata. A crash SDK may add device data, breadcrumbs, session history, logs, tracing fields, and user-defined tags. An agent framework may have already serialized its prompt, tool input, response, or retry state into one of those fields.

This distinction matters because developers tend to audit the visible stack trace and call the result clean. The sensitive material usually sits beside it.

A report can expose data through:

  • exception messages that interpolate a URL, command, or response body
  • breadcrumbs made from request logs or tool-call events
  • custom fields that receive a whole request object
  • command-line arguments, temporary-file paths, and environment-derived values
  • attached files, screenshots, session replay, or support feedback

A memory address in a native report does not normally reveal an API key by itself. A string copied into a panic message absolutely can. Do not blur those two cases. Native diagnostics need access controls; application telemetry needs deliberate data minimization.

Apple's documentation for diagnostic reports describes them as records used to diagnose application problems, with process and thread information. That is useful and usually narrower than an application monitoring event. Once an SDK enriches that report with your own runtime context, however, the data boundary changes. Treat the enriched event as application data headed to another system.

Agent failures create unusually rich diagnostic context

An ordinary request handler might fail with a route and a status code. An agent action can carry a user instruction, repository paths, command output, arguments for a remote API, an SSH destination, and the previous tool results that led to the failure. That context helps reproduce the bug. It also makes careless collection expensive.

The risky path is predictable. A developer wraps every tool invocation in a convenience logger. The logger emits the entire argument object. The observability SDK converts log records into breadcrumbs. A network timeout triggers an exception. The report now contains the tool input, including headers or a pasted token, and the vendor receives it.

The same failure appears when code does this:

try {
  await runTool(toolName, input);
} catch (error) {
  throw new Error(`Tool failed: ${toolName} input=${JSON.stringify(input)} error=${error.message}`);
}

That message looks helpful during a local test. In production, it creates an unbounded data field that every error collector, log sink, and alert integration may copy. Replace it with stable identifiers and an allowlisted summary:

try {
  await runTool(toolName, input);
} catch (error) {
  throw new Error(`Tool failed: name=${toolName} request_id=${requestId} input_shape=${inputShape}`);
}

inputShape can mean a list of approved field names and sizes, never field values. If you cannot explain why a field belongs in an error event, leave it out. Reproduction should begin with a correlation ID that points to a protected local record, not a full request pasted into a hosted dashboard.

Raw request capture is a bad default

Raw request capture remains popular because it makes the first debugging session fast. It is wrong as a default for services that handle agent actions. Authorization headers, cookies, signed URLs, query parameters, request bodies, and custom headers all routinely contain material that has no business in a crash system.

A safe event has enough facts to group, triage, and route the incident:

{
  "request_id": "rq_8c2f1a",
  "channel": "http",
  "method": "POST",
  "route": "/v1/issues/{issue_id}/comments",
  "status_class": "5xx",
  "duration_ms": 8120,
  "attempt": 2,
  "error_kind": "upstream_timeout"
}

The route uses a template, not the literal path. The event says the request retried, not what it sent. The opaque ID lets an authorized responder inspect the local source record if they need it.

Do not hash secrets and call them redacted. A deterministic hash can still identify a repeated bearer token and can be vulnerable when the original has a small search space. Replace sensitive values with a constant marker or omit them. If you need to know whether a credential was present, record auth_present: true, not its type, length, prefix, or fingerprint.

Also review URL handling. Many libraries record full URLs automatically. A route such as /callback?code=... or a signed download URL can leak through a field the developer never manually added. Remove query strings before the event enters the SDK, rather than hoping a later processor recognizes every variation.

Redaction must run before storage and before export

A scrubber is a backstop, not a permission slip to collect everything. SDK hooks differ: some process the final event, some only process selected fields, and some do not cover native crash attachments or breadcrumbs produced by a separate integration. A rule that redacts Authorization may miss authorization, x-api-token, a URL parameter, or a JSON string embedded in an exception.

Build the boundary in layers. First, turn off automatic capture you do not need. Then construct events from allowlisted fields. Then run a defensive scrubber over all remaining strings. Finally, test the serialized data received by the collector.

This pseudocode shows the order that prevents the most trouble:

request arrives
  -> derive route template and request ID
  -> retain protected local diagnostic record if policy permits
  -> create minimal crash context from allowlisted fields
  -> scrub all residual strings
  -> send minimized event

Do not pass the original request object to a "before send" callback. Once a library has inspected that object, plugins may have already turned it into breadcrumbs or spans. Give the telemetry code a small object that cannot contain sensitive fields in the first place.

Sentry documents data scrubbing and event processors, while Firebase Crashlytics documents custom keys and log collection. Read those settings as collection controls, not as a generic privacy guarantee. In both cases, custom context and logs are the places where application teams most often defeat their own default protections.

SDK defaults change when integrations change

Stop exporting agent credentials
Sallyport executes HTTP calls itself and returns results without handing credential material to your agent.

A crash SDK upgrade can add tracing, console capture, breadcrumbs, session replay, performance instrumentation, or a framework integration that sees more request state. A security review performed when the app had only stack traces does not cover the new data path.

Maintain a small collection inventory for each runtime. Record the native crash collector, exception SDK, logging bridge, tracing package, feedback widget, and support bundle mechanism. For each one, answer four mundane questions: what triggers collection, which fields it collects automatically, where it stores data before upload, and who can read it after upload.

Do not forget side channels. Developers often disable request bodies in the crash product but forward the same exception to a log aggregator. Alerting rules may copy the exception text into chat notifications. A support button may attach a local log archive. You need one data map for the incident path, not a separate optimistic story for each vendor.

On macOS, inspect both app-level reporting and operating-system diagnostics. Apple crash reports can remain local or be shared through system reporting choices, while third-party SDKs follow their own configuration and network path. A desktop app should make this distinction clear to the people operating it. "Crash reports are disabled" is meaningless if a separate error-monitoring client still uploads enriched events.

Put credentials outside the agent process

Redaction reduces exposure after code has handled a secret. The stronger move is to prevent the agent process from holding the secret at all. Then an agent crash, a copied environment, and an accidental debug dump have less sensitive material available to capture.

This does not mean a crash report becomes harmless. The agent may still hold private source, prompts, or tool arguments. It does mean that a bearer token cannot leak from a process that never received it.

Sallyport follows this boundary for HTTP APIs and SSH actions: the app keeps credentials in its encrypted vault, executes the action, and returns the result to the agent rather than giving the agent credential material. That removes one recurring crash-telemetry failure mode, but you still need to minimize the request data and result text your agent handles.

Avoid putting secrets in command arguments as well. Process arguments appear in diagnostic tools more often than developers expect, and shell history or process inspection can expose them independently of crash reporting. Pass secret material through a protected broker or a carefully managed local mechanism, not through a visible argument such as --token=....

Local retention needs a retention rule too

Lock the action boundary
A locked vault denies every action, so an agent cannot use stored credentials while Sallyport is locked.

Keeping full reports on the machine can be appropriate for difficult failures, especially during controlled development. It is not automatically safe. A local spool can outlive the incident, get copied into a support archive, or sit in a shared user profile where another process can read it.

Separate two records. Send a minimized remote event for grouping and alerting. Keep any fuller reproduction record locally in a protected location with a short retention period and an explicit deletion path. The remote event needs the local record's opaque ID, not its contents.

A useful local record includes the exact build identifier, a sanitized configuration fingerprint, timing, and a reference to the action attempt. It should not be a loose text file that concatenates environment variables, request bodies, and terminal output. Structured local storage lets you apply the same allowlist and deletion rules that you use for exports.

For agent action gateways, audit records deserve special care. They help establish what the agent tried to do, but they are not a license to mirror raw credentials or entire prompts into every diagnostic subsystem. Sallyport's audit trail separates action records from the agent itself, and its sp audit verify command verifies the encrypted hash chain offline without a vault key. Verification tells you whether the trail changed; data minimization still decides what diagnostic context belongs elsewhere.

Prove the collector with planted secrets

Verify the action trail
Verify Sallyport’s encrypted, hash-chained audit trail offline with sp audit verify, without a vault key.

Configuration review catches obvious mistakes. A planted-data test catches the mistakes that matter. Run it before launch, after telemetry SDK upgrades, and whenever an agent framework gains a new tool or logging integration.

Use unique marker strings that cannot be mistaken for real credentials. Put different markers in the places teams routinely forget: an authorization header, a query parameter, a JSON body field, an agent instruction, an environment variable, a command argument, and a tool result. Trigger a controlled exception, then search every destination.

Check the vendor event view, raw event export if available, local crash spool, application logs, tracing events, alert payloads, support attachments, and any queue between the app and the collector. Search for the exact markers and common transformations such as URL encoding or escaped JSON. Inspecting only the web dashboard is how teams miss a leak in a breadcrumb or attached log.

Write down the expected result. For example, a report may contain route=/v1/files/{file_id} and request_id=rq_test_01, but it must not contain MARKER_HEADER_7, MARKER_PROMPT_7, or the literal query string. Treat a failed test like a security defect: disable the offending capture path, add a regression test, and retest the serialized event.

A small review catches most accidental exports

Review crash telemetry whenever somebody changes error handling, observability, an agent tool, or support diagnostics. The reviewer should ask whether the new code can serialize a request-shaped object, not merely whether it added a new secret field.

Use this short checklist during review:

  • Error messages contain identifiers and categories, not serialized inputs or outputs.
  • Telemetry receives allowlisted context, never a raw request or agent state object.
  • URLs lose query strings and headers never enter breadcrumbs or custom fields.
  • Full local diagnostics have protected storage, deletion rules, and a named reason to exist.
  • A planted-marker test covers every configured export destination.

The uncomfortable part is that good debugging habits often cause the leak. Developers add context because the last incident lacked it. Keep the context that classifies the failure, preserve fuller evidence under local control when justified, and stop exporting raw material just because a crash SDK offers a field for it.

FAQ

Can crash reports contain API keys or agent prompts?

Yes. A crash report can include process arguments, environment-derived paths, loaded modules, thread stacks, breadcrumbs, logs, and application state captured shortly before failure. Whether it contains a secret depends on what your program put into memory, logs, URLs, exception messages, or metadata.

Is it safe to send production crash reports to a third-party service?

Crash collection is safe enough only after you treat it as an external data destination and test the actual event payload. Vendor assurances do not erase a token embedded in a URL, a request body recorded as a breadcrumb, or a copied prompt in an exception message.

Should I include HTTP headers in crash reports?

Most teams should keep request headers out of crash telemetry. If an error report needs request context, send an allowlisted method, route template, status code, and a correlation ID rather than the raw header map.

Can I rely on a crash reporting scrubber to remove secrets?

A redaction function helps, but it cannot clean data that the SDK captured before your function runs or data exposed through another integration. Disable risky capture first, then use redaction as a backstop and continuously test it.

What data do crash reporters collect by default?

Mobile and desktop crash systems often collect device facts, app version, stack frames, and diagnostic metadata by default. Logs, custom keys, screenshots, session replay, breadcrumbs, and user feedback usually need separate review because each can carry much more context.

What request information is useful without leaking the request itself?

Capture the route template, HTTP method, response class, elapsed time, retry count, and an opaque request ID. Do not capture raw query strings, bodies, authorization headers, cookies, or complete agent tool arguments unless a narrowly reviewed local workflow requires them.

How do I test whether crash telemetry leaks secrets?

Use a synthetic crash or controlled exception in a non-production project, then inspect the event in the vendor console, exported JSON, local spool, and any connected log system. Search for planted marker strings in headers, query parameters, prompt text, and environment variables.

Can agent tool calls appear in error monitoring?

A crash system can capture arguments indirectly if the client library records tool input as a breadcrumb, log entry, span attribute, custom context field, or exception text. Treat agent prompts and tool payloads as sensitive application data, even when they are not credentials.

Do environment variables leak through crash reports?

Disable automatic environment capture where the SDK allows it, and never place secrets in environment variable names or command-line arguments. More importantly, move credentials out of the agent process so an accidental diagnostic bundle has less to expose.

Should crash reports stay on the local machine?

Keep raw diagnostic artifacts on an access-controlled local machine or send only a minimized event to the hosted collector. If you must upload fuller artifacts for debugging, use a separate incident workflow with retention limits and named approvers rather than making full capture the default.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov