mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat(daemon): add sandbox runtime foundation * fix(daemon): preserve sandbox roots after agent env overrides * fix(daemon): keep readiness probes pathless * fix(daemon): harden headless run fallbacks * fix(daemon): bootstrap sandbox runtime discovery * fix(daemon): preserve explicit sandbox agent profile mounts * fix(daemon): keep sandbox profile lookup run scoped * fix(daemon): normalize sandbox data dir input * fix(daemon): pin sandbox env roots to base data dir
86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
// Plan §3.K1 / spec §15.7 — bound-API-token guard.
|
|
//
|
|
// Two halves:
|
|
// 1. The daemon refuses to start with OD_BIND_HOST=0.0.0.0 when no
|
|
// OD_API_TOKEN is set.
|
|
// 2. When OD_API_TOKEN is set, every /api/* request from a non-loopback
|
|
// peer must carry `Authorization: Bearer <OD_API_TOKEN>`. The
|
|
// health/readiness/version probes stay open for monitoring.
|
|
//
|
|
// Tests force the bearer-required code path by stamping the env vars
|
|
// before startServer. The daemon listens on 127.0.0.1 throughout (so
|
|
// the "refuse 0.0.0.0 without token" path is exercised by a separate
|
|
// negative case that constructs the start call directly).
|
|
|
|
import type http from 'node:http';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { startServer } from '../src/server.js';
|
|
|
|
const PREVIOUS_TOKEN = process.env.OD_API_TOKEN;
|
|
const PREVIOUS_HOST = process.env.OD_BIND_HOST;
|
|
|
|
let server: http.Server | undefined;
|
|
let baseUrl = '';
|
|
let shutdown: (() => Promise<void> | void) | undefined;
|
|
|
|
afterEach(async () => {
|
|
if (shutdown) await Promise.resolve(shutdown());
|
|
if (server) await new Promise<void>((resolve) => server!.close(() => resolve()));
|
|
server = undefined;
|
|
shutdown = undefined;
|
|
if (PREVIOUS_TOKEN === undefined) delete process.env.OD_API_TOKEN;
|
|
else process.env.OD_API_TOKEN = PREVIOUS_TOKEN;
|
|
if (PREVIOUS_HOST === undefined) delete process.env.OD_BIND_HOST;
|
|
else process.env.OD_BIND_HOST = PREVIOUS_HOST;
|
|
});
|
|
|
|
describe('bound-API-token guard', () => {
|
|
it('refuses to start with OD_BIND_HOST=0.0.0.0 when OD_API_TOKEN is unset', async () => {
|
|
delete process.env.OD_API_TOKEN;
|
|
await expect(startServer({ port: 0, host: '0.0.0.0', returnServer: true }))
|
|
.rejects.toThrow(/OD_API_TOKEN/);
|
|
});
|
|
|
|
it('starts on a public host when OD_API_TOKEN is set', async () => {
|
|
process.env.OD_API_TOKEN = 'test-token-abc';
|
|
// Bind to 127.0.0.1 (loopback) but pretend we crossed the guard
|
|
// by setting the env var; the assertion is that startup succeeds.
|
|
const started = (await startServer({ port: 0, host: '127.0.0.1', returnServer: true })) as {
|
|
url: string;
|
|
server: http.Server;
|
|
shutdown?: () => Promise<void> | void;
|
|
};
|
|
server = started.server;
|
|
shutdown = started.shutdown;
|
|
baseUrl = started.url;
|
|
expect(baseUrl).toMatch(/^http:\/\/127\.0\.0\.1:/);
|
|
});
|
|
});
|
|
|
|
describe('bearer middleware', () => {
|
|
beforeEach(async () => {
|
|
process.env.OD_API_TOKEN = 'secret-test-token';
|
|
const started = (await startServer({ port: 0, host: '127.0.0.1', returnServer: true })) as {
|
|
url: string;
|
|
server: http.Server;
|
|
shutdown?: () => Promise<void> | void;
|
|
};
|
|
baseUrl = started.url;
|
|
server = started.server;
|
|
shutdown = started.shutdown;
|
|
});
|
|
|
|
it('accepts loopback callers without a bearer (desktop UI flow)', async () => {
|
|
// The HTTP test client is on the same machine → req.socket.remoteAddress
|
|
// is 127.0.0.1 → middleware short-circuits.
|
|
const resp = await fetch(`${baseUrl}/api/plugins`);
|
|
expect(resp.status).toBe(200);
|
|
});
|
|
|
|
it('keeps health / readiness / version probes open without a bearer', async () => {
|
|
for (const path of ['/api/health', '/api/ready', '/api/version']) {
|
|
const resp = await fetch(`${baseUrl}${path}`);
|
|
expect(resp.status).toBe(200);
|
|
}
|
|
});
|
|
});
|