On this page
- A URL crosses more systems than you think
- Query strings are especially easy to leak
- Paths are not safer than query parameters
- URLs are designed to be shareable identifiers
- Authentication headers are easier to classify
- Custom endpoints make this more dangerous
- Redirects can expose URL credentials
- Referrer behavior is another reason to keep secrets out
- Mobile apps still log URLs
- Shell commands can persist URL secrets
- Error messages often include URLs automatically
- Monitoring systems love URLs
- Caches and request keys may preserve URLs
- URLs may be included in backups
- Analytics should be able to keep endpoint metadata without secrets
- Use the provider’s supported authentication contract
- Keep credentials out of base URL fields
- Separate endpoint and authentication in code
- Redact URL parameters defensively anyway
- Testing the invariant
- A practical rule for BYOK clients
- Where BYOKchat fits
- Further reading
An API key in a URL is much harder to contain than the same key in a properly handled authorization header.
Bad:
https://api.example.com/v1/chat?api_key=SECRET
Also bad:
https://api.example.com/v1/SECRET/chat
Prefer the provider’s documented authentication mechanism, usually a header such as:
Authorization: Bearer SECRET
or a provider-specific secret header.
The reason is not that headers are magically private.
It is that URLs are routinely copied, logged, displayed, indexed, shared, cached, and attached to diagnostics by infrastructure that may never expect them to contain secrets.
A URL crosses more systems than you think
A request URL can appear in:
- client debug logs;
- reverse-proxy access logs;
- load balancer logs;
- CDN logs;
- server traces;
- analytics;
- crash reports;
- browser histories;
- screenshots;
- support tickets;
- monitoring dashboards;
- copied curl commands;
- shell history;
- network debugging tools.
Once a credential is embedded in the URL, every one of those becomes part of the secret boundary.
Query strings are especially easy to leak
Developers often think:
It's HTTPS, so the query string is encrypted in transit.
HTTPS protects traffic on the wire between TLS endpoints.
It does not stop the application, proxy, gateway, server, or observability stack from recording the URL after decryption.
The correct question is:
Which systems receive or record the URL as application metadata?
That list is usually long.
Paths are not safer than query parameters
Moving the credential from:
?api_key=SECRET
to:
/v1/SECRET/chat
does not fix the core problem.
Access logs commonly record paths too.
A secret should not be part of the request identifier.
URLs are designed to be shareable identifiers
A URL’s job is to identify a resource.
Developers routinely:
copy URL
paste URL
bookmark URL
print URL
log URL
attach URL to issue
A bearer credential has the opposite property:
show only to the systems that need authorization
Mixing these roles creates leaks.
Authentication headers are easier to classify
A networking stack can centrally recognize:
Authorization
x-api-key
api-key
as sensitive.
A logger can then redact them before recording request metadata.
For example:
Authorization: [REDACTED]
With URL credentials, the logger must parse and sanitize every possible path/query convention correctly.
That is much more error-prone.
Custom endpoints make this more dangerous
A multi-provider AI client may support custom endpoints and custom headers.
If the user enters:
https://example.com/v1?token=SECRET
as a base URL, that secret can leak through:
- connection diagnostics;
- provider settings export;
- analytics;
- URL validation errors;
- UI screenshots;
- backup files.
The client should treat endpoint identity and credential material as separate fields.
base URL -> ordinary configuration
credential -> secret storage
Redirects can expose URL credentials
A request can receive a redirect to another location.
If a credential is embedded in the URL, redirect behavior can become hard to reason about.
The app may:
- preserve the query string;
- construct a new URL from the old one;
- log both locations;
- surface the final URL in diagnostics.
Credentials should remain attached to the configured authentication policy, not to string manipulation of URLs.
Referrer behavior is another reason to keep secrets out
Web-style clients can propagate URL context through referrer mechanisms under some navigation/request scenarios.
Even when modern policies limit referrer data, the durable security rule is simpler:
Do not rely on referrer policy to protect a credential that should never have been in the URL.
A secret-free URL is safer than a secret URL plus many compensating controls.
Mobile apps still log URLs
Even if your native app is not a browser, it can still expose URLs accidentally.
Examples:
print(request.url!)
logger.error("Request failed: \(error) URL=\(url)")
Network inspector: GET /v1/chat?api_key=SECRET
If authentication is in a header, a redaction layer can remove it while leaving the useful URL intact.
Shell commands can persist URL secrets
A support guide might tell a user to test:
curl 'https://api.example.com/v1/models?api_key=SECRET'
That command may remain in shell history.
A header form is still sensitive, but at least it follows normal credential handling patterns:
curl \
-H 'Authorization: Bearer SECRET' \
https://api.example.com/v1/models
Support docs should avoid asking users to paste real credentials into shared logs regardless of mechanism.
Error messages often include URLs automatically
Framework errors can include:
- request URL;
- failing host;
- redirect location;
- route path.
If a secret is in the URL, sanitization must happen before the error reaches:
UI
logs
crash reporting
telemetry
support export
Keeping secrets out of URLs makes ordinary error reporting much safer.
Monitoring systems love URLs
Observability stacks often group requests by:
host
path
route
query
status
latency
That is exactly the metadata you want for debugging.
It becomes dangerous if the same fields also contain credentials.
A well-designed system can record:
POST /v1/chat/completions -> 429 in 812ms
without recording any key.
Caches and request keys may preserve URLs
Some HTTP components use URLs as cache keys or deduplication identifiers.
Even if a sensitive request is not cacheable, application code may still persist the URL for retry/recovery.
For example:
{
"failed_request_url": "https://api.example.com/v1/chat?api_key=SECRET"
}
Now the credential has moved from request memory into local durable state.
URLs may be included in backups
A BYOK client’s backup format may legitimately include configured base URLs.
That is useful for restoring provider connections.
If the base URL itself carries a token, the backup suddenly contains a secret even though the export code intentionally excludes Keychain data.
This breaks a clean security invariant.
Prefer:
{
"base_url": "https://api.example.com/v1",
"credential": null
}
and request the credential again after restore.
Analytics should be able to keep endpoint metadata without secrets
A privacy-preserving client may store sanitized endpoint classes such as:
provider=custom
transport=https
private_network=false
It should not need the full user URL.
If the URL contains a key, even safe endpoint diagnostics become difficult.
See Privacy-Preserving Analytics for AI Apps.
Use the provider’s supported authentication contract
Common patterns include:
Authorization: Bearer <token>
x-api-key: <token>
or another documented header/token mechanism.
Do not invent a query parameter merely because it is easy to append.
If a provider explicitly requires query-based authentication, isolate that integration and treat the URL as sensitive everywhere. But that should be an exception driven by the external protocol, not your default client architecture.
Keep credentials out of base URL fields
When accepting a user-entered custom endpoint, validate for obvious credential material.
For example, flag URLs containing query keys such as:
api_key
token
access_token
secret
key
Do not pretend that heuristic detection is complete.
Instead, design the UI with separate fields:
Base URL
Authentication type
API key / token
Protected custom headers
That guides users toward a safer configuration.
Separate endpoint and authentication in code
Bad model:
struct Connection {
var urlWithCredential: URL
}
Better:
struct Connection {
var baseURL: URL
var credentialID: UUID?
var authKind: AuthKind
}
Then the request builder combines them only at send time.
Redact URL parameters defensively anyway
Even if your app never intentionally puts credentials in URLs, user input and third-party APIs can surprise you.
A sanitizer can remove suspicious parameters before logging:
https://example.com/callback?token=[REDACTED]&page=2
Treat this as defense in depth, not permission to use URL credentials deliberately.
Testing the invariant
Add tests such as:
provider connection export never contains known fake secret
request logs contain URL but not credential
redirect diagnostics redact sensitive query values
support bundle scanner finds no test credential
analytics schema has no raw_url field
backup scanner rejects secret-looking query parameters
A useful fake token:
TEST_API_KEY_URL_LEAK_SENTINEL_123456
Generate logs, backups, analytics, and crash breadcrumbs, then assert that string never appears.
A practical rule for BYOK clients
Use this policy:
Endpoint identity belongs in URL configuration.
Authentication belongs in protected credential storage.
Request construction joins them at the network boundary.
Diagnostics may show endpoint identity, never authentication.
That keeps security boundaries understandable.
Where BYOKchat fits
A multi-provider client needs to preserve useful custom endpoint diagnostics without turning every URL into secret data.
Keeping API keys and protected headers separate from base URLs makes it possible to:
- store credentials in Keychain;
- export provider configuration safely;
- log sanitized endpoints;
- restore connections without secrets;
- inspect failures without leaking authorization.
That is a much stronger design than trying to sanitize credentials after they have already been embedded into URLs.