Status: Draft (v0.3) · Last updated: 2026-07-13
A phased path from the current scaffold to a production-grade, open-core firewall gateway. Phases are roughly sequential but later OSS phases can overlap. The OSS / Paid column marks the open-core boundary (see
business-model.md).
Greenfield monorepo, scaffolded and building:
- Cargo workspace (
honmoon-core,honmoon-proxy,honmoon-cli) — builds, 4 tests pass. - Bun workspace (
@honmoon/policy,@honmoon/cli,@honmoon/api) + React/Vite/Tailwind dashboard. - Policy model (YAML + JSON Schema), example policy,
evaluate()with domain matching. honmoon run/gateway/joinare CLI stubs (bail!).- Decided: Pingora for the HTTP data plane (ADR-0001) — since superseded: the data plane runs on raw tokio (ADR-0002) and hudsucker for TLS termination (ADR-0003).
| Item | Status |
|---|---|
| Monorepo scaffold (Cargo + Bun + dashboard) | ✅ |
| Policy model + JSON Schema + example policy | ✅ |
| Knowledge docs, ARCHITECTURE.md, business model | ✅ |
| ADR-0001 (Pingora — superseded by ADR-0002/0003) | ✅ |
The first vertical slice: honmoon run actually enforces a domain allowlist.
- Terminating
CONNECTforward proxy inhonmoon-proxy::gateway(raw tokio — see ADR-0002) - Read CONNECT host →
Facts{domain}→evaluate()→ allow tunnels / deny403/ non-CONNECT405 -
honmoon run --policy p.yaml -- <cmd>: ephemeral proxy, sethttps_proxy/http_proxyfor the child, exec, propagate exit code -
honmoon gateway --config p.yaml --addr ...runs the proxy standalone - Hermetic integration test: allowed host tunnels to a local upstream, denied host blocked with 403
- (done in Phase 5) TLS termination — needed for body/HTTP-level rules; delivered with hudsucker (ADR-0003)
Exit criteria: ✅ proven by crates/honmoon-proxy/tests/egress.rs — an allowed host tunnels
through to an in-process upstream (200), a denied host is blocked (403), hermetically over loopback.
(The literal curl https://github.com smoke works too but is network-dependent; the automated test is hermetic.)
- Integrate
cel-interpreterinhonmoon-core -
decide(): rules in order — matchendpoint, evaluateconditionoverFacts, returnverdict; else egress lists - HTTP facts (
http.method,http.path,http.host,http.body_size) exposed to CEL ashttp - Rule ordering / precedence documented (first matching rule wins; deny>allow>default for egress)
- Consolidated the policy engine in
honmoon-core(domain matching moved out ofhonmoon-proxy) - (carried) Keep Rust
honmoon-coreand TS@honmoon/policyin sync — TD-001
Exit criteria: ✅ CEL rules evaluate correctly against synthetic facts — see crates/honmoon-core/src/engine.rs tests (cel_rule_matches_http_fact, rule_endpoint_must_match, unknown_fact_reference_does_not_match).
Note: real http.method/path/body_size need TLS termination (later phase); over CONNECT only http.host is populated.
The moat: wire-level protocol parsing beyond HTTP — in honmoon-core::protocols.
- PostgreSQL simple-query (
'Q') wire parser →sql.verb,sql.table(parse_postgres_query) - SQL verb/table heuristic over a statement (
parse_sql) — DROP/TRUNCATE/DELETE/UPDATE/INSERT/SELECT - Kubernetes API facts →
k8s.resource,k8s.verb,k8s.namespace(parse_k8s_request; core + grouped APIs, list vs get) -
sql/k8sfacts wired into the CEL engine; per-endpoint policy binding viaRule::endpoint - (carried) Live inline TCP relay that feeds the parsers from real traffic — needs endpoint listener config + (for K8s) TLS termination; see TD-006
Exit criteria: ✅ a DROP/TRUNCATE against postgres-prod and a delete secrets against
k8s-prod are caught by policy — proven end-to-end (raw packet/request → parser → decide()) by
engine.rs::protocol_facts_drive_policy_end_to_end and against the shipped policies/agent.yaml by
shipped_example_policy_fires.
Note: parsing is engine-complete and tested; wiring it onto a live socket is the data-plane follow-up (TD-006).
-
pauseverdict: hold a request pending approval (local, single-node) — the data plane registers held requests inhonmoon-proxy::approval::ApprovalRegistry, awaits aoneshotresolution (auto-rejects after--pause-timeout), and tunnels or403s - Local audit log (every verdict, structured) —
honmoon-core::audit::AuditLog(bounded in-memory ring + optional JSONL sink via--audit-log) + query API: the in-process management API serves the live ring;@honmoon/apiqueries the durable JSONL log (/api/auditwithlimit/decision/since/domain,/api/audit/stats) - Dashboard (
apps/dashboard): Overview, audit log viewer, Prism policy editor, and approval queue with approve/deny — live-polling the management API - Embed built dashboard into the Rust binary via
rust-embed(honmoon-mgmt); the management API serves it.honmoon gatewayruns the proxy + management API on one runtime sharing audit/approval state (--mgmt-addr, default127.0.0.1:8444)
Exit criteria: ✅ proven by crates/honmoon-mgmt/tests/e2e.rs — a pause rule holds a
live CONNECT, the held request appears on the management API's approval queue, approving it
(over HTTP) lets the tunnel through (200) while rejecting blocks it (403), and every step
(paused → approved/rejected) is recorded in the audit log.
Note: over CONNECT only http.host-based pause rules fire today; SQL/K8s pause needs the
live inline relay + TLS termination (TD-006).
Close the exfiltration gap: inspect what leaves, not just where / which protocol. Korean-first PII detection over request bodies, surfaced as CEL facts. Detection is OSS; fleet-wide DLP management and compliance reporting are Paid (Phase 7).
Prerequisite — TLS termination / body access (✅ done, ADR-0003). Over a raw CONNECT tunnel only
http.hostis visible; PII detection needs the decrypted body. The data plane now terminates TLS (hudsucker MITM, opt-in local CA) and scans decrypted bodies (detect-only), which also unblocks body-level SQL/K8s facts (TD-006).
- TLS termination in the data plane (hudsucker, not Pingora — ADR-0003) so request bodies reach the engine. Detect-only: decrypted bodies are scanned and findings audited (
--tls-intercept, opt-in local CA). Proven bycrates/honmoon-proxy/tests/mitm.rs. - Tier-1 deterministic PII detector in
honmoon-core::pii(Rust regex + checksum/Luhn): RRN, FRN, business reg. no., card (Luhn), email, IPv4, phone. (passport / driver / account / vehicle deferred — loose-format / keyword-anchored, precision risk) - Tier-2 format / dictionary detectors (postal code, medical IDs, DOB / age, …)
- Expose
pii.types/pii.count/pii.max_severityas CEL facts; wire toallow/deny/pause(Facts.pii, registered inengine::eval_condition, carried in the auditFactsSummary) - Detect (audit-only) vs block (enforcing) modes — precision-first block, recall-first audit.
Detect-only over terminated TLS is live (ADR-0003); enforcing (deny/pause on
pii) is the fast follow. - (optional) NER assist layer for PERSON / ADDRESS, kept off the inline path (audit / async)
- Benchmark harness (
pii-benchmark-goals.md) —pii_scanbridge +score.tsmeasurement loop in place (Tier-1 F1 1.000 onhonmoon-synth, §9.1); CI regression gate TODO
Exit criteria: a request body carrying a valid-checksum RRN to a non-allowlisted host is
caught by policy (deny/pause), measured against the targets in
pii-benchmark-goals.md — Tier-1 F1 ≥ 0.98, payload-surface micro-F1
≥ 0.80, rule-layer p99 ≤ 2 ms/doc.
Note: fleet-wide DLP policy management and compliance / exfil reporting are Paid (Phase 7).
-
honmoon gateway— standalone central proxy loading policy, accepting clients -
honmoon runhardened isolation (Linux netns / macOS NetworkExtension) -
honmoon join— route host traffic to a gateway via tunnel (WireGuard) - Policy hot-reload (graceful reload without dropping tunnels)
Exit criteria: all three modes work end-to-end on Linux; documented setup.
Can run in parallel with Phases 5–6; builds on the Phase 5 detectors and the secret tokenization primitive.
The proxy is the enforcement backstop: every request — including the full conversation
history agent clients resend each turn — crosses the wire through honmoon, so the model
never sees a raw secret. What the proxy cannot reach is what the client persists locally
before sending: Claude Code stores raw prompts and raw Read output in its session
transcript (~/.claude/projects/**/*.jsonl), which then feeds /resume, compaction,
subagents, and backups/sync. Client-side hooks close that gap by redacting before content
enters the transcript — the plugin doubles as lightweight onboarding (no local CA trust
needed).
- Claude Code plugin with redaction hooks (#19,
packages/claude-plugin/):PostToolUseonReadreplaces the tool result viaupdatedToolOutput(redacted in the model context; transcript rewrite is version-dependent — see caveat);UserPromptSubmitblocks prompts carrying secrets (hooks cannot rewrite a prompt — block + actionable reason);PreToolUsedenies reads of known-sensitive paths (.env*, key files) - New secret detector + redaction engine in
honmoon-core(#19):secret_detect(Anthropic/OpenAI/AWS/GitHub/Slack/Google/PEM + entropy-gated generic, precision-first likepii.rs) andredact(joins secret + Tier-1 PII detection into theSecretTokenizer→ deterministic, byte-stable placeholders per #20) - CLI transport —
honmoon hook(type: "command"): hook JSON on stdin (with a per-session salt derived from a persisted machine secret, so a given secret tokenizes identically across the fresh processes each call spawns, per #20) →honmoon-coredetectors- tokenization → hook JSON verdict; works with only the binary installed. Caveat noted below.
- Gateway-direct HTTP transport (follow-up, #19) —
type: "http"hooks POST the hook payload toPOST /api/hooks/claude-codeon the management API and get the same hook JSON schema back (no per-call process spawn; tokenization mapping shared with the proxy by construction). Deferred: HTTP hooks fail open (connection failure / non-2xx continues unredacted), so it must not become the silent default; the command transport ships first. - Cache-stable determinism on the proxy path (#20): identical secret → identical token across turns, so redacting the resent history preserves the provider prompt-cache prefix
Exit criteria: reading a file with a valid-checksum RRN or an API key lands redacted in the
model context via PostToolUse updatedToolOutput; a prompt carrying a secret is blocked with
actionable feedback; same multi-turn body redacted twice is byte-identical (#20). Transcript
caveat: updatedToolOutput is documented to replace what the model sees, but the docs do not
guarantee the persisted .jsonl is rewritten — for known credential files the guaranteed
transcript-hygiene path is the PreToolUse deny (never read ⇒ never transcribed).
The open-core boundary. Monetization begins where single-node becomes fleet.
- Centralized policy management across multiple nodes/agents
- RBAC / SSO / SAML
- Approval routing + Slack notifications
- Long-term audit retention & search; compliance/exfil reports (incl. PII/DLP findings from Phase 5)
-
packages/enterprise/under a commercial license (BSL/FSL)
- Hosted management plane (
apps/cloud/), multi-tenant - Managed allowlists + threat-intel feeds
- API-credential isolation sidecar (Anthropic/OpenAI/Gemini) with rate limiting
- SLA / support tiers
- Licensing: move core to Apache-2.0; add
LICENSE.enterprise(BSL/FSL) when Phase 7 lands. - Tech debt: TD-001 (dual policy model → generate from JSON Schema), TD-002 (
serde_yaml). - CI/CD: set up
cargo test/clippy/fmt+bun test/lintgates once a remote exists. - Optional Squid backend:
deploy/squid/as an alternate HTTP egress backend. - Cloudflare target: a Workers deployment can host the egress filter + control plane only; the wire-level core requires a host/container.
Not every layer runs everywhere — record this so scope stays honest:
| Capability | Self-host (host/container) | Cloudflare Workers |
|---|---|---|
| HTTP egress filter + control plane | ✅ | ✅ (explicit proxy only) |
Wire-level SQL/K8s, run/join, TLS MITM |
✅ | ❌ (needs OS networking) |