Conversation
AgentDefinition has model on the constraints object, not at the top level. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- postinstall.js: fix TOCTOU race in patchRelayauthCoreExports (existsSync before readFileSync) - start.ts: fix TOCTOU races in ensureProvisioned, fix biased random in generateWorkspaceId, fix signal handler resolving before agent exit code captured - relay.ts: fix biased random in generateWorkspaceId, fix ensureStarted race (client set before wireEvents), wrap persistWorkspaceMapping in try-catch, clear client on error - prereqs.ts: fix unreachable parent-directory fallbacks (path.join always returns string) - services.ts: fix unreachable fallbacks in pickFirstString, fix fd leak in spawnLogged, add detached:true for service processes - on.ts: remove ...overrides spread that overwrites ?? defaults with undefined - validator.ts: fix duplicate "Check 5" comment numbering Co-Authored-By: My Senior Dev <dev@myseniordev.com>
| this.relayApiKey = resolvedKey; | ||
| this.resolvedWorkspaceId = resolvedWorkspaceId; | ||
| this.applyWorkspaceEnv(resolvedWorkspaceId, resolvedKey); | ||
| this.persistWorkspaceMapping(resolvedWorkspaceId, resolvedKey); |
There was a problem hiding this comment.
🔴 SDK ensureRelaycastApiKey writes to disk without error handling, breaking read-only environments
The refactored ensureRelaycastApiKey now calls this.persistWorkspaceMapping() on every code path (relay.ts:1140, relay.ts:1157, relay.ts:1169), which calls writeFileSync to .relay/workspaces.json. None of these calls are wrapped in try/catch. The previous implementation (relay.ts:948-968 old) never wrote files when RELAY_API_KEY was provided via env vars. This is a behavioral regression: users in read-only filesystem environments (Docker containers, CI) who simply set RELAY_API_KEY will now get an uncaught EACCES/EROFS error that prevents the SDK from starting.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Added a second coverage-focused pass on top of the earlier CI/review fixes.\n\nWhat landed:\n- new tests for \n- new tests for \n- new tests for \n\nCoverage improvements from this pass:\n- overall: 58.72%\n- : 98.75%\n- : 90.9%\n- : 100%\n\nEarlier first-pass improvements on this branch are still in place too:\n- : 87.77%\n- : 82.47%\n- : 80.99%\n\nValidation:\n- full
✓ broker binary (agent-relay-broker) built and copied to packages/sdk/bin/ Tasks: 12 successful, 12 total src/cli/commands/on/services.ts(144,9): error TS1005: ',' expected. |
- CRITICAL: Remove hardcoded JWT signing secret, generate random per-workspace secret - HIGH: Pass tokens via env vars instead of CLI arguments (ps-visible) - HIGH: Fix globMatch to handle ** double-star patterns correctly - HIGH: Fix symlink traversal in collectSeedPaths with path boundary check - HIGH: Atomic file permissions (writeFileSync mode:0o600) for token files - HIGH: Add SHA-256 checksum verification for downloaded binaries - HIGH: Use homedir-based PID file path instead of CWD-relative - HIGH: Add error handling for malformed workspace registry JSON - MEDIUM: Write config files with 0o600 permissions (signing secrets, API keys) - MEDIUM: Fix path traversal in syncWritableFilesBack with resolve+prefix check - MEDIUM: Fix signal handler to store cleanup promise for close handler - MEDIUM: Replace deps:any with typed GoOnRelayDeps interface - MEDIUM: Close parent fd copy after spawn in spawnLogged - MEDIUM: Add node_modules to walkProjectFiles exclusion list - MEDIUM: Replace Math.random() with crypto.randomUUID() for JWT jti - MEDIUM: Re-throw auth errors in provision workspace creation - MEDIUM: Fix writeBulkWrite falsy status check with typeof - MEDIUM: Add workspaceName deprecation notice - LOW: Remove npx turbo build side effect from checkPrereqs Co-Authored-By: My Senior Dev <dev@myseniordev.com>
src/cli/commands/on/start.ts
Outdated
| const escaped = pattern | ||
| .replace(/[.+^${}()|[\]\\]/g, '\\$&') | ||
| .replace(/\\\*\\\*/g, '__DOUBLESTAR__') | ||
| .replace(/\\\*/g, '__STAR__') | ||
| .replace(/\\\?/g, '__QMARK__') | ||
| .replace(/__DOUBLESTAR__/g, '.*') | ||
| .replace(/__STAR__/g, '[^/]*') | ||
| .replace(/__QMARK__/g, '[^/]') | ||
| .replace(/__STAR__/g, '\\*') | ||
| .replace(/__QMARK__/g, '\\?'); |
There was a problem hiding this comment.
🔴 globMatch regex escaping omits * and ?, causing SyntaxError on any wildcard pattern
The globMatch function's first regex escape step (/[.+^${}()|[\]\\]/g at line 734) does not include * or ? in its character class. These glob wildcard characters pass through unescaped into the final regex string. For example, a pattern like docs/** produces the regex ^docs/**$, which throws SyntaxError: Nothing to repeat at runtime. This was confirmed by testing:
Reproduction showing the SyntaxError
For input pattern docs/**:
- After escape step:
docs/**(unchanged —*not in escape class) - After placeholder replacements:
docs/**(unchanged —\*\*not found in string) - Resulting regex:
^docs/**$ new RegExp('^docs/**$')→SyntaxError: Nothing to repeat
This function is called by isPathIgnored (start.ts:747-750) which controls permission enforcement in syncWritableFilesBack (start.ts:921-951). When cleanup runs after the agent exits, any readonly/ignored patterns containing wildcards (e.g., docs/**, *.js) will cause the sync-back to crash, potentially losing agent changes or failing to enforce file permissions.
| const escaped = pattern | |
| .replace(/[.+^${}()|[\]\\]/g, '\\$&') | |
| .replace(/\\\*\\\*/g, '__DOUBLESTAR__') | |
| .replace(/\\\*/g, '__STAR__') | |
| .replace(/\\\?/g, '__QMARK__') | |
| .replace(/__DOUBLESTAR__/g, '.*') | |
| .replace(/__STAR__/g, '[^/]*') | |
| .replace(/__QMARK__/g, '[^/]') | |
| .replace(/__STAR__/g, '\\*') | |
| .replace(/__QMARK__/g, '\\?'); | |
| const escaped = pattern | |
| .replace(/[.+^${}()|[\]\\*?]/g, '\\$&') | |
| .replace(/\\\*\\\*/g, '__DOUBLESTAR__') | |
| .replace(/\\\*/g, '__STAR__') | |
| .replace(/\\\?/g, '__QMARK__') | |
| .replace(/__DOUBLESTAR__/g, '.*') | |
| .replace(/__STAR__/g, '[^/]*') | |
| .replace(/__QMARK__/g, '[^/]'); |
Was this helpful? React with 👍 or 👎 to provide feedback.
| private writeWorkspaceRegistry(registry: WorkspaceRegistry): void { | ||
| const registryPath = this.getWorkspaceRegistryPath(); | ||
| mkdirSync(path.dirname(registryPath), { recursive: true }); | ||
| writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`, 'utf8'); |
There was a problem hiding this comment.
🟡 SDK writes workspaces.json with world-readable permissions, exposing API keys
The SDK's writeWorkspaceRegistry at packages/sdk/src/relay.ts:479 writes workspaces.json using writeFileSync(registryPath, ..., 'utf8') which defaults to mode 0o644 (world-readable after umask). This file contains relaycastApiKey values — API credentials that should be protected. The CLI counterpart at src/cli/commands/on/start.ts:238 correctly uses { encoding: 'utf8', mode: 0o600 } to restrict access to the file owner only.
Was this helpful? React with 👍 or 👎 to provide feedback.
| return {}; | ||
| } | ||
|
|
||
| const parsed = JSON.parse(raw) as unknown; |
There was a problem hiding this comment.
🟡 readWorkspaceRegistry in start.ts crashes on corrupted JSON instead of returning empty registry
In start.ts:222, JSON.parse(raw) is called without a try-catch, so a corrupted workspaces.json file (e.g., from a prior crash during write) will throw an unhandled SyntaxError and crash the agent-relay on command. The SDK's equivalent at packages/sdk/src/relay.ts:455-462 correctly wraps the parse in try-catch and returns an empty registry on failure, allowing the caller to re-create the file.
| const parsed = JSON.parse(raw) as unknown; | |
| let parsed: unknown; | |
| try { | |
| parsed = JSON.parse(raw) as unknown; | |
| } catch { | |
| return {}; | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
Resolved package.json version conflict (bumped to 3.2.22) and fixed misplaced parenthesis in resolveServiceConfig that caused TS1005/TS1128 compile errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
start.ts: CRITICAL - reject empty JWT signing_secret with clear error start.ts: HIGH - remove dead globMatch .replace() calls start.ts: MEDIUM - harden syncWritableFilesBack with realpathSync for symlink traversal start.ts: MEDIUM - add sandbox bypass warning log postinstall.js: HIGH - validate needle count before patching, backup before write relay.ts: MEDIUM - deprecation warning for workspaceName without workspaceId Co-Authored-By: My Senior Dev <dev@myseniordev.com>
Summary
Adds
agent-relay on <cli>— launch any agent in a permission-enforced relay workspace.agent-relay on codex— boots relayauth + relayfile, provisions scoped tokens, syncs files, launches codex with--dangerously-bypass-approvals-and-sandboxagent-relay off— stops services, cleans up mountsagent-relay on --scan— preview what the agent will seeagent-relay on --doctor— check prerequisitesHow it works
.agentignore/.agentreadonlyfrom project root (zero config)fs:read/fs:write).relay/,.git/,node_modules/)relayfile-mountwith permission enforcement:New files
Auto sandbox flags
--dangerously-bypass-approvals-and-sandbox--dangerously-skip-permissions--yolo--yesTest plan
agent-relay on --doctorchecks prerequisitesagent-relay on --scanshows permission summaryagent-relay on --workspace rw_xxxjoin flow (pending shareable workspaces)🤖 Generated with Claude Code