How Read-Only Session Sharing Works in Open Agents Sandboxes
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:
// 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:
// 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:
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:
// 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, andgetMessagesByChatIdfromapps/web/lib/db/sessions.tsandapps/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
redactSharedEnvContentbefore 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 |
CRUD operations for shares, chats, and messages. |
| Cached queries | 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 asharerow referencing the chat. - Anonymous consumption uses
GET /api/shared/:shareId/statusfor streaming state andGET /api/shared/:shareId/markdownfor 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →