- TypeScript 100%
| src | ||
| tests | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Remote Node Server
A standalone Node.js server that runs on a remote machine and executes bash commands on behalf of a Fractal Synapse agent. The agent connects to it over WebSocket via the remote-nodes-plugin, sends signed execute messages, and the server validates the auth token, checks each command against a local security-permissions.json using the @fractal-synapse/permissions-engine package, and either runs the command or returns a denial.
Requirements
- Node.js 18 or later
npm installto install dependenciesnpm run buildto compile TypeScript todist/
The
@fractal-synapse/permissions-enginedependency must be built first (npm run buildinpackages/permissions-engine).
First Run
node dist/index.js
On first run the server creates a config.json in the working directory and prints the generated auth token to the console:
=== REMOTE NODE SERVER — FIRST RUN ===
Config file created at: /path/to/config.json
Auth token (copy this into your app settings):
a1b2c3d4...64-char-hex-token
======================================
Copy the auth token into your app's remote node settings so the plugin can authenticate.
Configuration
config.json is created automatically on first run and can be edited afterward.
| Field | Default | Description |
|---|---|---|
port |
7331 |
Port the WebSocket server listens on |
authToken |
auto-generated (64 hex chars) | Authentication token sent by the plugin as Authorization: Bearer <token>. Regenerated if missing |
permissionsFile |
security-permissions.json |
Path to the permissions file. Relative to the config file directory, or absolute |
Use the --config flag to point to a config file in a different location:
node dist/index.js --config /path/to/config.json
Permissions
Commands are checked against security-permissions.json before execution. The file defines which commands are allowed, denied, or require human approval.
See packages/permissions-engine/README.md for the full permissions format reference.
Hot reload: Edit the permissions file while the server is running. Changes are picked up automatically within approximately 2 seconds. If the file contains invalid JSON the reload fails and the server continues using the previous rules.
Running
# Install dependencies (permissions-engine must be built first)
npm install
# Build TypeScript
npm run build
# Start the server
node dist/index.js
# Start with a custom config location
node dist/index.js --config /path/to/config.json
Protocol
The server communicates over a single persistent WebSocket connection using JSON messages.
Plugin to Server (PluginToServer)
| type | Fields | Description |
|---|---|---|
execute |
commandId, command |
Request execution of a shell command |
permission-response |
commandId, decision |
Respond to a permission-required prompt (V1 stub -- not yet active) |
heartbeat |
(none) | Keep-alive ping; server replies with heartbeat-ack |
Server to Plugin (ServerToPlugin)
| type | Fields | Description |
|---|---|---|
result |
commandId, output, exitCode |
Command completed successfully |
error |
commandId, message |
Runtime error during command execution |
permission-required |
commandId, command, ruleDescription |
Command needs human approval (designed but not active in V1) |
permission-denied |
commandId, reason |
Command was denied by the security policy |
heartbeat-ack |
(none) | Reply to a heartbeat ping |
V1 Limitations
askrules are treated asdeny. If a command matches a rule with"permission": "ask", the server logs a warning and returnspermission-deniedwith a message indicating V2 escalation is required. There is no mechanism to prompt for approval in V1.permission-required/permission-responseround-trip is not active. The protocol includes these message types for future use, but V1 never sendspermission-required. The server acceptspermission-responsemessages from the plugin but ignores them.
Reliability
- No process timeout. Commands run until they exit naturally. There is no artificial time limit.
- Result queue and reconnection delivery. If the client disconnects while a command is running, the result is queued server-side and delivered automatically when the client reconnects. On reconnect, all queued results are flushed immediately.
- Duplicate command guard. If the same
commandIdis received while the command is already running, the second request is silently ignored. This prevents duplicate execution when a plugin reconnects and re-sends an in-flight command. - State persistence. A
server-state.jsonfile (stored alongsideconfig.json) persists running commands and queued results to disk. On server restart, any commands that were running get an error result queued immediately (they were killed by the restart). Undelivered results survive restarts and are flushed on reconnect.
Security Notes
- Run the server behind Tailscale or on a private LAN. The server does not implement TLS itself.
- The auth token is application-level security. It prevents unauthorized WebSocket connections but is not a substitute for network-level isolation.
- Do not expose the server port to the public internet. There is no rate limiting, account lockout, or encrypted transport.