# How Read-Only Session Sharing Works in Open Agents Sandboxes

> Discover how Open Agents sandboxes enable read-only session sharing through unique share IDs and public endpoints serving sanitized markdown and streaming status.

- Repository: [Vercel Labs/open-agents](https://github.com/vercel-labs/open-agents)
- Tags: internals
- Published: 2026-04-16

---

**Open Agents implements read-only session sharing by generating a unique share ID that maps to a specific chat, then exposing public endpoints that serve sanitized markdown and streaming status without ever contacting the underlying sandbox runtime.**

Open Agents is an open-source platform for running AI agents in isolated sandboxes. The read-only session sharing feature allows users to expose individual chat transcripts as public, non-interactive views while maintaining strict isolation from the sandbox environment.

## Creating a Share Link

Authenticated users initiate sharing by posting to the session-scoped endpoint. In `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/share/route.ts`, the handler first validates ownership via `requireAuthenticatedUser` and `requireOwnedSessionChat`, then creates a persistent share record.

The system uses a 12-character nanoid as the public identifier:

```typescript
// POST /api/sessions/:sessionId/chats/:chatId/share
const createdShare = await createShareIfNotExists({
  id: nanoid(12),   // random short ID used in the public URL
  chatId,
});
return Response.json({ shareId: createdShare.id });

```

If a share already exists for the chat, the endpoint returns the existing ID, making the operation idempotent.

## Public Read-Only Endpoints

Once created, the share ID enables anonymous access to two dedicated read-only routes. Neither endpoint contacts the sandbox runtime; both query the database tables (`shares`, `chats`, `messages`) directly.

### Retrieving Streaming Status

The status endpoint at `GET /api/shared/:shareId/status` returns a minimal payload indicating whether the chat has an active stream:

```typescript
// GET /api/shared/:shareId/status
const status = await getSharedChatStatus(shareId);
if (!status) return Response.json({ error: "Not found" }, { status: 404 });
return Response.json(status);

```

The underlying resolver in `apps/web/app/api/shared/[shareId]/status/get-shared-chat-status.ts` loads the share and its associated chat to check `activeStreamId`:

```typescript
export async function getSharedChatStatus(
  shareId: string,
): Promise<SharedChatStatusData | null> {
  const share = await getShareByIdCached(shareId);
  if (!share) return null;
  const chat = await getChatById(share.chatId);
  if (!chat) return null;
  return { isStreaming: chat.activeStreamId != null };
}

```

### Accessing the Markdown Transcript

The markdown endpoint at `GET /api/shared/:shareId/markdown` returns the full chat transcript as markdown, with sensitive environment variables redacted:

```typescript
// GET /api/shared/:shareId/markdown
const share = await getShareByIdCached(shareId);
if (!share) return Response.json({ error: "Not found" }, { status: 404 });

const messages = await getMessagesByChatId(share.chatId);
// Redact any env values that might have been printed.
const safeMarkdown = redactSharedEnvContent(messages);
return new Response(safeMarkdown, { headers: { "Content-Type": "text/markdown" } });

```

The `redactSharedEnvContent` utility ensures that any secret values accidentally printed to the chat are scrubbed before public exposure.

## Security Model: Isolation from the Sandbox

Read-only session sharing maintains strict isolation through three architectural constraints:

- **Database-only queries**: The public routes invoke `getShareByIdCached`, `getChatById`, and `getMessagesByChatId` from [`apps/web/lib/db/sessions.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/db/sessions.ts) and [`apps/web/lib/db/sessions-cache.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/db/sessions-cache.ts). These helpers query the persistent store, never the live sandbox.
- **No runtime exposure**: The share endpoints do not receive the Vercel sandbox ID, file system handles, or process references. Consequently, viewers cannot trigger write operations, shell commands, or agent restarts.
- **Secret redaction**: Environment variables and other sensitive values are stripped via `redactSharedEnvContent` before the markdown payload is returned, preventing accidental credential leakage.

This design guarantees that a public share link provides a static, read-only view of a chat transcript without any vector to manipulate the underlying sandbox or access other users' sessions.

## Key Implementation Files

| Area | File | Purpose |
|------|------|---------|
| **Share creation** | `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/share/route.ts` | Authenticated POST/GET/DELETE for chat-level share links. |
| **Public status endpoint** | `apps/web/app/api/shared/[shareId]/status/route.ts` | Anonymous `isStreaming` check. |
| **Status resolver** | `apps/web/app/api/shared/[shareId]/status/get-shared-chat-status.ts` | Core logic mapping share ID to streaming status. |
| **Public markdown endpoint** | `apps/web/app/api/shared/[shareId]/markdown/route.ts` | Anonymous transcript retrieval with redaction. |
| **Database helpers** | [`apps/web/lib/db/sessions.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/db/sessions.ts) | CRUD operations for `shares`, `chats`, and `messages`. |
| **Cached queries** | [`apps/web/lib/db/sessions-cache.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/db/sessions-cache.ts) | Cached variants like `getShareByIdCached`. |

## Summary

- **Read-only session sharing** in Open Agents generates a unique 12-character share ID that maps to a specific chat without exposing the sandbox runtime.
- **Authenticated creation** happens via `POST /api/sessions/:sessionId/chats/:chatId/share`, which persists a `share` row referencing the chat.
- **Anonymous consumption** uses `GET /api/shared/:shareId/status` for streaming state and `GET /api/shared/:shareId/markdown` for the transcript, both querying the database directly.
- **Security guarantees** include strict isolation from the sandbox (no filesystem or process access), secret redaction via `redactSharedEnvContent`, and read-only database queries.

## Frequently Asked Questions

### How is the share ID generated?

Open Agents uses `nanoid(12)` to generate a 12-character random string when creating a share link in `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/share/route.ts`. This short ID becomes the public-facing identifier in share URLs, while the system internally maps it to the persisted `chatId` via the `shares` database table.

### Can viewers interact with the sandbox through a shared link?

No. The public endpoints at `/api/shared/:shareId/status` and `/api/shared/:shareId/markdown` never receive the Vercel sandbox ID or any runtime handles. They query only the database tables (`shares`, `chats`, `messages`) via helpers like `getShareByIdCached` and `getMessagesByChatId`. Consequently, viewers cannot execute commands, modify files, or trigger agent actions.

### How does Open Agents prevent secret leakage in shared transcripts?

Before returning the markdown payload, the `redactSharedEnvContent` utility scrubs any environment variable values that may have been printed during the chat session. This redaction occurs in `apps/web/app/api/shared/[shareId]/markdown/route.ts`, ensuring that sensitive credentials or API keys accidentally logged by the agent are not exposed to public viewers.

### What happens if the original chat is deleted?

If the original chat or session is deleted, the share link will return a 404 Not Found error. The public endpoints rely on the foreign key relationships between the `shares`, `chats`, and `messages` tables. When `getShareByIdCached` or `getChatById` cannot resolve the database records, the handlers return `Response.json({ error: "Not found" }, { status: 404 })`, effectively invalidating the public link.