Skip to content

On the relay#662

Open
khaliqgant wants to merge 14 commits intomainfrom
on-the-relay
Open

On the relay#662
khaliqgant wants to merge 14 commits intomainfrom
on-the-relay

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Mar 27, 2026

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-sandbox
  • agent-relay off — stops services, cleans up mounts
  • agent-relay on --scan — preview what the agent will see
  • agent-relay on --doctor — check prerequisites

How it works

  1. Reads .agentignore / .agentreadonly from project root (zero config)
  2. Compiles dotfiles into per-file scopes and ACL rules
  3. Mints scoped JWT tokens (no blanket fs:read/fs:write)
  4. Seeds project files into relayfile workspace (excludes .relay/, .git/, node_modules/)
  5. Syncs workspace via relayfile-mount with permission enforcement:
    • Ignored files never appear locally
    • Readonly files are chmod 444
    • Writes to denied files are reverted via fsnotify
  6. Launches agent with sandbox bypass flag (relay IS the sandbox)
  7. On exit: syncs changes back, cleans up workspace

New files

src/cli/commands/on.ts              — Commander.js command registration (on/off)
src/cli/commands/on/start.ts        — Main "agent-relay on" flow
src/cli/commands/on/stop.ts         — "agent-relay off" flow
src/cli/commands/on/dotfiles.ts     — .agentignore/.agentreadonly parser + compiler
src/cli/commands/on/token.ts        — JWT signing (HS256, no shell subprocess)
src/cli/commands/on/workspace.ts    — Relayfile workspace creation + seeding + ACL seeding
src/cli/commands/on/provision.ts    — Orchestrates token + workspace + ACLs
src/cli/commands/on/services.ts     — Start/stop relayauth + relayfile locally
src/cli/commands/on/prereqs.ts      — Check node, go, wrangler, relayfile binary
src/cli/commands/on/scan.ts         — Preview permissions without launching

Auto sandbox flags

CLI Flag applied
codex --dangerously-bypass-approvals-and-sandbox
claude --dangerously-skip-permissions
gemini --yolo
aider --yes

Test plan

  • agent-relay on --doctor checks prerequisites
  • agent-relay on --scan shows permission summary
  • Codex tested end-to-end: secrets/ invisible, README.md read-only, src/ writable
  • Claude tested end-to-end
  • agent-relay on --workspace rw_xxx join flow (pending shareable workspaces)

🤖 Generated with Claude Code


Open with Devin

khaliqgant and others added 2 commits March 27, 2026 14:58
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>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

- 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>
devin-ai-integration[bot]

This comment was marked as resolved.

@miyaontherelay
Copy link
Copy Markdown
Contributor

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

agent-relay@3.2.21 pretest:coverage
npm run build

agent-relay@3.2.21 build
npm run clean && npm run build:rust && turbo run build --filter='./packages/*' && tsc

agent-relay@3.2.21 clean
rm -rf dist && find packages -maxdepth 2 -name dist -type d -exec rm -rf {} + 2>/dev/null || true

agent-relay@3.2.21 build:rust
if command -v ~/.cargo/bin/cargo >/dev/null 2>&1; then ~/.cargo/bin/cargo build --release --bin agent-relay-broker && mkdir -p packages/sdk/bin && cp target/release/agent-relay-broker packages/sdk/bin/agent-relay-broker.new && mv -f packages/sdk/bin/agent-relay-broker.new packages/sdk/bin/agent-relay-broker && echo '✓ broker binary (agent-relay-broker) built and copied to packages/sdk/bin/'; else echo '⚠ Rust not installed, using prebuilt binaries from bin/'; fi

✓ broker binary (agent-relay-broker) built and copied to packages/sdk/bin/
• Packages in scope: @agent-relay/acp-bridge, @agent-relay/brand, @agent-relay/cloud, @agent-relay/config, @agent-relay/hooks, @agent-relay/memory, @agent-relay/openclaw, @agent-relay/policy, @agent-relay/sdk, @agent-relay/telemetry, @agent-relay/trajectory, @agent-relay/user-directory, @agent-relay/utils
• Running build in 13 packages
• Remote caching disabled
@agent-relay/config:build: cache miss, executing 3a117769928a3a94
@agent-relay/telemetry:build: cache miss, executing 0c0a7f976c32cd32
@agent-relay/telemetry:build:
@agent-relay/config:build:
@agent-relay/telemetry:build: > @agent-relay/telemetry@3.2.21 build
@agent-relay/config:build: > @agent-relay/config@3.2.21 build
@agent-relay/telemetry:build: > tsc
@agent-relay/telemetry:build:
@agent-relay/config:build: > tsc
@agent-relay/config:build:
@agent-relay/trajectory:build: cache miss, executing 6b29fee74c8fa352
@agent-relay/cloud:build: cache miss, executing 95243399640fb01f
@agent-relay/sdk:build: cache miss, executing 793158e92e81e49c
@agent-relay/utils:build: cache miss, executing 8c3bbffa5f729796
@agent-relay/policy:build: cache miss, executing db41b63826d8bfa7
@agent-relay/trajectory:build:
@agent-relay/trajectory:build: > @agent-relay/trajectory@3.2.21 build
@agent-relay/trajectory:build: > tsc
@agent-relay/trajectory:build:
@agent-relay/policy:build:
@agent-relay/policy:build: > @agent-relay/policy@3.2.21 build
@agent-relay/policy:build: > tsc
@agent-relay/policy:build:
@agent-relay/cloud:build:
@agent-relay/cloud:build: > @agent-relay/cloud@3.2.21 build
@agent-relay/cloud:build: > tsc
@agent-relay/cloud:build:
@agent-relay/sdk:build:
@agent-relay/sdk:build: > @agent-relay/sdk@3.2.21 prebuild
@agent-relay/sdk:build: > npm --prefix ../config run build
@agent-relay/sdk:build:
@agent-relay/utils:build:
@agent-relay/utils:build: > @agent-relay/utils@3.2.21 build
@agent-relay/utils:build: > npm run clean && npm run build:esm && npm run build:cjs
@agent-relay/utils:build:
@agent-relay/sdk:build:
@agent-relay/sdk:build: > @agent-relay/config@3.2.21 build
@agent-relay/sdk:build: > tsc
@agent-relay/sdk:build:
@agent-relay/utils:build:
@agent-relay/utils:build: > @agent-relay/utils@3.2.21 clean
@agent-relay/utils:build: > rm -rf dist
@agent-relay/utils:build:
@agent-relay/utils:build:
@agent-relay/utils:build: > @agent-relay/utils@3.2.21 build:esm
@agent-relay/utils:build: > tsc
@agent-relay/utils:build:
@agent-relay/sdk:build:
@agent-relay/sdk:build: > @agent-relay/sdk@3.2.21 build
@agent-relay/sdk:build: > tsc -p tsconfig.build.json
@agent-relay/sdk:build:
@agent-relay/utils:build:
@agent-relay/utils:build: > @agent-relay/utils@3.2.21 build:cjs
@agent-relay/utils:build: > node ./scripts/build-cjs.mjs
@agent-relay/utils:build:
@agent-relay/utils:build:
@agent-relay/utils:build: dist/cjs/discovery.js 11.3kb
@agent-relay/utils:build: dist/cjs/precompiled-patterns.js 10.3kb
@agent-relay/utils:build: dist/cjs/relay-pty-path.js 9.3kb
@agent-relay/utils:build: dist/cjs/update-checker.js 6.3kb
@agent-relay/utils:build: dist/cjs/client-helpers.js 4.4kb
@agent-relay/utils:build: dist/cjs/git-remote.js 4.0kb
@agent-relay/utils:build: dist/cjs/logger.js 3.9kb
@agent-relay/utils:build: dist/cjs/error-tracking.js 3.5kb
@agent-relay/utils:build: dist/cjs/model-commands.js 3.4kb
@agent-relay/utils:build: dist/cjs/name-generator.js 3.2kb
@agent-relay/utils:build: dist/cjs/command-resolver.js 3.2kb
@agent-relay/utils:build: dist/cjs/legacy-protocol.js 2.5kb
@agent-relay/utils:build: dist/cjs/errors.js 2.5kb
@agent-relay/utils:build: dist/cjs/index.js 2.1kb
@agent-relay/utils:build: dist/cjs/model-mapping.js 1.7kb
@agent-relay/utils:build:
@agent-relay/utils:build: ⚡ Done in 6ms
@agent-relay/utils:build:
@agent-relay/utils:build: > @agent-relay/utils@3.2.21 postbuild
@agent-relay/utils:build: > npm run build:cjs
@agent-relay/utils:build:
@agent-relay/utils:build:
@agent-relay/utils:build: > @agent-relay/utils@3.2.21 build:cjs
@agent-relay/utils:build: > node ./scripts/build-cjs.mjs
@agent-relay/utils:build:
@agent-relay/utils:build:
@agent-relay/utils:build: dist/cjs/discovery.js 11.3kb
@agent-relay/utils:build: dist/cjs/precompiled-patterns.js 10.3kb
@agent-relay/utils:build: dist/cjs/relay-pty-path.js 9.3kb
@agent-relay/utils:build: dist/cjs/update-checker.js 6.3kb
@agent-relay/utils:build: dist/cjs/client-helpers.js 4.4kb
@agent-relay/utils:build: dist/cjs/git-remote.js 4.0kb
@agent-relay/utils:build: dist/cjs/logger.js 3.9kb
@agent-relay/utils:build: dist/cjs/error-tracking.js 3.5kb
@agent-relay/utils:build: dist/cjs/model-commands.js 3.4kb
@agent-relay/utils:build: dist/cjs/name-generator.js 3.2kb
@agent-relay/utils:build: dist/cjs/command-resolver.js 3.2kb
@agent-relay/utils:build: dist/cjs/legacy-protocol.js 2.5kb
@agent-relay/utils:build: dist/cjs/errors.js 2.5kb
@agent-relay/utils:build: dist/cjs/index.js 2.1kb
@agent-relay/utils:build: dist/cjs/model-mapping.js 1.7kb
@agent-relay/utils:build:
@agent-relay/utils:build: ⚡ Done in 5ms
@agent-relay/user-directory:build: cache miss, executing 321c11764e122558
@agent-relay/user-directory:build:
@agent-relay/user-directory:build: > @agent-relay/user-directory@3.2.21 build
@agent-relay/user-directory:build: > tsc
@agent-relay/user-directory:build:
@agent-relay/acp-bridge:build: cache miss, executing 722c86a457f24337
@agent-relay/hooks:build: cache miss, executing dab166194b943cef
@agent-relay/openclaw:build: cache miss, executing fd0ae6bde08a445d
@agent-relay/acp-bridge:build:
@agent-relay/acp-bridge:build: > @agent-relay/acp-bridge@3.2.21 build
@agent-relay/acp-bridge:build: > tsc
@agent-relay/acp-bridge:build:
@agent-relay/hooks:build:
@agent-relay/hooks:build: > @agent-relay/hooks@3.2.21 build
@agent-relay/hooks:build: > npm run clean && tsc
@agent-relay/hooks:build:
@agent-relay/openclaw:build:
@agent-relay/openclaw:build: > @agent-relay/openclaw@3.2.21 build
@agent-relay/openclaw:build: > tsc
@agent-relay/openclaw:build:
@agent-relay/hooks:build:
@agent-relay/hooks:build: > @agent-relay/hooks@3.2.21 clean
@agent-relay/hooks:build: > rm -rf dist
@agent-relay/hooks:build:
@agent-relay/memory:build: cache miss, executing 0a08437bad9ec359
@agent-relay/memory:build:
@agent-relay/memory:build: > @agent-relay/memory@3.2.21 build
@agent-relay/memory:build: > tsc
@agent-relay/memory:build:

