8 min read

Basic authentication usernames are sensitive metadata

Basic authentication usernames can reveal tenants, account roles, and API structure. Keep sensitive API metadata out of agent prompts and logs.

Basic authentication usernames are sensitive metadata

Basic authentication usernames are sensitive metadata.

That statement sounds fussy until you have to clean up after an automation run that copied billing-export@north-division into a shell transcript, a CI log, an agent prompt, and an incident ticket. The password may be absent from all four places. An attacker still learns that a north-division tenant exists, that it has a billing export integration, and that the account probably reaches a particular legacy API.

Teams often classify data into secrets and everything else. That split is too crude for agent-driven API work. A username, tenant ID, hostname, API path, and response code may each look harmless alone. Put them together and they describe an account structure worth attacking. Treat those combinations as sensitive metadata, especially when an agent can copy context much farther and faster than a person typing one request.

A username can reveal how the account system is built

A Basic username often carries more meaning than its field name suggests. Legacy systems use it as a login name, a customer code, a division label, a service role, an environment marker, or a compound identifier glued together before anyone had a better identity model.

Consider these values:

acme-east:password
svc-payroll-prod:password
tenant-48291-export:password
[email protected]:password

The first value tells you a customer and regional split. The second names an internal capability and an environment. The third reveals a tenant identifier and a function. The fourth exposes a person and a customer relationship. A password rotation fixes the password half. It does not make the copied account map disappear.

This matters because attackers do not begin every attack with a credential. They begin with a target list. A recognizable username helps them craft a believable reset request, guess related accounts, search breach dumps, probe a customer-specific route, or pressure support with details that sound internal.

Do not dismiss this as theoretical because the username is only valid with a password. Knowing that svc-orders-import-prod exists is not equivalent to controlling it, but it is far more useful than knowing nothing. Security work gets expensive when teams wait for a field to be a complete credential before they protect it.

The right classification depends on context. A generic public username used by every sandbox account may need little protection. A tenant-scoped service identity paired with an internal endpoint deserves much tighter handling. Write that distinction down for each integration instead of applying a blanket label after a leak.

Basic authentication keeps the account name attached to every call

Basic authentication sends a user-id:password value encoded in Base64 in the HTTP Authorization header. Base64 changes representation. It does not hide the original bytes from anyone who can read the header.

A typical request looks like this:

GET /v1/exports/monthly HTTP/1.1
Host: api.legacy.example
Authorization: Basic c3ZjLWJpbGxpbmctZXhwb3J0LXByb2Q6cmVkYWN0ZWQ=
Accept: application/json

The encoded text carries both pieces every time the client calls the API. That repetition is the awkward property teams forget. A bearer token can also expose account context, of course, but many Basic integrations use human-readable usernames that spell it out plainly after decoding.

RFC 7617 defines the scheme and makes two details relevant here. The first colon separates the user-id from the password, so a colon in the username is invalid. Control characters are also forbidden. That means a legacy vendor may accept a visually convenient account convention that cannot actually survive a standards-compliant Basic header. Do not invent an escaping rule and hope every library agrees with it.

The second detail is character handling. RFC 7617 permits a server challenge to advertise UTF-8, but that signal is advisory and many older systems do not handle non-ASCII identities consistently. If an account name includes accented characters, case rules, or transformed Unicode, test the exact client and server pair before production. A mismatch can become an authentication failure that someone "fixes" by spraying the full credential into debug output.

Basic authentication also needs HTTPS. OWASP describes Basic credentials as encoded rather than encrypted and recommends TLS wherever Basic is used. TLS protects the connection in transit. It does not protect a header after a client library, reverse proxy, tracing agent, error handler, or debugging tool records it.

Tenant IDs and endpoints become dangerous as a pair

A tenant ID alone may be an opaque number. An endpoint alone may be a generic route. Together, they can identify a customer's business function and the system that carries it.

Suppose an agent receives this instruction:

For tenant 48291, call https://ledger.internal.example/v2/reconciliation/import
with username tenant-48291-ledger-import.

Even if another component injects the password, the instruction gives the agent a tenant, a host, an operation, and a service identity. The agent may quote that instruction in its own working notes. A tool wrapper may record it. A model transcript may preserve it. A developer may paste the error into a chat channel. The password stayed out of sight, but the account structure did not.

The exposure grows when names follow a predictable grammar. If one username is tenant-48291-ledger-import, it is reasonable to guess there may be tenant-48291-ledger-export, tenant-48291-reporting, or the same roles for other tenants. Predictability is convenient for operators and useful for enumerators. You do not have to abandon all naming conventions, but you should recognize when the convention turns one leaked name into a directory query.

Endpoint pairs carry their own signal. /admin/users, /payroll/export, /claims/submit, and /archive/retention reveal different kinds of work even with a neutral hostname. A host plus path plus tenant often reveals enough to make a phishing message or support impersonation attempt credible.

Classify the tuple, not only its fields:

  • service identity plus tenant ID
  • service identity plus hostname
  • tenant ID plus endpoint path
  • endpoint path plus response body or error text
  • timestamp plus a successful action record

This is a sharper rule than "redact passwords." It tells reviewers why a log line that contains no literal secret may still be unsafe to send to a broad observability system.

Agents copy context where ordinary clients do not

An ordinary API client usually receives a URL, an account name, and a password through a narrow configuration path. An AI agent handles instructions as text. It may inspect a repository, read a ticket, call a command, interpret an error, and write a summary. Each handoff can preserve identifiers that a traditional client would never need to display.

The risk is not that agents are uniquely careless. The risk is that agent workflows make context portable by design. The same feature that lets an agent reason across a deployment note and an API response lets it carry tenant names and endpoint patterns into tool inputs, transcripts, or generated patches.

A familiar failure looks like this:

  1. A developer puts a Basic username and tenant code in a local .env file because the password comes from a secret store.
  2. The agent reads the file to understand a failing integration.
  3. The API returns a 401 response with a verbose error that repeats the username and tenant.
  4. The agent writes a troubleshooting report containing the config excerpt and error.
  5. The developer pastes that report into an issue visible to a wider group.

Nobody intended to publish credentials. Still, the issue now records a valid naming pattern, a tenant, a host, a route, a service role, and a time window when the integration was active. That is enough context to create follow-on risk.

Do not solve this by forbidding agents from seeing every nonsecret string. That blocks useful work and usually fails in practice. Instead, decide which action needs which facts. An agent that needs to request a monthly export may need an abstract action name and a month. It does not need the Basic username, password, tenant routing rule, or raw target URL.

This changes the design question from "Can the agent authenticate?" to "What is the smallest action description the agent needs to produce the intended request?" That is the question that keeps account structure out of the agent's context window.

Keep identity material outside prompts and repositories

Put MCP calls behind control
Use the bundled sp mcp shim to connect Claude Code or another MCP-capable agent to controlled HTTP actions.

A prompt is a poor configuration store. So are source files, sample curl commands, issue templates, shell aliases, and test fixtures. They all travel farther than their author expects.

Start by separating three things that teams routinely blur together:

  1. Credential material is the username and password used to authenticate.
  2. Routing metadata tells a client where a request goes, such as a host, tenant partition, or endpoint family.
  3. Action intent is the business operation, such as "download the March reconciliation file."

A human or an agent can often state action intent without seeing the other two categories. That is the preferred boundary. If a legacy API forces routing through a tenant-specific host or a tenant-specific username, keep the mapping in the component that executes the request, not in the prompt that asks for it.

Avoid raw .env examples like this in a repository:

LEGACY_API_URL=https://tenant-48291.api.legacy.example/v2/payroll/export
LEGACY_API_USER=tenant-48291-payroll-export
LEGACY_API_PASSWORD=replace-me

The replace-me placeholder does not make the example safe. The URL and username still document a customer-specific integration pattern. A copied example can also turn into production configuration later, usually under deadline pressure.

Use an abstract local contract instead:

