
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
Human-in-the-loop approvals and notifications for AI coding agents via CLI, MCP, and agent hooks.
Human-in-the-loop approvals and notifications for AI coding agents, without running a Nofax SaaS.
Nofax is a small open-source bridge between an agent and a human. Local mode can pause an AI workflow, notify your phone, and return an explicit decision. An optional self-deployed Cloudflare Worker exposes a deliberately narrower remote MCP surface for one-way notifications and safe request inspection.
No Nofax account. No paid model API. No inbound port on your machine. MIT licensed.
Current status: local Nofax is
0.2.1. The optional Cloudflare Worker is the upcoming0.3.0remote surface and is developed alongside the local package.
Agent workflows increasingly need a clean answer to one question: when automation reaches a human decision boundary, how does it ask without pretending that silence means approval?
Nofax keeps that boundary explicit:
| Capability | Local Nofax 0.2 | Remote Worker 0.3 |
|---|---|---|
| Transport | stdio / CLI hooks | MCP Streamable HTTP |
| One-way notification | Yes | Yes |
| Allow / Deny | Yes | No |
| Explicit choices | Yes | No |
| Free-text refinement | Yes | No |
| Wait for human response | Yes | No |
| Read request metadata | Yes | Yes |
| Durable state | Local files | Existing SQLite Durable Object rows |
| Hosted by Nofax | No | No — self-deployed Worker |
| Remote authentication | Local process boundary | Private bearer key |
The remote Worker is not a hosted remote-approval service. It can send an informational notification and inspect existing request state, but it has no approval callback, choice, refinement, wait, webhook, or arbitrary remote-write endpoint.
npm install -g nofax
Requires Node.js 20 or newer.
nofax init
Nofax creates ~/.nofax/config.json and generates a high-entropy notification topic. With the default transport, subscribe to the displayed topic in the ntfy mobile app.
nofax test
nofax notify --title "Build finished" "All tests passed"
nofax approve --title "Deploy?" "Release 1.4.0 is ready"
nofax refine --title "Refine draft" "Tell me what to change"
An approval resolves to stable terminal JSON:
{"decision":"allow"}
or:
{"decision":"deny"}
If the request is still pending, times out, disconnects, or hits a transport error, Nofax never converts that condition into approval.
Start the local stdio MCP server:
nofax mcp
Local MCP exposes:
nofax_notifynofax_request_approvalnofax_request_choicenofax_request_refinementnofax_wait_for_responsenofax_get_requestnofax_list_pendingInteractive requests return a durable request ID. nofax_wait_for_response performs a bounded wait; callers must repeat the wait while the request remains pending rather than infer approval.
Use Nofax as a local PermissionRequest hook in ~/.claude/settings.json:
{
"hooks": {
"PermissionRequest": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "nofax hook claude"
}
]
}
]
}
}
Codex hooks are enabled by default. Configure ~/.codex/hooks.json:
{
"hooks": {
"PermissionRequest": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "nofax hook codex",
"statusMessage": "Waiting for Nofax approval"
}
]
}
]
}
}
Restart Codex, run /hooks, and review/trust the exact Nofax hook definition before relying on it. Codex skips non-managed hooks until they are trusted, and a changed hook definition must be reviewed again. If an administrator or local policy has explicitly disabled hooks, re-enable them with [features] hooks = true in ~/.codex/config.toml.
Current Gemini CLI builds expose a synchronous BeforeTool hook that can allow or deny a tool call. Route selected tools through Nofax in ~/.gemini/settings.json:
{
"hooks": {
"BeforeTool": [
{
"matcher": "run_shell_command|write_file|replace",
"hooks": [
{
"name": "nofax-approval",
"type": "command",
"command": "nofax hook gemini",
"timeout": 305000
}
]
}
],
"Notification": [
{
"matcher": "ToolPermission",
"hooks": [
{
"name": "nofax-notification",
"type": "command",
"command": "nofax hook gemini"
}
]
}
]
}
}
BeforeTool waits for an explicit Nofax Allow/Deny result. A Nofax timeout or transport failure emits valid no-decision JSON and leaves Gemini CLI's own policy/confirmation flow in control rather than converting failure into approval. The Notification hook remains advisory and is forwarded only as a phone notification.
Adjust the matcher to the tools you want Nofax to gate. Keep the hook timeout longer than Nofax's configured approval timeout (timeoutSeconds, 300 seconds by default).
The worker/ package provides a private, self-deployed MCP endpoint:
remote MCP client
|
| authenticated Streamable HTTP
v
Cloudflare Worker
|
+--> nofax_notify ------> ntfy ------> phone
|
+--> SQLite Durable Object
|
+--> get request metadata
+--> list pending requests
It exposes exactly three tools:
nofax_notify — one-way notification only;nofax_get_request — read one safe request projection;nofax_list_pending — read unresolved, unexpired request projections.Deploy from worker/:
npm ci
npx wrangler login
npx wrangler secret put NOFAX_REMOTE_KEY
npx wrangler secret put NTFY_TOPIC
npm run check
npm run deploy
Preferred MCP connection:
https://<worker>.workers.dev/mcp
Authorization: Bearer <NOFAX_REMOTE_KEY>
Clients that cannot attach a static authorization header can use the compatibility capability path:
https://<worker>.workers.dev/mcp/<NOFAX_REMOTE_KEY>
Treat the complete capability URL like a password.
See docs/remote-mcp.md for deployment, threat boundaries, and qualification details.
The default public ntfy.sh service applies publisher quotas. Serverless platforms such as Cloudflare Workers may use shared outbound IP space, so a Worker can receive an ntfy 42908 daily-quota response even when that individual Worker has sent very little traffic. That limit is imposed by ntfy, not by the Cloudflare Workers request quota.
For reliability-sensitive deployments, use a notification provider whose quota is tied to your own authenticated account/identity, or operate a trusted self-hosted transport. Do not build a critical workflow around anonymous public-topic quota assumptions.
Nofax is a transport and human-interaction component, not an authorization policy engine.
Local mode:
Remote mode:
nofax_notify performs an external messaging side effect;NOFAX_REMOTE_KEY is a bearer credential;Read SECURITY.md before using Nofax with sensitive information.
Default local config lives at ~/.nofax/config.json:
{
"version": 1,
"server": "https://ntfy.sh",
"topic": "nofax_<random>",
"timeoutSeconds": 300
}
Override the home directory with NOFAX_HOME:
NOFAX_HOME=/path/to/nofax-home nofax config
Use another ntfy-compatible server with:
nofax init --server https://ntfy.example.com --force
Local package:
npm ci
npm run check
npm test
npm pack --dry-run
Remote Worker:
cd worker
npm ci
npm run check
CI qualifies Node.js 20, 22, and 24 for the local package. The Worker gate runs TypeScript, Vitest, a production-dependency audit, and a Wrangler deployment dry-run.
docs/architecture.md — trust boundaries and data flowdocs/remote-mcp.md — remote Worker deployment and qualificationSECURITY.md — security assumptions and vulnerability reportingCONTRIBUTING.md — contribution and test expectationsCHANGELOG.md — release historyNofax deliberately does not provide:
always approve policy;MIT © Tomi Šeregi. See LICENSE.
FAQs
Human-in-the-loop approvals and notifications for AI coding agents via CLI, MCP, and agent hooks.
We found that nofax demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.