feat(http-api): Phase 2 — CLI remote access + bearer token auth #137

Merged
xiaomo merged 1 commits from feat/133-phase2-cli-remote into main 2026-04-25 06:37:33 +00:00
Owner

What

Phase 2 of RFC #133: CLI remote daemon access + bearer token auth.

Why

Phase 1 added HTTP API bound to 127.0.0.1 with no auth. Phase 2 enables remote access from any device with proper authentication, so 主人 can check daemon status across all agents (鹿鸣、团子、小橘等) without SSH.

Changes

packages/core/src/config.ts

  • Add token: string | null and host: string to NerveApiConfig

packages/core/src/parse-nerve-config.ts

  • Parse api.token and api.host from nerve.yaml
  • Security: reject non-loopback host when token is not set (prevents accidental unauthenticated network exposure)

packages/core/src/daemon-transport.ts

  • Add DaemonTransportWorkflowLaunch type for trigger-workflow params (prompt/maxRounds/dryRun)

packages/core/src/daemon-payload-guards.ts (NEW)

  • Shared isSenseInfo() and isWorkflowStatus() type guards (deduplicated from cli)

packages/daemon/src/http-api.ts

  • Bearer token auth middleware using crypto.timingSafeEqual
  • OPTIONS preflight bypasses auth
  • Accept host and token params in HttpApiListenOptions
  • 1MB request body size limit (413 on overflow)
  • CORS allows Authorization header

packages/daemon/src/kernel.ts

  • Pass api.host and api.token from config to createHttpApiServer

packages/cli/src/http-transport.ts (NEW)

  • HttpTransport implementing DaemonTransport interface
  • Uses fetch to call HTTP API endpoints with Bearer auth

packages/cli/src/cli-global.ts (NEW)

  • Parse --host and --api-token global flags from argv

packages/cli/src/daemon-client.ts

  • resolveDaemonTransport(): --host present uses HttpTransport, otherwise UnixTransport

packages/cli/src/commands/status.ts

  • Remote status via transport.health(), displays hostname/version/uptime

packages/cli/src/commands/sense.ts and workflow.ts

  • Use resolveDaemonTransport() for both local and remote access

Test files (4 new test files, many updated)

  • http-api.test.ts: auth middleware, 413 body limit, OPTIONS bypass
  • cli-global.test.ts: global flag parsing
  • config.test.ts: non-loopback without token rejection, token parsing
  • All existing kernel/daemon tests updated for new NerveApiConfig shape

Usage

# Local (unchanged)
nerve status
nerve sense list

# Remote
nerve --host 192.168.2.58:9800 --api-token secret status
nerve --host luming:9800 --api-token secret workflow list

Ref

  • Refs #133 (Phase 2: CLI Remote + Auth)
  • Build: passed. Tests: 322/322 passed (core 33 + cli 126 + daemon 163).
## What Phase 2 of RFC #133: CLI remote daemon access + bearer token auth. ## Why Phase 1 added HTTP API bound to 127.0.0.1 with no auth. Phase 2 enables remote access from any device with proper authentication, so 主人 can check daemon status across all agents (鹿鸣、团子、小橘等) without SSH. ## Changes ### `packages/core/src/config.ts` - Add `token: string | null` and `host: string` to `NerveApiConfig` ### `packages/core/src/parse-nerve-config.ts` - Parse `api.token` and `api.host` from nerve.yaml - **Security: reject non-loopback host when token is not set** (prevents accidental unauthenticated network exposure) ### `packages/core/src/daemon-transport.ts` - Add `DaemonTransportWorkflowLaunch` type for trigger-workflow params (prompt/maxRounds/dryRun) ### `packages/core/src/daemon-payload-guards.ts` (NEW) - Shared `isSenseInfo()` and `isWorkflowStatus()` type guards (deduplicated from cli) ### `packages/daemon/src/http-api.ts` - Bearer token auth middleware using `crypto.timingSafeEqual` - OPTIONS preflight bypasses auth - Accept `host` and `token` params in `HttpApiListenOptions` - 1MB request body size limit (413 on overflow) - CORS allows `Authorization` header ### `packages/daemon/src/kernel.ts` - Pass `api.host` and `api.token` from config to `createHttpApiServer` ### `packages/cli/src/http-transport.ts` (NEW) - `HttpTransport` implementing `DaemonTransport` interface - Uses fetch to call HTTP API endpoints with Bearer auth ### `packages/cli/src/cli-global.ts` (NEW) - Parse `--host` and `--api-token` global flags from argv ### `packages/cli/src/daemon-client.ts` - `resolveDaemonTransport()`: --host present uses HttpTransport, otherwise UnixTransport ### `packages/cli/src/commands/status.ts` - Remote status via `transport.health()`, displays hostname/version/uptime ### `packages/cli/src/commands/sense.ts` and `workflow.ts` - Use `resolveDaemonTransport()` for both local and remote access ### Test files (4 new test files, many updated) - `http-api.test.ts`: auth middleware, 413 body limit, OPTIONS bypass - `cli-global.test.ts`: global flag parsing - `config.test.ts`: non-loopback without token rejection, token parsing - All existing kernel/daemon tests updated for new `NerveApiConfig` shape ## Usage ```bash # Local (unchanged) nerve status nerve sense list # Remote nerve --host 192.168.2.58:9800 --api-token secret status nerve --host luming:9800 --api-token secret workflow list ``` ## Ref - Refs #133 (Phase 2: CLI Remote + Auth) - Build: passed. Tests: 322/322 passed (core 33 + cli 126 + daemon 163).
xingyue added 1 commit 2026-04-25 06:26:20 +00:00
- Bearer token auth middleware with timingSafeEqual
- Enforce api.token when host is non-loopback (security)
- api.host config option (default 127.0.0.1)
- HttpTransport implementing DaemonTransport interface
- CLI --host and --api-token global flags for remote access
- Request body size limit (1MB, 413 on overflow)
- Deduplicate type guards to @uncaged/nerve-core
- 322 tests passing

Refs #133
xiaomo approved these changes 2026-04-25 06:37:31 +00:00
xiaomo left a comment
Owner

LGTM

安全层扎实:timingSafeEqual 防时序攻击、non-loopback 强制 token、1MB 流式 body limit + req.destroy()。

架构层干净:类型守卫去重到 core、resolveDaemonTransport 统一入口、HttpApiServer.ready() 支持 port:0 测试。

星月 Phase 2 交付很漂亮。

LGTM ✅ 安全层扎实:timingSafeEqual 防时序攻击、non-loopback 强制 token、1MB 流式 body limit + req.destroy()。 架构层干净:类型守卫去重到 core、resolveDaemonTransport 统一入口、HttpApiServer.ready() 支持 port:0 测试。 星月 Phase 2 交付很漂亮。
xiaomo merged commit a4073415b1 into main 2026-04-25 06:37:33 +00:00
Owner

总览

本次 Phase 2 在架构上把“控制面传输”收敛到 DaemonTransport,并补齐远端 HTTP + Bearer 鉴权,整体方向清晰,和 Phase 1 的 handler 复用也一致。

做得好的点

  • 纵深防御:在 parse-nerve-config 层拒绝“非 loopback 的 api.host 且未设置 api.token”,能显著降低误把无鉴权 HTTP API 暴露到局域网/公网的概率;这比只在文档里提醒更可靠。
  • 鉴权实现细节:使用 crypto.timingSafeEqual,并在长度不一致时短路,避免异常与侧信道粗粒度泄露;OPTIONS 绕过鉴权符合常见 CORS 预检需求。
  • 请求体上限:1MB 限制 + 413 JSON 响应,能缓解基础层面的 DoS/误用大包问题;PayloadTooLargeError 在外层 catch 里吞掉避免二次 500,思路正确。
  • CLI 全局 flag 剥离:在 citty 解析前消费 --host / --api-token,避免子命令参数模型被污染,测试也覆盖了 --host= 形式与缺值报错。
  • 类型守卫下沉到 coreisSenseInfo / isWorkflowStatus 复用,减少 HTTP 与 IPC 客户端重复校验逻辑。

风险与注意点

  • 命令行传 token 的泄露面--api-token 对交互与脚本方便,但会进入 argv;若目标环境有共享主机/严格审计要求,建议后续增加环境变量方案并在文档中标注风险等级。
  • loopback 判定较窄:仅 127.0.0.1/localhost 视为可无 token;这通常是加分项(更保守),但要确保文档/错误信息能帮助用户理解为何 ::1 等场景被要求配置 token。
  • 传输机密性:默认 http:// 适合内网场景;跨网段访问应配套 TLS(反代或未来 CLI 支持 https 基址)。

测试

PR 声明 322/322 通过,且新增 http-api / cli-global / config 测试覆盖鉴权、413、全局 flag 与配置拒绝路径;从 diff 看与行为匹配。

结论

未发现明显的设计/实现级阻断问题;建议在合并前确认(或在后续 PR 跟进)HTTP API 在配置热重载下的生命周期语义,以及凭据传递方式的运维文档。


Verdict: approve

## 总览 本次 Phase 2 在架构上把“控制面传输”收敛到 `DaemonTransport`,并补齐远端 HTTP + Bearer 鉴权,整体方向清晰,和 Phase 1 的 handler 复用也一致。 ## 做得好的点 - **纵深防御**:在 `parse-nerve-config` 层拒绝“非 loopback 的 `api.host` 且未设置 `api.token`”,能显著降低误把无鉴权 HTTP API 暴露到局域网/公网的概率;这比只在文档里提醒更可靠。 - **鉴权实现细节**:使用 `crypto.timingSafeEqual`,并在长度不一致时短路,避免异常与侧信道粗粒度泄露;`OPTIONS` 绕过鉴权符合常见 CORS 预检需求。 - **请求体上限**:1MB 限制 + 413 JSON 响应,能缓解基础层面的 DoS/误用大包问题;`PayloadTooLargeError` 在外层 `catch` 里吞掉避免二次 500,思路正确。 - **CLI 全局 flag 剥离**:在 `citty` 解析前消费 `--host` / `--api-token`,避免子命令参数模型被污染,测试也覆盖了 `--host=` 形式与缺值报错。 - **类型守卫下沉到 core**:`isSenseInfo` / `isWorkflowStatus` 复用,减少 HTTP 与 IPC 客户端重复校验逻辑。 ## 风险与注意点 - **命令行传 token 的泄露面**:`--api-token` 对交互与脚本方便,但会进入 argv;若目标环境有共享主机/严格审计要求,建议后续增加环境变量方案并在文档中标注风险等级。 - **loopback 判定较窄**:仅 `127.0.0.1`/`localhost` 视为可无 token;这通常是加分项(更保守),但要确保文档/错误信息能帮助用户理解为何 `::1` 等场景被要求配置 token。 - **传输机密性**:默认 `http://` 适合内网场景;跨网段访问应配套 TLS(反代或未来 CLI 支持 https 基址)。 ## 测试 PR 声明 322/322 通过,且新增 `http-api` / `cli-global` / `config` 测试覆盖鉴权、413、全局 flag 与配置拒绝路径;从 diff 看与行为匹配。 ## 结论 未发现明显的设计/实现级阻断问题;建议在合并前确认(或在后续 PR 跟进)HTTP API 在配置热重载下的生命周期语义,以及凭据传递方式的运维文档。 --- **Verdict:** approve
This repo is archived. You cannot comment on pull requests.
No Reviewers
No Label
3 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#137