actions:
  export_monthly_payroll:
    account_ref: payroll-export-production
    target_ref: payroll-export-api
    inputs:
      - tenant_alias
      - month

The account_ref and target_ref labels should be opaque enough that a repository reader cannot infer the customer, hostname, or service role. The executor resolves them locally. The agent receives tenant_alias only if the action truly requires it, and that alias should not be a production tenant identifier when a local mapping can do the job.

This approach also prevents a common migration mistake: moving the password to a secret manager while leaving the username, target URL, and customer code hard-coded in the application. That is an improvement over committing a password, but it leaves the structure exposed to every developer, build log, and code-scanning export.

Use aliases that do not turn one leak into a directory

Opaque names are not magic, but they reduce the information an accidental disclosure provides. The account name should describe its purpose to the small group that administers it, not advertise a customer relationship to every system that sees it.

Compare these service aliases:

bad:  acme-east-payroll-export-prod
better: svc-47f2-export-p1

The second one still has a readable service prefix and environment marker. That is often practical. Its useful details stop there. A separate protected registry can map svc-47f2-export-p1 to the customer, owner, endpoint, permissions, and rotation record.

Do not treat random-looking aliases as a substitute for authorization. An attacker who has a password can still authenticate, and an employee who has access to the registry can still see the mapping. Aliases reduce unnecessary disclosure. Least privilege and access reviews control what the account can do.

There are cases where a vendor dictates the username format. Some older APIs require a customer number, an email address, or a compound value that includes a region. When that is true, accept that the field is sensitive and change the handling around it. Do not pretend the value is public because you cannot rename it.

Keep vendor-required identifiers in the credential boundary, and make downstream outputs boring. A request executor can return export accepted with a job identifier. It does not need to echo the submitted username, full target URL, or tenant routing value to the calling agent.

Logs need an audit trail without an account directory

Security logging needs enough detail to answer who initiated an action, what happened, when it happened, and whether it succeeded. It does not need to preserve every byte the client sent. OWASP's logging guidance explicitly warns against logging sensitive information such as passwords, session identifiers, and unnecessary system details, while also calling for authentication and access-control events to be recorded.

The tension is real. If you remove everything, operators cannot investigate an incident. If you retain raw headers and full URLs forever, your logs become a searchable account directory.

Use a record shape that separates operational correlation from sensitive metadata:

{
  "event": "legacy_api_call",
  "action": "export_monthly_payroll",
  "request_id": "req_01J...",
  "actor_run": "run_01J...",
  "credential_ref": "cred_4c91",
  "target_ref": "target_a77e",
  "result": "denied",
  "http_status": 401,
  "reason": "authentication_failed",
  "occurred_at": "2026-07-22T14:03:21Z"
}

The references let authorized operators correlate events with a protected inventory. They do not put the username, tenant ID, raw endpoint, or Authorization header in every event. Keep the inventory access narrower than ordinary log access.

Do not log a hash of a username and call the problem solved. A deterministic hash of a small, patterned username space is often guessable, and a stable hash still permits tracking an account across records. If you need correlation, use a random credential reference assigned by your own system. Rotate or retire the reference when you rotate the credential.

Be careful with failure diagnostics. This is a bad response to pass through:

401 for tenant-48291-payroll-export at /v2/payroll/export: user exists but password rejected

It confirms an account, tenant, route, and validation result. Better internal handling keeps the vendor's exact diagnostic in a restricted support record if you truly need it, while the general activity trail records authentication_failed. The agent should receive a short failure that tells it to stop and request help, rather than another hint to try variations.

Redaction and minimization solve different problems

Stop passing headers to agents
Sallyport handles bearer, Basic, and custom-header injection inside the Mac app rather than inside agent context.

Redaction removes a known dangerous value after someone has already handled it. Minimization stops the value from entering a place where it does not belong. You need both, and confusing them produces bad designs.

A header scrubber can replace this:

Authorization: Basic c3ZjLWJpbGxpbmctZXhwb3J0LXByb2Q6cmVkYWN0ZWQ=

with this:

Authorization: [REDACTED]

