bridge

agent
Security Audit
Fail
Health Warn
  • No license — Repository has no license file
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 10 GitHub stars
Code Fail
  • rm -rf — Recursive force deletion command in install.sh
  • network request — Outbound network request in install.sh
  • rm -rf — Recursive force deletion command in test/pty_teardown_probe.sh
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

TODOforAI Bridge — native C edge agent (PTY relay over Noise-encrypted WebSocket)

README.md

TODOforAI Bridge

Native bridge runtime. Connects user machines, Firecracker sandboxes, and
cloud VMs to the TODOforAI backend over an encrypted WebSocket channel.

The same binary runs everywhere. Location is a deployment detail; the
protocol is uniform.

Design principle

Bridges are islands by default. One guaranteed capability: "accept
commands from the backend over WebSocket and execute them." Everything
else — SSH, port exposure, overlay networking, language runtimes — is
configured by the AI at the user's request by running commands through
this same channel.

Wire protocol

TCP → WebSocket → Noise_NX_25519_ChaChaPoly_BLAKE2b → JSON

No TLS. No OpenSSL. All crypto handled by Noise (monocypher + blake2b,
vendored in noise/).

After the Noise handshake, each binary WS frame carries one encrypted
JSON message. First encrypted message from edge is auth:

{"type":"auth","deviceId":"dev_...","secret":"..."}

Device credentials are provisioned via todoforai-bridge login (stored on disk by
the shared c-core login helper). Then the v2 multi-session protocol:

  • identity (→ server, once)
  • exec / input / resize / signal / kill (← server)
  • output / exit / error (→ server)

Server side: backend/src/api/ws/handlers/BridgeHandler.ts.

Layout

File Purpose
main.c Event loop, session table, command dispatch
subcmd.c / .h CLI subcommands: login / enroll / whoami
conn.c / conn.h TCP + WS client handshake + Noise_NX initiator
util.c / util.h Base64 + SHA-1 (for WS-Accept)
pty.c / pty.h forkpty session: read/write/resize/signal
identity.c / .h Host identity gathering (uname, pwd, cwd)
tools.c / .h Probe installed CLI tools via scan_tools function-call
policy.c / .h Device policy: workspace confinement + Landlock jail

| ws.c / ws.h | RFC 6455 WebSocket client (sync connect, poll loop) |
| json.c / json.h | Minimal JSON parser/writer + base64 |
| noise/ | Vendored noise.c + monocypher (BLAKE2b) |

Build

Only libc + libutil (for forkpty, in libc on macOS).

# Dynamic build (default system cc) — ~77 KB stripped, libc only
make
./build/todoforai-bridge --help

# Static musl build via `zig cc` — ~90 KB, single-file, zero deps
make static

# Windows x64 build via `zig cc` (mingw-w64) — ~150 KB
make release-windows-x64

Run

# First time: provision device credentials (opens browser / device flow)
./build/todoforai-bridge login [--device-name NAME]

# Then connect — defaults to api.todofor.ai:80 (Noise is end-to-end;
# no TLS on the wire — typically Cloudflare/nginx terminates 443 in front)
./build/todoforai-bridge

# Custom server
./build/todoforai-bridge --host 127.0.0.1 --port 4000

# Local-dev: point at a backend on a different host/port
# (Noise TCP RPC port — defaults to api.todofor.ai:4100; for `bun run dev` use 14100)
./build/todoforai-bridge login  --host 127.0.0.1 --port 14100
./build/todoforai-bridge enroll --host 127.0.0.1 --port 14100

# Firecracker sandbox: presence of enroll.token=... in /proc/cmdline
# routes to DeviceType.SANDBOX path (?deviceType=SANDBOX).

# Show version / help
./build/todoforai-bridge --version
./build/todoforai-bridge --help

The backend's Noise static public key (the trust anchor for the encrypted
channel) is learned during login via the Noise_NX handshake and persisted
to credentials.json as backendPubkey. All later connections (daemon run,
enroll) pin against it. No flag, no env, no hardcoded default — same flow in
dev, prod, and self-hosted. If the server's identity changes (key rotation,
new deployment), login again to re-learn.

Environment variables

CLI flags take precedence; env vars are fallbacks for non-interactive
deployments (sandbox init, systemd units, CI).

Variable Used by Equivalent flag Purpose
NOISE_BACKEND_HOST run, login, enroll --host Backend hostname (default api.todofor.ai)
NOISE_BACKEND_PORT login, enroll --port Noise TCP RPC port (default 4100, dev 14100)
BRIDGE_PORT run --port Bridge HTTP/WS port (default 80, dev 4000)

Updates

The bridge has no HTTP/download logic of its own. Updates ride on the
existing RUN channel (see DeviceService.updateBridge in the backend):
the server sends a shell command that re-runs the sha256-verified
installer over the running binary (rename() over a running binary is
fine on POSIX) with --service — so a systemd/launchd supervisor is
ensured; the installer exits nonzero when none is available, stopping
the chain before the kill — then kills the bridge; the supervisor
relaunches it on the new binary.

Command shape (authoritative version lives in DeviceService.updateBridge) —
note $PPID (the bridge) is the reliable way to find the executable;
inside the RUN shell, $0 is the shell itself:

exe=$(readlink -f /proc/$PPID/exe 2>/dev/null || readlink -f "$(command -v todoforai-bridge)" 2>/dev/null || command -v todoforai-bridge) \
  && [ -x "$exe" ] \
  && i=$(mktemp) && curl -fsSL https://todofor.ai/bridge -o "$i" \
  && sh "$i" --prefix "$(dirname "$exe")" --service \
  && kill $PPID

No new protocol messages, no in-binary HTTP client, no extra dependencies.

Skills (SKILL.md)

The TODOforAI agent discovers local skills on a bridge the same way it does on
an edge — by walking well-known locations and reading SKILL.md files. Both
.agents/skills and .claude/skills are scanned, per scope:

Scope Path Source
repo <workspace-root>/{.agents,.claude}/skills/** one per workspace root
user $HOME/{.agents,.claude}/skills/** resolved on the bridge device

Discovery rules (mirror edge/bun/src/skills.ts):

  • Priority order: repo .agents → repo .claude → user; skills are deduped by
    name, first match wins.
  • Walk depth: find -maxdepth 6; hidden entries (.*) are pruned.
  • Only files literally named SKILL.md are accepted.
  • Only the first 8 KiB of each file is read for metadata.
  • Frontmatter (YAML between leading --- lines) provides:
    • name — skill identifier (falls back to parent directory name)
    • description — full description
    • metadata.short-description — optional shorter line preferred in the prompt

On a bridge there's no get_skills RPC: the agent runs find + head over
the same machine_exec channel used for shell tools, base64-encoding paths and
file heads so the wire stays ASCII-clean. The full body is fetched on demand via
the Skill tool, which reads the file via base64 < <path> like ReadTool.

Required tools on the bridge: sh, find, head, base64. Standard on
GNU/BSD/macOS userspace; no extra installation needed.

Notes

  • POSIX + Windows (ConPTY, Win10 1809+). Windows build via
    make release-windows-x64 (zig cc + mingw-w64). On Windows the bridge
    spawns a POSIX shell inside ConPTY — set BRIDGE_SHELL to override,
    otherwise it prefers Git for Windows (bash.exe or sh.exe, the same msys2
    binary, so an install whose bash.exe was renamed still works), then a PATH
    bash.exe/sh.exe (skipping the System32 WSL launcher, which isn't a
    usable POSIX shell here), then a pinned busybox-w32 it provisions itself
    into %USERPROFILE%\.todoforai\shell\sh.exe, falling back to cmd.exe
    (RUN/tool catalog assume bash semantics — install Git for Windows).
    step_paused works on Linux, macOS, and Windows; on Windows the
    passwordPrompt flag is always 0 (the child's ECHO state isn't exposed
    through the ConPTY API).
  • Session cap defaults to 256 concurrent PTYs. When full, RUNs evict the
    least-recently-used idle session (running sessions are never evicted).
  • WebSocket uses plain ws:// — TLS is replaced by Noise end-to-end.
    Typically deployed behind nginx/Cloudflare which terminates external
    TLS on 443 and forwards plain WS to the backend; the Noise channel
    runs through it unchanged.

Device policy (optional)

Confines the backend to listed workspaces on this machine. Local-only file,
never written by the wire. No file ⇒ full user access (today's behaviour).

todoforai-bridge policy init            # allow cwd
todoforai-bridge policy add ~/repo/x
todoforai-bridge policy list            # restart bridge to apply

~/.config/todoforai/policy.json (or /etc/todoforai/policy.json, wins):

{ "jail": true, "workspaces": ["/home/me/repo/x"] }
  • read_file / write_file / RUN cwd must be inside a workspace
    (openat2 RESOLVE_BENEATH — symlink swaps can't escape).
  • Linux 6.2+: RUN shells and tool probes are Landlock-jailed. $HOME is
    dark except workspaces, toolchain/cache dirs (RW) and shell rc files (RO).
    "jail": false disables (needed on older kernels).
  • macOS / Windows: path checks only; the shell is not confined.
  • Fail closed: bad policy file ⇒ all paths denied, no shells, reason on
    stderr.

Reviews (0)

No results found