# How to Configure Indirect Egress for Assets in OpenMAIC Production Deployments

> Learn to configure indirect egress for assets in OpenMAIC production deployments. Optimize performance by setting ASSET_BYTE_EGRESS to redirect and following best practices for signed URLs.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: how-to-guide
- Published: 2026-09-06

---

**Set `ASSET_BYTE_EGRESS=redirect`, ensure `ASSET_COLLECTION_GRACE_MS` is at least 10× the signed‑URL TTL, and verify your object store supports signed URL generation.**

OpenMAIC uses a three‑layer storage architecture—registry, byte layer, and optional object store—to manage binary assets like images, video, and audio. In production environments, you can configure **indirect egress** so that asset requests return short‑lived signed URLs instead of raw bytes, offloading large downloads directly to object storage. This article explains how to configure indirect egress for assets in OpenMAIC production deployments based on the source code in `THU-MAIC/OpenMAIC`.

## How Indirect Egress Works

When indirect egress is enabled, the asset handler behavior changes at `packages/@openmaic/storage/src/server/asset.ts` (line 613). Instead of streaming bytes, the server validates authorization, then constructs a signed URL pointing to the underlying object store.

The flow follows these steps:

1. **Request reception**: The HTTP handler receives `GET /assets/{id}/content` at `packages/@openmaic/storage/src/server/asset.ts`.

2. **Authorization**: The principal is derived from the session and the asset entry is read—authorization occurs **before** any byte access, ensuring no‑bearer‑token exposure.

3. **Signed URL generation**: When `byteEgress === 'redirect'`, the handler mints a signed URL and adds response headers including `X‑Asset‑Revision`, `Cache‑Control`, and `Vary`.

4. **Response formats**: The client receives either `302 Found` with a `Location` header, or `200 OK` with a JSON descriptor (`application/vnd.openmaic.asset-descriptor+json`) if requested.

5. **Direct object store fetch**: The client follows the signed URL; the URL itself serves as the credential, with no further credentials forwarded.

The **Asset HTTP contract** in `packages/@openmaic/storage/docs/asset-http-contract.md` formalizes these semantics under the "Indirect byte egress" section.

## Production Configuration Steps

### 1. Enable Redirect Mode

Set the environment variable `ASSET_BYTE_EGRESS=redirect` in your container or orchestrator configuration. This is read by `configuredAssetByteEgress()` in `app/api/persistence/[...path]/route.ts` (lines 43–48):

```bash
export ASSET_BYTE_EGRESS=redirect

```

### 2. Configure Collection Grace Period

Set `ASSET_COLLECTION_GRACE_MS` to at least ten times the signed‑URL TTL. The default TTL is derived from `DEFAULT_SIGNED_URL_TTL_SECONDS`, and `indirectEgressWithinGrace()` validates this relationship (lines 66–74). If grace is insufficient, the system falls back to direct egress with a warning:

```bash
export ASSET_COLLECTION_GRACE_MS=86400000   # 24 hours (default is 30 days)

```

### 3. Verify Object Store Capabilities

Your underlying object store (S3, MinIO, etc.) must support signed URL generation. The OpenMAIC service account requires `s3:PutObject`, `s3:GetObject`, and `s3:ListBucket` permissions. The storage implementation—`PgAssetStore`, `S3AssetStore`, or similar—is selected at boot time.

### 4. Expose Grace Period to Asset Collector

The collector reads the same `ASSET_COLLECTION_GRACE_MS` variable via `resolveAssetCollectionGraceMs()` to know the maximum signed URL lifetime. Ensure both persistence and collector services share this configuration.

### 5. Deploy with Egress Lockdown

Enable the default egress isolation. The render service runs with `RENDER_EGRESS_LOCKDOWN=true`, which restricts outbound traffic to explicitly allowed endpoints:

```yaml

# render-service/README.md (lines 103-106)

RENDER_EGRESS_LOCKDOWN=true

```

This prevents unauthorized egress while permitting connections to your object store endpoint.

### 6. Verify Configuration

Test an asset endpoint with cURL:

```bash
curl -i -H "Authorization: Bearer <token>" \
     https://<openmaic-host>/assets/<asset-id>/content

```

With indirect egress active, expect `302 Found` with a `Location` header, or `200 OK` with a JSON descriptor containing the signed URL.

## Configuration Examples

### Docker Compose Production Profile

