Secret leakage — prompts, logs, stored responses, retention
Status: DOCUMENTED (xAI GET /v1/me.zdr_status LIVE_VERIFIED no_zdr; xAI GET /v1/responses/{id} on a store:false response returned 200 — LIVE_DISCOVERED 2026-09-19)
Sources: https://developers.openai.com/api/docs/guides/background · https://developers.openai.com/api/docs/guides/tools-connectors-mcp#risks-and-safety · OpenAI OpenAPI spec · https://platform.claude.com/docs/en/manage-claude/api-and-data-retention · https://platform.claude.com/docs/en/api/errors#request-id · xAI: https://docs.x.ai/developers/faq/security (30-day encrypted retention, not used for training, team-wide ZDR and what it disables, US regional handling, HIPAA via BAA), https://docs.x.ai/developers/rest-api-reference/inference/responses (store, previous_response_id, include: reasoning.encrypted_content, DELETE), https://docs.x.ai/developers/advanced-api-usage/context-compaction, https://docs.x.ai/developers/files/public-urls · Gemini: https://ai.google.dev/gemini-api/terms (Unpaid vs Paid Services data use), https://ai.google.dev/gemini-api/docs/usage-policies (abuse-monitoring logs, 55 days), https://ai.google.dev/gemini-api/docs/zdr (Search/Maps grounding 30-day storage; Interactions store), https://ai.google.dev/gemini-api/docs/available-regions (EEA/UK/CH Paid Services only), https://ai.google.dev/gemini-api/docs/interactions (store, background, previous_interaction_id) · scripts/live.py::mask (this repo)
Last verified: 2026-09-19
Where secrets leak in an LLM app
| Channel | Leak | Mitigation |
|---|---|---|
| Prompt content | keys, tokens, PII pasted into input/messages (by users, by RAG, by tool outputs such as env dumps) |
redact before sending; never put credentials in prompts (Anthropic computer-use warns even about <robot_credentials>); scrub tool outputs |
| Provider storage | OpenAI stores Responses 30 days by default (store defaults to true; also required for previous_response_id chaining); Anthropic stores per its retention policy unless ZDR |
OpenAI store: false when you don't need retrieval/chaining; request ZDR (both providers, sales/eligibility) for regulated data; know which features are ZDR-ineligible (Anthropic publishes a feature table; OpenAI: stateful features need storage) |
| Your logs | request/response bodies, Authorization/x-api-key headers, URLs with tokens |
structured logging with field allowlists; mask sk-…, Bearer …, x-api-key (see mask() regexes in scripts/live.py); log request ids instead of bodies |
| Model output | the model repeats secrets it saw (system prompt, tool results) to the user or into a tool argument (exfil) | keep secrets out of context in the first place; output filters for key patterns; URL allowlists on outbound tools |
| Error messages | stack traces with paths/env in is_error results or user-facing errors |
sanitise (untrusted-tool-outputs.md) |
| Third parties | MCP servers, web fetch targets, webhooks | MCP: data sent is under the server's retention (OpenAI docs); log what you send; allowlist |
| Caches | prompt caches keyed on content; prompt_cache_key (OpenAI) / cache_control (Anthropic) |
caches are per-organization and provider-internal; still avoid caching secret-bearing prefixes |
| Metadata | metadata, safety_identifier, metadata.user_id |
use opaque/hashed ids, never emails or names (Anthropic: user_id must not contain identifying info) |
| Files | uploaded files readable by any key in the project/workspace | delete after use; per-tenant projects (file-uploads-and-ssrf.md) |
OpenAI knobs
store: false— response not retained for retrieval (you loseprevious_response_id,GET /v1/responses/{id}, background polling beyond the short window, and dashboard logs). For chaining without storage, replay the conversation yourself.include[]— only request what you need (e.g.reasoning.encrypted_contentlets you keep reasoning state client-side withstore: false).- Data controls in the org dashboard (retention, training opt-out is default for API), Zero Data Retention and Data Residency as contractual features; MCP and most tools are compatible but third-party servers are out of scope.
safety_identifiershould be a stable hash of your user id.
Anthropic knobs
- ZDR arrangement (contact sales): no prompts/responses at rest after the response; applies to Messages and Token Counting for eligible features — check the feature eligibility table (e.g. features that inherently store data such as Files, Batches results, container reuse have their own retention). HIPAA-ready access is a separate arrangement.
metadata.user_id: opaque identifier only.- Request tracing without bodies:
request-idheader /request_idfield in errors;anthropic-organization-id,anthropic-workspace-id.
xAI knobs
- Default retention 30 days (encrypted, not used for training, then deleted);
DELETE /v1/responses/{id}for early removal.store: falseis echoed, but live astore:falseresponse was still retrievable by id — do not treatstore:falseas a privacy guarantee; the documented guarantee is ZDR. - Zero Data Retention is a team-wide console toggle (self-serve for admins where available), visible via
GET /v1/me.zdr_statusand thex-zero-data-retentionresponse header. It disables the stateful features:store/previous_response_id, Files, Collections, Batch, deferred completions, stored image/video outputs (base64 only), per-key request logging, voice history — design for client-side state (include:["reasoning.encrypted_content"]and replayoutput[], orPOST /v1/responses/compactblobs, which are opaque and must not be edited). metadatais rejected on Responses (400) — end-user attribution goes insafety_identifier/user;prompt_cache_key/x-grok-conv-idare routing hints, don't put PII in them.- Files are team-scoped and permanent unless
expires_afteris set;POST /v1/files/{id}/public-urlcreates an anonymous CDN URL (files-cdn.x.ai, up to 30 days) — anyone with the URL can download; revoke with/public-url/revoke; deleting the file revokes it. - US regional host (
us.api.x.ai) keeps handling/inference/retained data in the US (not Files/Collections/tools);eu-west-1.api.x.aianswers but is undocumented — not a compliance control. - MCP: your
authorization/headersare forwarded to the third-party server by xAI; the server's retention applies.
Gemini knobs
- Free tier = Unpaid Services: prompts, uploaded content and responses "may be used to provide, improve, and develop Google products", with human review (Terms). Never send confidential or personal data through a free-tier key; enabling a Cloud Billing account switches the project to Paid Services (not used to improve products; abuse-monitoring logs kept 55 days). End users in the EEA/UK/CH may only be served through Paid Services.
- No full zero-data-retention on the Developer API: Search/Maps grounding prompts and outputs are stored 30 days and cannot be disabled; Interactions store state unless
store: false(incompatible withbackgroundand chaining); Files live 48 h; File Search stores persist until deleted; explicit caches until TTL. Contractual ZDR/DPA → Vertex AI. store(per-request logging override ongenerateContent, discovery-documented; accepted live) and Interactionsstore: falsereduce what AI Studio logs; not documented whether they exclude abuse-monitoring logs (UNVERIFIED).- End-user attribution:
labels.safety_identifier(Cloud-label rules: lower-case, ≤ 63 chars) — use a hash, never an e-mail. - Key in URL (
?key=) puts the secret in access logs, referrers and browser history — header only. Auth keys are bound to a service account: a leaked key = a leaked identity with whatever IAM you granted. thoughtSignature/ Interactionssignatureblobs are opaque encrypted reasoning state tied to your key — store them like prompt content (they can contain the model's view of your data), and never send them to another provider.
This repository's discipline (reuse it)
Keys only in .env (600, gitignored); scripts/live.py masks sk-(ant-)?…, xai-…, AIza…, AQ.…, ?key= and bearer|x-api-key|x-goog-api-key values before writing reports/live-requests.jsonl or tmp-live/; Gemini log paths are normalised to models/{model}; nothing under docs/, examples/, tests/, sources/, generated/, reports/ may contain a key; raw responses live only in the gitignored tmp-live/; fixtures cut from live captures have thoughtSignature/encrypted_content truncated.
Checklist
- Redaction layer before the provider call (keys, tokens, PII per your policy) and on tool outputs.
-
store: false(OpenAI, Gemini Interactions) unless chaining/retrieval is needed; xAI ZDR toggled for regulated teams (with client-side state); ZDR-ineligible features inventoried per provider (Gemini grounding storage, xAI stateful features). - Gemini: billing enabled (Paid Services) before any non-public data; free-tier keys only for public/synthetic prompts.
- Logs: headers masked (
Authorization,x-api-key,x-goog-api-key), no?key=URLs, bodies not logged, request ids (x-request-id,request-id,responseId) kept; log retention short. - Opaque hashed ids in
safety_identifier(OpenAI/xAI) /metadata.user_id(Anthropic) /labels.safety_identifier(Gemini). - Output filters for secret patterns and unexpected URLs (incl. xAI inline
[[N]](url)citations). - Files deleted after use (xAI: no auto-expiry unless
expires_after; public URLs revoked); MCP/webhook third parties documented in your data map. - Pre-commit secret scanning on the repo (patterns for all four key formats).