# Can API host aliases bypass your destination review?

A destination review has value only when it reviews the name that receives the credential. If a team approves `https://api.example.test` but leaves agents free to use `https://api-us.example.test`, `https://gateway.example.test`, or a vendor supplied compatibility endpoint, the approval describes an intention rather than a boundary.

I have seen this fail in the dullest possible way: someone approves a familiar hostname, a deployment variable points at a regional alias, and the same bearer token works. Nobody needed a dramatic exploit. The team simply had more destination names than its review process admitted.

The first correction is conceptual. A CNAME record, an alternate base URL, a redirect, an IP address, and an HTTP authority are related, but they are not interchangeable. Treating them as the same thing produces reviews that look careful and controls that leak credentials.

## A destination review only covers the literal host

A review of `api.example.test` does not approve `api-eu.example.test`, `proxy.example.test`, or `api.example.test.evil.invalid`. The credential should go only to an exact, normalized hostname that appears in an explicit inventory for that credential.

Teams often start with an informal rule such as "this token is for Example's API." That is an ownership statement, not a destination rule. A company can operate many domains, a vendor can move traffic behind several names, and a third party can host one part of a vendor's edge. The HTTP client needs a concrete URL. Your control needs one too.

Write the boundary in terms a URL parser can compare:

- scheme: normally `https`
- hostname: lowercased ASCII hostname after IDNA handling
- port: the explicit port, or the scheme default
- path policy: only if the credential is limited to a particular API surface

Do not replace the hostname list with a suffix test. `endsWith("example.test")` accepts `notexample.test`. `endsWith(".example.test")` still accepts every current and future subdomain. That may be acceptable for an internal service mesh with one owner and strong issuance controls. It is usually lazy for a credential that can alter production data.

The awkward question is whether a human must approve every hostname before use. For a high privilege token, yes. For a low scope token used against a vendor that publishes many regional endpoints, approve a maintained list and make additions a deliberate change. The cost of one more review is smaller than the cost of explaining why a token reached a hostname nobody recorded.

This also separates destination control from request content review. A reviewer may be comfortable with a `GET` to an approved host yet reject a `POST` that changes billing. Those are separate questions. Do not claim that a hostname check decides whether the request is safe; it decides where the credential may go.

## CNAMEs change the route, not the HTTP host

A CNAME changes DNS resolution. It does not, by itself, rewrite the hostname in the URL, the HTTP `Host` header, or the TLS server name indication that a conventional HTTPS client sends.

Suppose an agent calls this URL:

```text
https://api.example.test/v1/orders
```

DNS may answer:

```text
api.example.test.  300  IN  CNAME  api.edge.vendor.test.
api.edge.vendor.test. 300 IN A      203.0.113.42
```

The TCP connection reaches `203.0.113.42`, perhaps on infrastructure run by the vendor. The client should still ask TLS for `api.example.test` and send `Host: api.example.test`. If the server does not present a certificate valid for that original name, certificate validation should fail. If it does present one, the server is authorized to terminate traffic for that name, at least at that moment.

That distinction matters because it knocks down a popular but wrong fix: approving the CNAME target as if it were the API authority. The target name is routing evidence. It can help you understand where traffic goes, but it is not a substitute for reviewing the URL hostname that receives the authorization header.

RFC 1034 describes a CNAME as an alias for another domain name and requires lookup to continue at the canonical name. That explains the resolver behavior, not an HTTP credential decision. DNS has no notion of a bearer token, an API scope, or your change approval.

A CNAME becomes security relevant in four practical cases:

1. The DNS name is owned by a different team or vendor, so a record change can alter where your approved authority terminates.
2. The resolved address crosses into a network you would not expect, such as an internal range or a cloud metadata address.
3. A control approves names from DNS output instead of the URL authority, letting an alias relationship stand in for an actual authorization decision.
4. The application constructs a second URL from the resolved name, redirect target, or service discovery result and then forwards credentials.

The fourth case leaks tokens. The first three weaken review and make future leakage more likely. They deserve different tests and different owners.

A CNAME also does not protect you from DNS rebinding. If a hostname that your client permits later resolves to a different address, the client may make a fresh connection to that address. For destinations you control, monitor the records and restrict where they are allowed to resolve. For destinations you do not control, do not assume a one-time DNS lookup proves permanent safety.

## Alternate base URLs create the quieter bypass

Alternate base URLs are the more common bypass because they change the HTTP authority directly. They are the names hidden in environment variables, SDK defaults, test fixtures, and migration notes.

A service may document all of these for legitimate reasons:

```text
https://api.example.test
https://api-us.example.test
https://sandbox-api.example.test
https://gateway.example.test/service-a
https://tenant-42.api.example.test
```

They may terminate on the same edge today. That does not mean they deserve the same credential. The production endpoint may accept an account-level token; the sandbox endpoint may send requests into a separate system; the gateway path may select another service; the tenant hostname may route according to customer identity. The names encode operational differences that an IP comparison hides.