That is necessary. It does nothing about the path, hostname, tenant query parameter, verbose 401 body, request label, or trace attributes recorded beside the header. A system that proudly redacts passwords while retaining tenant-48291.api.legacy.example/payroll/export has reduced one risk and left a useful account map behind.

Minimization asks harder questions before the request runs:

  • Does the agent need the actual target host or only an action name?
  • Does the activity log need the tenant identifier or only a protected reference?
  • Does support need the raw vendor response in the ordinary log stream?
  • Does the request need a tenant in the URL when the executor can resolve it locally?
  • Does a person reviewing an approval need the full identity, or a human-friendly but nonrevealing label?

This is where many teams make a popular but wrong recommendation: "Log the whole request once, then improve the filters later." They say it because debugging legacy APIs is miserable and full captures answer questions quickly. The captured data then becomes permanent evidence in backups, analytics stores, test environments, and copied incident notes. Build a restricted diagnostic path for a short-lived investigation instead. Do not make broad raw capture the normal operating mode.

Legacy APIs deserve a containment boundary, not blind trust

You may not be able to replace Basic authentication this quarter. A supplier may support only one integration style. A warehouse appliance may have an API frozen years ago. The practical response is containment, not a wishful declaration that the legacy API is acceptable.

Put the Basic credential and the identifier mapping behind a component that performs the HTTP request itself. The agent should ask for a named action with structured inputs. The request component selects the target, obtains the credential, injects it into the header, checks the intended scope, and returns a limited result.

For an agent action, a useful request contract looks like this:

{
  "action": "export_monthly_payroll",
  "tenant_alias": "tenant_ref_91ab",
  "month": "2026-06"
}

The action executor can validate the month, resolve tenant_ref_91ab in a local protected mapping, and make the vendor call. It should reject extra fields such as url, authorization, username, and headers. If callers can override those fields, they can route a trusted credential to an arbitrary host or turn a narrowly defined action back into a raw HTTP client.

That restriction matters for SSRF-style mistakes as well as credential exposure. A user-provided URL is not a harmless convenience when the executor holds credentials. The target must come from a controlled definition, and redirects need equally careful treatment. Do not follow a redirect to a new origin while retaining the Authorization header.

This boundary should also handle retries. A poorly designed agent may retry a 401 with changed input or repeatedly invoke an operation after a timeout. The executor should distinguish a safe transport retry from an authentication failure or an unknown write outcome. For a non-idempotent legacy endpoint, return a status that forces a human review rather than sending the same request again because the agent asked confidently.

Sallyport fits this pattern when an MCP-capable agent needs to call an HTTP API without receiving the Basic credential itself. Its HTTP channel injects Basic credentials inside the app, and its per-session and per-call controls let a person decide when an agent run may use that account. The agent gets the action result, not a plaintext username or password to reuse elsewhere.

Approval must show enough context to catch the wrong action

Keep Basic identities out of prompts
Sallyport injects Basic credentials from its encrypted vault, so agents never receive the username or password.

An approval dialog that says only "Allow API call?" does not help. A dialog that prints the complete Basic username, tenant, full URL, and raw request body shares too much. The reviewer needs a compact description that makes a bad action obvious without exposing the whole account structure.

Show the action name, a protected target label, a tenant display label chosen for the reviewer, operation type, and high-level consequence. For example:

Allow export_monthly_payroll?
Target: Payroll export service
Tenant: Finance tenant 7
Operation: Create June 2026 export
Agent run: signed local coding process

That gives a reviewer enough information to notice an unexpected month, function, or target. It does not reveal a vendor hostname, tenant number, or service username in the approval record.

The display label needs governance. If "Finance tenant 7" appears in a team with only one finance customer, it may still identify that customer. Use labels appropriate to the group that sees them. Security is not achieved by replacing every useful name with an opaque code that reviewers cannot understand.

For sensitive Basic accounts, require approval on every use when the action can move money, export regulated records, change access, or contact an external party. Per-session approval is more practical for a short run that makes many low-risk reads. The choice should follow the account's authority and the action's consequence, not the fact that Basic authentication happens to be old.

Keep an auditable record of the approval decision and the resulting call. A tamper-evident record is useful only if it describes the actor, action, result, and approval state without becoming another copy of the credential map. Sallyport's separate session and activity journals are designed around that split, with both views projected from its encrypted hash-chained audit log.

Make account structure hard to collect in the first place

The fix is not a giant policy document that says "handle metadata carefully." Make the safer route the path developers and agents naturally use.

Inventory every Basic integration and answer these questions for each one: What does the username reveal? Does it contain a person, customer, environment, product, or role? Which host and endpoint pair make that value more revealing? Where do those values appear today? Which caller truly needs to see them?

Then remove the easy copies. Replace raw curl snippets with action examples. Replace tenant-specific fixture names with neutral test aliases. Block Authorization headers and URL userinfo in application logs. Keep full vendor diagnostics out of agent-visible output. Reject user-supplied request targets at the credential boundary. Give support a controlled way to retrieve details when an incident justifies it.

RFC 9110 says senders must not generate HTTP or HTTPS URI references with user:password@host userinfo, and it warns that implementations can expose a user identifier or password when they use that form in configuration or command options. That is a useful warning beyond URLs: identity details placed in convenient text fields tend to get copied into places where they were never meant to live.

Basic authentication may remain because a vendor has not moved on. Your own handling does not have to remain stuck with it. Once you treat usernames, tenant IDs, and endpoint pairs as sensitive metadata, you stop handing an agent the account directory along with the task.

FAQ

Is a Basic authentication username considered sensitive data?

Yes. A Basic username can identify a customer, service role, environment, directory convention, or tenant even when the password remains protected. Treat it as sensitive operational metadata when combining it with a host, path, or tenant identifier reveals more than any one field alone.

Does Base64 protect Basic authentication credentials?

Base64 is an encoding, not encryption. Anyone who can read the Authorization header can decode the username and password, so Basic authentication needs TLS and careful handling at every point that can observe request headers.

Are tenant IDs sensitive if they are not secrets?

Usually, yes. A tenant ID plus an endpoint path often tells an observer which customer uses which service and what operation the account can perform. That can support targeted phishing, account discovery, or a more accurate attack against an exposed legacy system.

Should an AI agent use a human Basic authentication account?

A service account names an automated actor and should not double as a human employee identifier. Use an opaque, purpose-specific alias, keep it out of prompts and source control, and make sure its access is limited to the job it actually performs.

Can I put Basic authentication in an API URL?

Do not put HTTP credentials in a URL. RFC 9110 deprecates the user:password userinfo form for HTTP and HTTPS references, and URLs spread through shell histories, logs, tickets, browser history, and monitoring systems.

Why is redacting the password not enough in request logs?

A redacted log can still expose a customer or account if it retains the hostname, endpoint, tenant ID, service username, timestamp, and status code. Redaction removes a secret value; metadata minimization asks whether the remaining record lets someone reconstruct the relationship.

What makes a safer legacy API username?

Start with distinct names per environment and per purpose, then avoid customer names, email addresses, and internal system names in those aliases. An opaque alias such as svc-billing-export-prod-7 is often safer than an employee name, but it still needs protection when paired with an endpoint and tenant.

How can an AI agent call a legacy Basic authentication API safely?

Use an action gateway when the agent must call a legacy API but should never receive the credential material. The gateway should inject the Basic credential itself, obtain a human approval when your process requires it, and return only the API result to the agent.

Does HTTPS make Basic authentication safe enough?

No. HTTPS protects the request while it travels across the network, but it does not control what the client, proxy, log collector, debugger, browser extension, CI runner, or agent transcript retains. You still need to limit where the username and endpoint pair can appear.

What should I do if a Basic authentication header reaches a log?

Rotate immediately if the password may be exposed, then assess the username, tenant, endpoint, and request history as exposed context too. A new password does not erase copied logs, prompts, or tickets that reveal which account exists and what system it reaches.

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