Files
Gabriel Brown c1263b2e69 Worker: interactive terminal WebSocket bridge (PTY in workspace container)
- attachTerminalServer() upgrades /jobs/:id/terminal WS connections, verifying a
  short-lived job-scoped HMAC token (verifyTerminalToken) so the browser never
  holds the worker secret
- Bridges the socket to a bash PTY via dockerode exec (Tty) in a persistent
  per-job shell container (spoon-agent-term-<id>) mounting the workspace; binary
  frames = stdin, JSON text frames = resize; idle containers reaped after 30m
- New env: SPOON_AGENT_TERMINAL_IMAGE/SECRET/IDLE_MS (secret falls back to the
  shared worker internal token)
2026-06-24 08:16:39 -04:00

43 lines
1.5 KiB
TypeScript

import { createHmac } from 'node:crypto';
import { describe, expect, test } from 'vitest';
import { verifyTerminalToken } from '../../src/terminal-token';
const mint = (jobId: string, expiresAt: number, secret: string) => {
const payload = `${expiresAt}.${jobId}`;
const sig = createHmac('sha256', secret).update(payload).digest('hex');
return `${payload}.${sig}`;
};
describe('verifyTerminalToken', () => {
const secret = 'test-secret';
test('accepts a valid, unexpired, job-matched token', () => {
const token = mint('job1', Date.now() + 60_000, secret);
expect(verifyTerminalToken(token, 'job1', secret)).toBe(true);
});
test('rejects an expired token', () => {
const token = mint('job1', Date.now() - 1, secret);
expect(verifyTerminalToken(token, 'job1', secret)).toBe(false);
});
test('rejects a token minted for another job', () => {
const token = mint('job1', Date.now() + 60_000, secret);
expect(verifyTerminalToken(token, 'job2', secret)).toBe(false);
});
test('rejects a token signed with a different secret', () => {
const token = mint('job1', Date.now() + 60_000, 'other-secret');
expect(verifyTerminalToken(token, 'job1', secret)).toBe(false);
});
test('rejects malformed input and an empty secret', () => {
expect(verifyTerminalToken('garbage', 'job1', secret)).toBe(false);
expect(verifyTerminalToken('', 'job1', secret)).toBe(false);
expect(
verifyTerminalToken(mint('job1', Date.now() + 1000, ''), 'job1', ''),
).toBe(false);
});
});