The failure pattern is predictable. A codebase defines `API_BASE_URL` with a production default. A developer changes it for a regional test or migration. The credential injection layer sees a URL that still looks related and adds the header. The destination review was attached to a label such as "Example API," not the exact authority, so nobody sees the expansion.

Fix the data model before you fix the code. Each credential needs its own record with four fields that reviewers can inspect:

```text
credential: orders-write-prod
allowed authorities:
  https://api.example.test:443
  https://api-us.example.test:443
purpose: create and amend production orders
owner: commerce operations
review trigger: DNS change, new endpoint, scope change
```

The word "authorities" is deliberate. Store the scheme, host, and port together. A hostname that is fine over HTTPS is not automatically approved over a nonstandard port. A path may matter when a shared gateway uses one host for unrelated APIs, though path rules need careful normalization and should never replace separate credentials where scopes differ.

Do not rely on a vendor's published list as your finished inventory. Vendor documentation tells you what can exist. Your code and deployment settings tell you what you can call. You need both, plus the names that remain in old automation after a migration.

## The credential's audience is the boundary

The correct question is not "which servers belong to this vendor?" It is "which HTTP authorities may receive this exact secret in this exact request path?"

A bearer token has no built-in audience restriction unless the issuer enforces one. Once a client puts it in an `Authorization` header, every recipient that receives the header can attempt to use it. Basic authentication and custom API key headers have the same transport problem. Transport encryption protects the request in transit; it does not narrow the audience at the application layer.

OAuth access tokens sometimes include an `aud` claim. That helps the resource server reject a token intended for someone else, but do not confuse server-side rejection with safe client behavior. Sending a token to the wrong hostname still exposes it to that hostname and puts it in its access logs, telemetry, or incident queue. A rejected token is better than an accepted one, but it is still an avoidable disclosure.

Credential binding has two parts:

- The client injects the credential only for a reviewed authority.
- The credential issuer gives it the narrowest practical scope, audience, and environment.

You need both. Host binding prevents a client mistake from spraying a secret across adjacent services. Scope limits the damage if the expected host, its logs, or its route configuration is compromised.

This is why a single organization-wide API key is such a bad bargain. It makes setup easy and incident response ugly. If the same key reaches a payments API, an analytics collector, and a staging gateway, you cannot revoke access to one destination without disrupting all three. Separate credentials turn a routing error into a contained rotation rather than an all-hands outage.

## Inventory names from code, DNS, and vendor docs

An endpoint inventory becomes credible when it captures observed use rather than only planned architecture. Build it from code, deployment configuration, DNS, and vendor documentation, then reconcile the differences.

Start with a repository search for URL schemes and base URL settings. Include application code, shell scripts, CI definitions, example files, infrastructure definitions, and test fixtures. Record every hostname, even names that look obsolete. Old names stay dangerous when a cron job or agent prompt still invokes them.

Then resolve each candidate through the same resolver context the calling machine uses. On macOS or another Unix system, a basic inspection looks like this:

```sh
dig +noall +answer api.example.test CNAME A AAAA
```

A useful output shape is:

```text
api.example.test.       300 IN CNAME api.edge.vendor.test.
api.edge.vendor.test.    60 IN A     203.0.113.42
api.edge.vendor.test.    60 IN AAAA  2001:db8::42
```

Run separate queries for `CNAME`, `A`, and `AAAA` if your resolver does not return the whole chain in one answer. Record the query date and resolver used, because split DNS can give a laptop, a CI runner, and a production host different answers. Do not copy the resulting IP address into a permanent allowlist for an internet API. Edges change addresses routinely. Record it as evidence for review and alert on surprising changes.

Next, make an inventory table with one row per authority, not one row per vendor. It should answer these questions without a meeting:

| Authority | Used by | Credential | DNS owner | Expected route | Review status |
| --- | --- | --- | --- | --- | --- |
| `https://api.example.test:443` | production agent | orders-write-prod | vendor | public edge | approved |
| `https://api-us.example.test:443` | regional job | orders-write-us | vendor | public edge | pending |
| `https://gateway.example.test:443` | legacy script | none | internal team | internal gateway | retired |

Do not invent a row merely because a vendor has an endpoint. Mark it unused until code, configuration, or an approved migration needs it. The inventory should make accidental capability visible, not document every possibility.

OWASP's Server-Side Request Forgery Prevention Cheat Sheet makes a related point: when an application talks only to identified trusted applications, an allowlist is viable, but domain validation alone does not settle DNS behavior. That guidance is aimed at SSRF, yet the operational lesson applies here. A hostname list is strong only when you know who maintains the names, what they resolve to, and whether a caller can turn a reviewed name into a different request later.

## Redirects must get their own review

A redirect is a new destination decision. A client that follows redirects automatically can leave an approved authority after the first request, and an authorization header must not take that trip with it.

For credentialed API calls, start with redirects disabled. Treat a 301, 302, 303, 307, or 308 response as a response that needs an explicit decision. If the new URL is already in the credential's exact authority list and the method and body behavior are acceptable, make a separate request to it. If it is not listed, stop.

The status codes are not interchangeable. A 303 commonly changes a request to `GET`; 307 and 308 preserve the method and request body. An automatic replay of a `POST` with an authorization header is more consequential than a harmless-looking `GET`, especially when the new host differs.

Test the behavior of the specific HTTP library you use. Some clients drop sensitive headers across host changes, some preserve headers under conditions you may not expect, and wrappers can override defaults. A test should capture the request that arrives at a controlled second host, then assert that it received neither the credential nor a copied body. Do not take a library's name as proof of its redirect policy.

A safe call path is simple to state:

```text
1. Parse the requested URL.
2. Normalize and match its authority against the credential record.
3. Inject the credential only after that match.
4. Send one request with redirects disabled.
5. If a redirect arrives, parse and review the new authority before any new request.
```

This sequence also protects against a subtler bug: injecting the header into a generic client before the destination check. Once code attaches a token to a reusable request object, later URL changes can carry it somewhere unintended. Bind credentials at the last responsible moment, after the final URL is known.

## Shared hostnames demand separate credentials

A single hostname can host several APIs, environments, and tenants. Exact host matching is necessary, but it cannot express all the security differences behind a shared gateway.

Consider `https://gateway.example.test`. One path might create invoices, another might submit telemetry, and a third might administer users. If one bearer token authorizes all three, a host review provides only coarse protection. A bug that changes `/telemetry` to `/admin` stays on the approved hostname and still succeeds.

The proper answer is usually separate credentials with distinct scopes. Give the telemetry client a token that cannot administer users, even if both calls go to the same host. If the provider supports audiences or resource indicators, use them. If it supports only broad tokens, split service accounts or choose a safer integration boundary rather than pretending a path allowlist solves authorization.

Path rules still have a place. They can catch coding errors and make intent reviewable. But paths are easier to mishandle than hosts: percent encoding, repeated slashes, dot segments, gateway rewrites, and version redirects all complicate comparison. Normalize with the same URL parser and request library that sends the call. Never make a security decision with a hand-written substring check.

The same reasoning applies to ports. `api.example.test:443` and `api.example.test:8443` are different authorities. A reverse proxy may route them to different services, and a developer who tests the second port may assume the first review covers it. Record both or permit neither.

## Put approvals where a human can still judge destination

An approval that says only "allow API call" asks the reviewer to bless a blank cheque. The prompt needs the method, full normalized authority, request path, credential identity, and the requesting process. Otherwise a human has no practical way to notice that the agent switched from production API to a forgotten compatibility hostname.

Sallyport keeps the credential in its encrypted vault and performs the HTTP action rather than handing the secret to the agent. That is useful because the approval can sit at the action boundary, where the destination and the requesting process are visible together.

Do not turn an approval screen into a ritual. Per-session approval works for a known, short-lived agent run that calls a stable set of approved authorities. Require a per-call confirmation for credentials that can move money, delete data, or touch administration endpoints. The friction should track the consequence of one wrong call, not the reviewer’s patience.

Sallyport deliberately has no policy language or rules engine, so it should not be treated as an excuse to skip the endpoint inventory. Its vault gate and authorization controls answer whether a process may use a credential now. Your credential record must still answer which authority is entitled to receive it.

Activity records should preserve enough evidence to reconstruct the decision later: requested authority, resolved route where available, method, status, requesting session, and which credential record applied. Do not log the secret or full sensitive payloads just to improve an audit trail. A log that creates a second secret store is not an audit improvement.

## Recheck aliases after every DNS and integration change

Destination binding decays when DNS, vendor endpoints, or deployment settings change. Make the inventory review part of those changes rather than a yearly exercise that discovers a pile of stale names.

Trigger a review when someone adds or edits a CNAME, changes an A or AAAA record for a permitted internal name, introduces a regional endpoint, swaps an SDK, changes an API gateway, or adds a redirect. The reviewer should compare the old and new authority lists, then decide whether the existing credential may follow the change. DNS ownership changes deserve the same attention as code ownership changes.

For names you control, alert when an approved hostname begins resolving to private, loopback, link-local, or unexpected internal addresses. This is an SSRF defense as well as a credential defense. For public vendor names, alert on CNAME target changes and material changes in address ranges, then investigate rather than automatically blocking every CDN rotation.

Keep a small regression test beside the integration. It should try an approved URL, an unlisted alternate base URL, a host with a misleading suffix, an explicit alternate port, and a cross-host redirect. The expected result is not merely a failed network connection. The client must refuse to attach the credential before any request reaches the unlisted destination.

That last condition is the standard worth holding. If the agent can send the secret first and discover the destination was wrong afterward, the review failed when it mattered.
