# Security and access control

TAG is designed so your secret keys never leave your hands. This page explains how authentication, authorization, and credential handling work under the hood.

## Setting up credentials[​](#setting-up-credentials "Direct link to Setting up credentials")

TAG needs two sets of credentials to operate:

### Client credentials[​](#client-credentials "Direct link to Client credentials")

Your application clients use their own Tigris credentials, signing requests with standard AWS SigV4. No changes are needed on the client side beyond pointing the endpoint URL at TAG. TAG never sees or stores client secret keys.

### TAG's own credentials[​](#tags-own-credentials "Direct link to TAG's own credentials")

Your application must provide TAG with its own Tigris credentials via environment variables. These credentials must have read-only access to all buckets that clients will access through TAG:

```
export AWS_ACCESS_KEY_ID=<TAG's access key>

export AWS_SECRET_ACCESS_KEY=<TAG's secret key>
```

Both TAG's access key and client access keys must belong to the same Tigris organization.

## Authentication[​](#authentication "Direct link to Authentication")

TAG operates in transparent proxy mode by default. In this mode, TAG forwards the client's original `Authorization` header to Tigris unchanged and adds cryptographically signed proxy headers (`X-Tigris-Forwarded-Host`, `X-Tigris-Proxy-Access-Key`, `X-Tigris-Proxy-Timestamp`, `X-Tigris-Proxy-Signature`) computed with TAG's own secret key. Tigris independently validates both the client's SigV4 signature and TAG's proxy signature in a single round-trip.

TAG never sees client secret keys, client credentials do not need to be stored anywhere other than on the client, and Tigris retains full control over authorization decisions.

## Local authentication[​](#local-authentication "Direct link to Local authentication")

TAG doesn't call Tigris on every request. After your client's first request, TAG learns enough to validate signatures locally — so cache hits skip the network entirely.

### How it works[​](#how-it-works "Direct link to How it works")

On the first request, TAG learns your client's derived signing keys from Tigris. Every request after that is validated locally:

**First request (key learning):**

**Subsequent requests (local validation):**

## Access control flow[​](#access-control-flow "Direct link to Access control flow")

Here's the full decision tree TAG uses when a request arrives. Most paths end with a forward to Tigris — TAG only serves from cache when it has both a valid signature and a cached authorization grant:

## Authorization lifecycle[​](#authorization-lifecycle "Direct link to Authorization lifecycle")

Authorization decisions are cached per `(accessKey, bucket)` pair:

| Event                                | Action                                    |
| ------------------------------------ | ----------------------------------------- |
| Tigris returns 2xx with signing keys | `AuthzCache.Grant(accessKey, bucket)`     |
| Tigris returns 403                   | `AuthzCache.Revoke(accessKey, bucket)`    |
| TTL expires (10 min default)         | Entry removed, next request re-authorizes |

Authorization is strictly per-bucket. A client may have access to some buckets but not others, and TAG enforces this at the cache level.

## Proxy header security[​](#proxy-header-security "Direct link to Proxy header security")

### Preventing client injection[​](#preventing-client-injection "Direct link to Preventing client injection")

TAG overwrites any client-supplied proxy header values with TAG's own computed values. Clients cannot impersonate TAG or bypass proxy authentication.

### Proxy signature computation[​](#proxy-signature-computation "Direct link to Proxy signature computation")

TAG computes the proxy signature using its own secret key. Only TAG (and Tigris, which knows TAG's key) can produce a valid proxy signature.

## Endpoint validation[​](#endpoint-validation "Direct link to Endpoint validation")

TAG validates the upstream endpoint at startup to prevent misconfiguration and SSRF attacks.

**Allowed hosts:**

| Pattern         | Example                  | Use case                |
| --------------- | ------------------------ | ----------------------- |
| `localhost`     | `http://localhost:8080`  | Development and testing |
| `*.storage.dev` | `https://t3.storage.dev` | Tigris storage domains  |

Any other endpoint causes a fatal startup error.

## Error mapping[​](#error-mapping "Direct link to Error mapping")

| Auth error         | S3 error code         | HTTP status | Action            |
| ------------------ | --------------------- | ----------- | ----------------- |
| Signature mismatch | SignatureDoesNotMatch | 403         | Forward to Tigris |
| Unknown access key | InvalidAccessKeyId    | 403         | Forward to Tigris |
| Expired request    | RequestTimeTooSkewed  | 403         | Forward to Tigris |
| Malformed auth     | MalformedAuth         | 400         | Reject at TAG     |
| Missing auth       | (none)                | (none)      | Forward to Tigris |
