@axlsdk/studio 0.20.1 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,7 +68,9 @@ Your application imports from `src/runtime.ts` directly. Studio discovers everyt
68
68
  npx @axlsdk/studio --open
69
69
  ```
70
70
 
71
- This loads your config, starts the server on `http://localhost:4400`, and opens the browser.
71
+ This loads your config and starts the server on `http://127.0.0.1:4400`. The
72
+ standalone CLI binds only to IPv4 loopback, emits no CORS headers, and rejects
73
+ non-local HTTP hosts and browser origins across both REST and WebSocket routes.
72
74
 
73
75
  ## CLI Options
74
76
 
@@ -77,12 +79,24 @@ axl-studio [options]
77
79
 
78
80
  Options:
79
81
  --port <number> Server port (default: 4400)
82
+ --dangerously-bind <ip> Bind for a loopback-published container/local-forward tunnel
80
83
  --config <path> Path to config file (default: auto-detect)
81
84
  --conditions <list> Comma-separated Node.js import conditions (e.g., development)
85
+ --read-only Disable mutating endpoints
82
86
  --open Auto-open browser
83
87
  -h, --help Show help
84
88
  ```
85
89
 
90
+ For a container or devcontainer, bind the published host port to loopback and
91
+ make only the container listener broad:
92
+
93
+ ```bash
94
+ docker run -p 127.0.0.1:4400:4400 ... axl-studio --dangerously-bind 0.0.0.0
95
+ ```
96
+
97
+ The Host/Origin guard remains active. Do not publish that port on a public or
98
+ LAN interface; standalone Studio has no user authentication.
99
+
86
100
  The `--conditions` flag is useful in monorepos where workspace packages use the `"development"` export condition to point at source instead of built dist files. Pass `--conditions development` to resolve imports through source paths.
87
101
 
88
102
  **Note:** `--conditions` only applies to ESM `import()` resolution. Transitive `require()` calls from packages without `"type": "module"` bypass the hook and use default conditions. If a dependency loads via CJS, it will still resolve from `dist/` regardless of the flag.
@@ -179,24 +193,32 @@ const studio = createStudioMiddleware({
179
193
  runtime,
180
194
  basePath: '/studio',
181
195
  // WebSocket upgrades bypass Express middleware — always authenticate here.
182
- verifyUpgrade: (req) => {
183
- const url = new URL(req.url!, `http://${req.headers.host}`);
184
- return url.searchParams.get('token') === process.env.MY_SECRET;
185
- },
196
+ verifyUpgrade: (req) => authenticateStudioRequest(req)?.isAdmin === true,
186
197
  });
187
198
 
188
199
  const app = express();
189
- app.use('/studio', studio.handler);
200
+ const authenticateStudioHttp: express.RequestHandler = (req, res, next) => {
201
+ if (authenticateStudioRequest(req)?.isAdmin !== true) return res.sendStatus(403);
202
+ next();
203
+ };
204
+ app.use('/studio', authenticateStudioHttp, studio.handler);
190
205
  const server = app.listen(3000);
191
206
  studio.upgradeWebSocket(server); // required for live data
192
207
  ```
193
208
 
194
- Key options: `readOnly` (disable mutating endpoints for production monitoring), `evals` (lazy-load eval files), `filterTraceEvent` (per-tenant broadcast scoping), `bufferCaps` (WS replay-buffer limits). `basePath` must match your framework's mount path.
209
+ Key options: `readOnly` (disable mutating endpoints for production monitoring), `evals` (lazy-load eval files), `filterTraceEvent` (per-tenant broadcast scoping), `bufferCaps` (WS replay-buffer limits). `basePath` must match your framework's mount path. In production, `upgradeWebSocket()` refuses to attach without `verifyUpgrade`; `dangerouslyAllowUnauthenticatedWebSockets` is the explicit escape hatch only when an outer upgrade gate is independently guaranteed.
195
210
 
196
211
  **See [`docs/studio-api.md`](../../docs/studio-api.md) for the full reference:** every REST endpoint, the WebSocket protocol, the complete middleware options/return tables, NestJS/Fastify/Hono examples, host body-limit guidance, lazy eval loading, multi-tenant setup, and the internal architecture.
197
212
 
198
213
  ## Security
199
214
 
215
+ - **Treat Studio as an administrative surface, never as a public application
216
+ API.** Agent routes intentionally expose resolved system prompts, tool
217
+ descriptions, schemas, and runtime configuration. `trace.redact` protects
218
+ user/model observability content; it is not authentication and does not hide
219
+ static agent configuration.
220
+ - Protect the Studio HTTP mount with your framework's authentication and
221
+ authorization middleware.
200
222
  - **Always** provide `verifyUpgrade` — WebSocket upgrades bypass Express/Fastify/Koa middleware, so your auth middleware does **not** protect WebSocket connections.
201
223
  - Consider `readOnly: true` for production monitoring — view traces, costs, and schemas without execution capability.
202
224
  - CORS is not applied in embedded mode — the host framework owns CORS policy.
@@ -1363,7 +1363,10 @@ function createSessionRoutes(connMgr) {
1363
1363
  const body = await c.req.json();
1364
1364
  const session = runtime.session(id);
1365
1365
  const result = await session.send(body.workflow, body.message);
1366
- return c.json({ ok: true, data: { result } });
1366
+ return c.json({
1367
+ ok: true,
1368
+ data: { result: redactValue(result, runtime.isRedactEnabled()) }
1369
+ });
1367
1370
  });
1368
1371
  app5.post("/sessions/:id/stream", async (c) => {
1369
1372
  const runtime = c.get("runtime");
@@ -1372,9 +1375,13 @@ function createSessionRoutes(connMgr) {
1372
1375
  const session = runtime.session(id);
1373
1376
  const stream = await session.stream(body.workflow, body.message);
1374
1377
  const executionId = `session-${id}-${Date.now()}`;
1378
+ const redactOn = runtime.isRedactEnabled();
1375
1379
  (async () => {
1376
1380
  for await (const event of stream) {
1377
- connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
1381
+ connMgr.broadcastWithWildcard(
1382
+ `execution:${executionId}`,
1383
+ redactStreamEvent(event, redactOn)
1384
+ );
1378
1385
  }
1379
1386
  })();
1380
1387
  return c.json({ ok: true, data: { executionId, streaming: true } });
@@ -16080,7 +16087,7 @@ function createServer(options) {
16080
16087
  emptyState: emptyEvalTrendData,
16081
16088
  windows
16082
16089
  });
16083
- if (options.cors !== false) {
16090
+ if (options.cors === true) {
16084
16091
  app5.use("*", cors());
16085
16092
  }
16086
16093
  app5.onError(errorHandler);
@@ -16239,4 +16246,4 @@ export {
16239
16246
  EvalAggregator,
16240
16247
  createServer
16241
16248
  };
16242
- //# sourceMappingURL=chunk-KQ3HBRTC.js.map
16249
+ //# sourceMappingURL=chunk-YGLWYDQ3.js.map