How to Configure Indirect Egress for Assets in OpenMAIC Production Deployments

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):

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:

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:


# 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:

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

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)

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)

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)

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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →