Security Architecture — DDx Agent Execution Surface
Source identity (from
02-design/security-architecture.md):
ddx:
id: security-architecture
depends_on:
- architectureSecurity Architecture — DDx Agent Execution Surface
Scope: ddx-server and the agent-execution surface that HELIX delegates
to (ddx agent execute-bead, ddx agent execute-loop, ddx server workers).
Status: Complete
Decision
The agent-execution surface is a local-first developer service with no
inbound network exposure by default. Trust originates with the operator’s
local user account. Secrets (model-provider API keys) are loaded from the
operator’s environment, never persisted by the service, and never echoed
into bead bodies, execution evidence, or git-tracked logs. Authority is
governed by the operator’s filesystem permissions and the
CONTRACT-001-defined boundary between HELIX (supervisory prompts) and DDx
(managed execution + tracker writes).
The primary controls are:
- Bind
ddx-serverto loopback (127.0.0.1:7743) by default; remote access requires explicit operator opt-in via the Tailscale (tsnet) sidecar with ACL-gated identity. - Route every model-provider call through DDx so API keys live in one process boundary; HELIX never reads them directly.
- Treat
.ddx/beads.jsonl,.ddx/exec-runs.jsonl, and execution evidence as public-by-default (checked into the repo) and forbid secret-bearing content there by contract. - Make every agent invocation produce a durable execution record
(
.ddx/exec-runs.d/<run-id>/) so privilege use is auditable.
Trust Boundaries
| Boundary | Assets | Trust Change | Control |
|---|---|---|---|
Operator shell → helix run | Repo working tree, env vars | Operator authority enters the supervisory loop | Filesystem permissions; HELIX never elevates beyond operator UID |
helix run → ddx agent execute-bead | Selected bead, governing artifacts, prompt | Supervisor prompt crosses into DDx-managed execution | Bead claim is single-writer; DDx validates the bead is ready and execution-eligible before dispatch |
ddx-server ↔ model provider (OpenRouter, Anthropic, etc.) | Prompt body, model output, API key | Plaintext prompt leaves the host; key authenticates | TLS-only client; key in environment (OPENROUTER_API_KEY, ANTHROPIC_API_KEY); never logged |
Loopback HTTP (127.0.0.1:7743) | Server control plane (worker status, queue) | Local UNIX-domain trust | Bind to loopback only; remote access is opt-in via Tailscale sidecar |
Tailscale (tsnet) → ddx-server (opt-in) | Same control plane, over WireGuard | Identity-bound mesh access | Tailscale ACLs restrict to operator’s tailnet; service still trusts only the authenticated identity |
| Repo working tree → git remote | Bead bodies, execution evidence, logs | Repo content becomes public (or org-visible) on push | Contract: no secrets in beads, evidence, or logs; pre-commit hooks scan for known secret patterns |
Control Mapping
| Threat / Risk | Control | Implementation Surface | Verification |
|---|---|---|---|
| Remote code execution via exposed HTTP | Loopback bind by default | ddx-server startup config | ss -lntp confirms only 127.0.0.1:7743; integration test asserts non-loopback bind requires explicit flag |
| API-key exfiltration via committed logs | Keys live in env, never written to disk by the service | DDx executor; agent harness | grep audit on .ddx/agent-logs/, .ddx/exec-runs.d/; pre-commit secret-scan |
| Prompt injection writes secrets into bead body | Bead bodies are operator-curated; agent output is captured in execution evidence with size/shape checks | ddx agent execute-bead write path | Review-stage helix review reads recent execution evidence; pre-commit hook flags suspicious patterns |
| Stale claim leaves work locked indefinitely | Orphan-recovery sweep with dead-PID + age threshold (HELIX_ORPHAN_THRESHOLD, default 7200s) | ddx bead claim subsystem | TP-002 test cases cover orphan recovery |
Concurrent writers tear .ddx/beads.jsonl | ddx bead is the single sanctioned writer; per-record file locking | ddx bead CLI | Tracker-mutation tests in TP-002; ADR-002 governs the write-safety model |
| Operator runs untrusted bead body | Beads are repo-scoped artifacts under the operator’s review; supervisor stops on ambiguity | helix run supervisor; helix review | Stop-for-guidance tests; bounded-action contract |
| Cross-model verification bypass | helix run --review-agent <other> routes review to a second model when configured | helix run review pass | TP-002 cross-model review test |
| Tailscale identity drift | Tailscale ACLs are the only authorization for non-loopback access | tsnet sidecar config | Operator-owned tailnet ACL; verified by tailnet admin, not by the service |
Identity and Access
- Authentication: No service-level password or OAuth surface. Local
access is loopback-only and authenticated by UNIX user identity. Remote
access (opt-in) is authenticated by Tailscale node identity through the
tsnetsidecar; the service trusts the tailnet’s ACL. - Authorization: Authority order is the effective authorization
boundary inside HELIX (vision > requirements > design > code per the
authority order). At the service layer, write operations are gated by
filesystem permissions on
.ddx/. The supervisor stops for guidance when required authority is missing rather than escalating privilege. - Session or token handling: Model-provider API keys are session-scoped
to the running
ddx-serverprocess. Keys are read from environment at startup and held in process memory only. No persistent token store; no refresh flow; no on-disk credential cache.
Data Protection
- Data at rest:
.ddx/beads.jsonland.ddx/exec-runs.jsonlare plain JSONL under git. They are public-by-default and must not contain secrets..ddx/exec-runs.d/<run-id>/contains prompt, result, and embedded transcripts. Same public-by-default rule applies..ddx/agent-logs/holds raw harness output. Listed in.gitignorefor runtime state; pre-commit hooks block accidental commits.
- Data in transit:
- Model-provider calls use TLS (provider-enforced).
- Loopback HTTP is plaintext but constrained to
127.0.0.1. - Tailscale traffic is WireGuard-encrypted end-to-end.
- Secrets and key handling:
OPENROUTER_API_KEY,ANTHROPIC_API_KEY, and similar provider keys are sourced from the operator environment (typically a shell rc file ordirenv), never from a repo-tracked config.- The service does not echo keys into stdout, stderr, or any file under
.ddx/. - Operator rotation is out-of-band: revoke at the provider, update env,
restart
ddx-server.
Logging and Monitoring
- Security events: Worker process start/stop, claim acquisitions, claim releases, orphan-recovery sweeps, model-call failures (with provider name but never request bodies), and any non-loopback bind attempts.
- Alerting: This is a developer-local service; alerting is operator-
driven through
helix statusandddx server workers list. There is no paging surface — the operator is the on-call. - Audit trail:
.ddx/exec-runs.jsonlis the canonical execution audit log. Each run has a stablerun-id, governing-artifact references, and a durable evidence directory. Theevents[]array on each bead provides a per-bead audit trail of state transitions.
Residual Risk
- Operator-introduced secrets in bead bodies: The contract is “no
secrets in beads,” but enforcement is contract + pre-commit, not service-
level. A determined operator can still leak. Mitigation is education and
pre-commit secret scanning; the service does not (and cannot, given the
free-form
descriptionfield) prevent it. - Supply-chain risk on model providers: Prompt and (potentially) governing-artifact text leaves the host. Mitigation is operator choice of provider and provider-side data-handling agreements; not solved at the HELIX layer.
- Tailnet ACL misconfiguration: When the operator opts into the
tsnetsidecar, exposure is governed by tailnet ACLs that HELIX does not own. Mitigation is the loopback-default posture: opting in is an explicit operator action.
Security Test Hooks
- TP-002 covers tracker write-safety: claim atomicity, orphan recovery, unclaim semantics, and supersession.
- TP-002 covers stop-for-guidance: the supervisor must stop rather than escalate when authority is missing.
tests/validate-skills.shvalidates plugin-layout integrity (skills do not import from outside the package boundary).- Pre-commit hooks run secret-pattern scans (
detect-secrets-style) and the workflow-paths check that forbids bareworkflows/references in documents. - The
ddx-serverintegration tests assert that the default bind is127.0.0.1:7743and that any non-loopback bind requires an explicit flag.