```yaml
services:
  persistence:
    image: ghcr.io/thu-maic/openmaic/persistence:latest
    environment:
      - ASSET_BYTE_EGRESS=redirect
      - ASSET_COLLECTION_GRACE_MS=2592000000
      - ASSET_BYTE_EGRESS_SIGNED_URL_TTL_SECONDS=60
    cap_add:
      - NET_ADMIN

```

### Custom Storage Handler (TypeScript)

```typescript
import { createStorageHttpHandler } from '@openmaic/storage';

const handler = createStorageHttpHandler(runtimeStore, documentStore, {
  authenticate: myAuthFn,
  authorizeAssets: myAssetPolicy,
  byteEgress: {
    mode: 'redirect',
    collectionGraceMs: 30 * 24 * 60 * 60 * 1000, // 30 days
  },
});

```

### Client-Side Descriptor Handling (Node.js)

```typescript
import { HttpAssetStore } from '@openmaic/storage';

const store = new HttpAssetStore({ baseUrl: 'https://api.my-openmaic.com' });

async function getAssetUrl(id: string) {
  const desc = await store.getDescriptor(id, {
    headers: { Accept: 'application/vnd.openmaic.asset-descriptor+json' },
  });
  return desc.url; // Short-lived signed URL
}

```

### Manual Verification (cURL)

```bash
curl -i -H "Authorization: Bearer <token>" \
     https://api.my-openmaic.com/assets/12345/content

# Expected response

HTTP/1.1 302 Found
Location: https://s3.my-cloud.com/bucket/obj?X-Amz-Expires=60&X-Amz-Signature=...
X-Asset-Revision: 42
Cache-Control: private, no-store
Vary: Cookie, Authorization, Accept

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| `packages/@openmaic/storage/docs/asset-http-contract.md` | Defines indirect egress contract, headers, TTL rules, and security trade-offs (lines 187–206) |
| `packages/@openmaic/storage/docs/reference-server.md` | Documents `byteEgress` option passing and indirect mode enablement (lines 58–66) |
| `app/api/persistence/[...path]/route.ts` | Reads environment variables, validates grace period, injects `byteEgress` option (lines 43–78) |
| `packages/@openmaic/storage/src/server/asset.ts` | Implements direct/redirect egress decision, signed URL construction, response headers (lines 613–630) |
| [`render-service/README.md`](https://github.com/THU-MAIC/OpenMAIC/blob/main/render-service/README.md) | Describes egress lockdown for container isolation (lines 103–106) |

## Indirect vs. Direct Egress: When to Choose

**Indirect egress** suits production workloads requiring:
- **Cost-effective scaling**: Large media streams from cheap object storage, reducing application server load
- **Security hardening**: Short-lived URLs limit exposure window; credentials never transit through clients
- **Compliance alignment**: No content hash disclosure on the wire (unless using hash-keyed byte layers)

**Direct egress** remains appropriate for:
- **Confidential assets** where "no disclosure" is mandatory
- **Development environments** without object store infrastructure
- **Small assets** where redirect overhead exceeds benefit

## Summary

- Set `ASSET_BYTE_EGRESS=redirect` to enable indirect egress for assets in OpenMAIC
- Ensure `ASSET_COLLECTION_GRACE_MS ≥ 10 ×` signed‑URL TTL to prevent premature URL expiration
- Verify object store supports signed URLs and OpenMAIC service account has required S3 permissions
- Deploy with `RENDER_EGRESS_LOCKDOWN=true` for security isolation
- Test endpoints to confirm `302` redirects or JSON descriptor responses
- Choose direct egress for confidential assets requiring zero disclosure

## Frequently Asked Questions

### What happens if the collection grace period is too short?

`indirectEgressWithinGrace()` in `app/api/persistence/[...path]/route.ts` detects the misconfiguration, logs a warning, and falls back to direct egress. This prevents signed URLs from outliving the grace period during which reclaimed bytes may be deleted.

### Can clients request the signed URL directly without following a redirect?

Yes. Clients send `Accept: application/vnd.openmaic.asset-descriptor+json` to receive a `200 OK` response with a JSON descriptor containing the signed URL. The `@openmaic/storage` client library handles this automatically via `getDescriptor()`.

### Does indirect egress weaken security authorization?

No. The Asset HTTP contract enforces identical authorization checks before minting any signed URL. The principal is validated, asset permissions are checked, and only then does `packages/@openmaic/storage/src/server/asset.ts` construct the redirect response.

### Which object stores work with OpenMAIC indirect egress?

Any store implementing the signable byte layer interface works, including AWS S3, MinIO, Google Cloud Storage, and Azure Blob Storage via compatible adapters. The implementation is selected at server boot based on your storage configuration.