Tasks: 12 successful, 12 total
Cached: 0 cached, 12 total
Time: 4.142s

src/cli/commands/on/services.ts(144,9): error TS1005: ',' expected.
src/cli/commands/on/services.ts(144,14): error TS1005: ',' expected.
src/cli/commands/on/services.ts(145,3): error TS1128: Declaration or statement expected. passed locally after the new tests\n- repo total coverage remains green at 63.29%\n\nLatest commit from this pass: ()

- 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>
try {
const checksumContent = await new Promise((resolve, reject) => {
const chunks = [];
const request = https.get(checksumUrl, res => {

Check warning

Code scanning / CodeQL

File data in outbound network request Medium

Outbound network request depends on
file data
.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

View 15 additional findings in Devin Review.

Open in Devin Review

Comment on lines +733 to +742
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, '\\?');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Suggested change
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, '[^/]');
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

khaliqgant and others added 5 commits March 27, 2026 23:41
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>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 25 additional findings in Devin Review.

Open in Devin Review

Comment on lines +179 to +192
const checksumContent = await new Promise((resolve, reject) => {
const chunks = [];
const request = https.get(checksumUrl, res => {
if (res.statusCode !== 200) {
res.resume();
reject(new Error(`Checksums file not available (HTTP ${res.statusCode})`));
return;
}
res.on('data', chunk => chunks.push(chunk));
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
res.on('error', reject);
});
request.on('error', reject);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 verifyChecksum does not follow HTTP redirects, making checksum verification always skip for GitHub releases

The new verifyChecksum function uses a bare https.get to download the checksums file, but unlike downloadBinary (which has redirect-following logic), it rejects any non-200 status code — including 3xx redirects. GitHub release asset URLs always return a 302 redirect to a CDN. This means verifyChecksum will always receive a 302, reject with "Checksums file not available (HTTP 302)", and the outer catch block will log a warning but return false, silently skipping verification. The checksum verification feature added by this PR is effectively non-functional for all GitHub-hosted binaries (broker, dashboard-server, relay-acp).

Prompt for agents
In scripts/postinstall.js, the verifyChecksum function (lines 174-225) uses a simple https.get that does not follow HTTP redirects. GitHub release URLs always return 302 redirects, so the checksums file download always fails silently. Fix this by adding redirect-following logic similar to the existing downloadBinary function (lines 231-281). The simplest approach: extract the redirect-following logic from downloadBinary's attemptDownload into a shared helper, or inline redirect handling in verifyChecksum's https.get callback. The key change is: when res.statusCode is 3xx and res.headers.location exists, follow the redirect URL (up to a max of e.g. 5 redirects) instead of rejecting.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants