Self-hosted ยท open architecture

Reach applications inside a private network โ€” without opening a single inbound port.

Run the OpenHook server in your own cloud. Install a small agent on Windows or Linux machines inside the network. Agents connect outbound and pull work. Your cloud application submits a task over a REST API and gets the result back โ€” typically in well under a second.

Node 24+ server ยท one 7 MB static agent binary ยท no runtime deps unless you choose Postgres

Deny-by-default agent policy ยท SHA-256 / scrypt credentials ยท CIDR & DNS-rebind-safe allowlists ยท audited every request

Why it's built this way

The design assumes the cloud side may be compromised, and the local machine should still be safe.

๐Ÿ”

The local machine holds the authority

The cloud can only ask. Each agent has a local policy file listing the exact task types, hosts, paths and commands it will accept โ€” anything else is refused before it runs, no matter what the server sends.

โšก

Latency that feels synchronous

Agents hold a long poll open, so a task usually starts within milliseconds. Submitting with wait_ms turns the whole round trip into a single blocking HTTP call.

๐Ÿ“ฆ

Nothing to install on machines you least want to touch

The agent is one static binary that registers itself as a native service. No runtime, no dependencies, nothing to patch across the fleet.

๐Ÿ”

Small enough to audit

The server is plain TypeScript run directly by Node 24 โ€” no build step, and no runtime dependencies at all unless you choose Postgres.

Quick start

Requires Node.js 24+ for the server; the agent runs on Node 18+.

npm start

The server prints an owner email and generated password on first boot. Open http://localhost:8080 and:

  1. Agents โ†’ Add agent gives you a one-line install command for Windows or Linux.
  2. API keys โ†’ Create key gives your cloud application a credential.
  3. New task lets you try it before writing any code.

Install an agent โ€” Windows (elevated PowerShell)

$env:OPENHOOK_SERVER='https://openhook.example.com'
$env:OPENHOOK_ENROLL_TOKEN='ohe_โ€ฆ'
irm https://openhook.example.com/download/install.ps1 | iex

Install an agent โ€” Linux (root)

curl -fsSL https://openhook.example.com/download/install.sh | \
  sudo OPENHOOK_SERVER='https://openhook.example.com' \
       OPENHOOK_ENROLL_TOKEN='ohe_โ€ฆ' bash

Both write a deny-by-default policy โ€” the agent reports system info and calls localhost, nothing more, until you widen it in agent.config.json.

Calling a local service from the cloud

curl -X POST https://openhook.example.com/api/v1/call \
  -H "Authorization: Bearer $OPENHOOK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "tag": "warehouse",
        "url": "http://192.168.1.50:8080/api/stock/SKU-441",
        "method": "GET"
      }'
{
  "task_id": "8f3cโ€ฆ", "agent_id": "a55dโ€ฆ",
  "duration_ms": 37,
  "status": 200,
  "json": { "sku": "SKU-441", "on_hand": 128 }
}

Task types

Every type is off unless the agent's policy enables it.

TypeWhat it doesPayload
http.requestCalls an HTTP service only the agent can reach{ url, method?, headers?, body?, json?, timeout_ms?, max_bytes? }
shell.execRuns an allow-listed executable{ command, args?, cwd?, env?, stdin?, timeout_ms? }
script.runRuns an inline script{ interpreter, code, args?, cwd?, env? }
fs.read / fs.write / fs.listFile access under allowed roots{ path, โ€ฆ }
net.checkTCP reachability probe{ host, port, timeout_ms? }
sys.infoHost facts: OS, CPU, memory, uptime, interfaces{}

Target with "agent": "warehouse-pc-01" for one machine, "tag": "warehouse" for whichever tagged agent is free, or neither for any capable agent. Tasks wait in the queue, so a machine can be offline when the work is submitted.

Security model

The agent decides what it will run, from a config file the machine's administrator controls.

"policy": {
  "enabled_types": ["http.request", "fs.read"],
  "http": { "allow_hosts": ["10.0.0.0/8", "*.corp.local"],
            "deny_hosts": ["169.254.169.254", "169.254.0.0/16"] },
  "fs":    { "allow_read_roots": ["C:/openhook/inbox"] },
  "shell": { "allow_commands": [], "allow_any": false }
}
  • shell.exec and script.run are disabled by default; each executable or interpreter must be named.
  • Filesystem access is confined to explicit roots, checked after resolving ...
  • HTTP targets match allow/deny lists supporting exact names, *.wildcards and CIDR ranges.
  • Hostnames are resolved and the resulting IP re-checked, so a DNS name pointing at a blocked range is still refused.
  • Cloud metadata endpoints are blocked out of the box; redirects are not followed unless you opt in.
  • Credentials are stored as SHA-256 hashes (passwords use scrypt): enrollment tokens, agent tokens, and scoped API keys.

What OpenHook will not do: stop an operator who deliberately sets allow_any: true on cmd.exe from handing the cloud full control of that machine. Keep policies narrow.

Identity and access

Single sign-on against any OIDC provider โ€” Entra ID, Okta, Google Workspace, Keycloak, Auth0 โ€” with authorization-code flow and PKCE.

vieweroperatoradminowner
Read tasks and agentsโœ“โœ“โœ“โœ“
Submit and cancel tasksโœ“โœ“โœ“
Manage agents, keys, users, auditโœ“โœ“
System settingsโœ“

API keys are separate credentials for machines, scoped to tasks:read, tasks:write, agents:read, agents:write, with optional expiry and a per-key rate limit.

Reliability & observability

Leases

A claimed task carries a lease; if the agent dies, the task is requeued โ€” or failed, once max_attempts is spent โ€” rather than lost.

Cancellation

Cancellation reaches a running task within seconds and kills its process tree.

Idempotency

Pass idempotency_key and a repeated submission returns the original task, even across replicas.

Graceful drain

On SIGTERM, readiness flips false, parked agents are released to reconnect elsewhere, and in-flight work finishes before exit.

Metrics

/metrics serves Prometheus text: queue depth, task durations, queue wait, lease expiries, auth failures, agent counts, live replicas.

Health checks

/healthz is liveness, /readyz is readiness including a database check. Logs are structured JSON with automatic credential redaction.

Deployment

docker compose up -d          # single node, SQLite
helm install openhook deploy/helm/openhook -f my-values.yaml
SQLite (default)Postgres
ReplicasExactly oneAs many as you like
SetupNoneA managed database
Rolling deploysBrief gapZero gap

Full platform recipes for AWS, Azure, GCP and Kubernetes live in deploy/README.md. One requirement that bites people: agents hold long polls open for up to 25 seconds, so every proxy, ingress and load balancer in front must allow idle connections longer than that.

Run OpenHook in your own cloud

Self-hosted, auditable, and outbound-only from day one.