Skip to content

fix(daemon): harden local API against CSRF and DNS rebinding#243

Merged
vreshch merged 1 commit into
masterfrom
fix/daemon-api-hardening
Jul 7, 2026
Merged

fix(daemon): harden local API against CSRF and DNS rebinding#243
vreshch merged 1 commit into
masterfrom
fix/daemon-api-hardening

Conversation

@vreshch

@vreshch vreshch commented Jul 7, 2026

Copy link
Copy Markdown
Member

What

Adds layered web-origin defense to the local daemon (127.0.0.1:4243) - previously any website could POST text/plain JSON to the mutating /api/* routes (no CORS preflight), and DNS rebinding additionally unlocked reads plus the MCP surface.

  • Host allow-list on EVERY request: only 127.0.0.1:<port>, localhost:<port>, [::1]:<port> (the actually-bound port); else 403.
  • Origin allow-list: a present Origin must be loopback, else 403 (absent is fine for non-browser clients).
  • POST /api/* requires Content-Type: application/json (415 otherwise) so browser simple-requests cannot reach handlers.
  • /mcp transport: enableDnsRebindingProtection: true + allowedHosts pinned to the loopback host:port set (SDK 1.29.0 option surface).
  • Per-daemon 256-bit token: generated at boot, written 0600 to the config dir beside pid/port, required on X-Agentage-Token for /api/*. DaemonClient reads it per call; a missing/stale token makes the daemon count as unreachable (falls back to the in-process DirectClient). Cleaned up wherever pid/port files are cleaned.
  • /health stays tokenless (probe path) but is still Host-validated; /mcp stays tokenless so editor MCP clients keep working, protected by the Host/Origin/DNS-rebinding layers.

Why

The loopback socket was trusted implicitly - a malicious web page or another local process could drive memory writes/edits/deletes and sync. These layers close CSRF, DNS-rebinding, and same-machine cross-process access.

Scope notes

  • New server tests isolated in src/daemon/server-security.test.ts (avoids a sibling PR test file).
  • daemon-entry.ts touched only to write/clean the token beside pid/port (a sibling PR owns the cleanup race).
  • One e2e helper that raw-fetches /api/sync/status now sends the token.

Verified: npm run verify green locally.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🎉 PR Validation ✅ PASSED

Commit: 4680e15f30bfea882592582055ad46dc2bf6efdb
Branch: fix/daemon-api-hardening

Checks:

  • ✅ Release guard (no version/changelog changes)
  • ✅ Dependencies installed
  • ✅ Type check passed
  • ✅ Linting passed
  • ✅ Format check passed
  • ✅ Tests + coverage passed
  • ✅ Build successful

Ready to merge!


🔗 View workflow run
⏰ Generated at: 2026-07-07T22:05:31.080Z

@vreshch
vreshch marked this pull request as ready for review July 7, 2026 22:09
@vreshch
vreshch merged commit f24b619 into master Jul 7, 2026
2 checks passed
@vreshch
vreshch deleted the fix/daemon-api-hardening branch July 7, 2026 22:09
vreshch added a commit that referenced this pull request Jul 7, 2026
## What

README overhaul plus a hand-authored architecture diagram, aligned to
master after #238-#245. Ready as the npm landing page.

Touches only `README.md` and `docs/**` - no `src/`, `package.json`, or
workflows.

## Changes

- **`docs/architecture.svg`** - hand-written SVG (no render tooling).
Neutral background with dark text on light boxes, so it reads in both
light and dark GitHub themes and on npm. Shows AI tools -> MCP (stdio
`agentage mcp` + daemon HTTP `/mcp` on `127.0.0.1:4243`) -> CLI + local
daemon (single writer) -> local vaults (git-per-vault,
`@agentage/memory-core`) -> sync out to git remotes and account sync
(labeled protocol-neutrally, no internal tech named).
- **`README.md`** restructured for a first-time reader: What is this ->
Install -> Quickstart (local-only first, then optional `agentage setup`)
-> Architecture (diagram) -> Command reference -> Sync -> MCP
integration -> Daemon -> Env vars -> Development.
- **`docs/architecture.md`** - short walk-through of the diagram.

## Reality-check vs master

Built the CLI (`npm run build`) and verified every command and flag
against the live `--help` output. Corrected drift from the merged PRs:

- `vault add` now defaults to an **account** vault; `--local [path]`,
`--git <remote>`, `--path <dir>` are the alternatives.
- `vault sync [name]` now covers git and the account channel; progress
prints per vault.
- Documented the `update` command and `--no-daemon` global flag.
- Daemon section notes the loopback-only, token-guarded,
cross-origin-rejecting API and port-in-use reporting (#243, #244).
- Friendly memory errors + non-zero exits (#240); 64 KB read clamp and
secret refusal.
- Describes no specific `src/` file paths (a sibling PR is moving
`src/sync/` paths).

## Verification

- `npm run verify` green: 405 unit tests pass, type-check + lint +
format:check + build clean.
- Ran the offline quickstart end-to-end against the built `dist/cli.js`
(vault add --local, write via stdin, list, search, read `@vault/path`) -
all pass.
- Rendered `docs/architecture.svg` to PNG and eyeballed legibility.

Note: `format:check` only globs `{src,e2e}/**/*.ts`, so it does not lint
markdown; README changes are prose-only.
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.

1 participant