This document is the single source of truth for Tix security boundaries, trust flows, pairing behavior, and recommended production posture.
- Protect node administrative surfaces from unauthorized access.
- Keep node trust and agent/channel binding as separate concerns.
- Limit browser and network attack surface by default.
- Keep audit trails attributable to authenticated actors, not client-claimed fields.
- Make the secure path the default path for operators and users.
In-scope attacker classes:
- Remote network client reaching Tix HTTP or WebSocket endpoints.
- Browser-based attacker attempting cross-origin access to node APIs.
- Client attempting to spoof sender identity on
/runsor WebSocket message paths. - Unpaired channel/user attempting to talk to an agent without authorization.
- Operator misconfiguration exposing trust or admin endpoints without strong controls.
- Malicious or compromised skill attempting filesystem escape or secret exfiltration.
Assumptions:
- Host OS and runtime are not already fully compromised.
- Operators control deployment topology and can set environment variables.
- Gateway, proxy, or ingress may add extra transport protections, but node security must still stand on its own.
Tix has two distinct security layers:
A node is the machine-side Tix runtime. In this document, node and device refer to the same security object.
Node security answers two separate questions:
- Who may operate the node API?
- Answered by token-based node admin access.
- Is this node trusted by the control plane/system?
- Answered by enrollment / pairing / trust-state transitions.
An agent is the conversational/service identity exposed to users across channels.
Agent access security answers:
- Which users/chats/channels are allowed to talk to this agent?
- Which identities are bound to which agent?
- What should happen when an unbound identity first contacts the agent?
This layer is handled by agent pairing / channel binding, not by node trust.
Node HTTP/WS management surfaces are protected by a node-level token mechanism such as HTTP_API_KEY.
This protects operations like:
- node HTTP API access
- admin WebSocket auth
- schedules, sessions, skills, enrollment token issuance
A node does not become trusted by calling a generic “trust me” endpoint.
Instead, trust is established through controlled enrollment/pairing and represented by trust state.
A user or chat may not talk to an agent merely because it can reach the node.
Instead, the channel identity must be explicitly bound to the target agent, typically via a pair-code or equivalent approval flow.
If a channel identity is not paired/bound to an agent, it should not enter standard chat flow.
Its allowed behavior should be limited to:
- requesting a pair code
- receiving pairing instructions
- waiting for approval/binding
External users/channels
-> Channel identity boundary
-> Agent binding / pairing boundary
-> Tix agent conversation boundary
-> Node HTTP/WS admin boundary
-> Task computer boundary
-> Local filesystem (~/.tix)
|
+-> Node enrollment / trust boundary
+-> Optional Hub / control-plane boundary
Primary protection boundary:
- Tix node process and data under
~/.tix/.
Secondary boundaries:
- Node enrollment and trust-state lifecycle.
- Agent/channel binding lifecycle.
- Hub/control-plane transport when enabled.
- Agent workspaces under configured allowed roots.
Admin-protected surfaces:
- All
/api/*endpoints. POST /runs.- WebSocket
authhandshake used for interactive HTTP/WS clients.
Authorization behavior:
- If
HTTP_API_KEYis configured:- Requests must present a valid key via
X-API-KeyorAuthorization: Bearer <key>. - WebSocket
authmust includeapi_key.
- Requests must present a valid key via
- If
HTTP_API_KEYis not configured:- Admin surfaces are restricted to loopback clients only (
127.0.0.1,::1,::ffff:127.0.0.1).
- Admin surfaces are restricted to loopback clients only (
Operational guidance:
- Loopback fallback is only for local development.
- Any non-local or production deployment should set
HTTP_API_KEY. - For future dynamic-node deployments, a per-node token model is preferred over a single shared static token.
Node trust is a state machine, not a generic admin toggle.
Trust states:
discovered_untrustedtrustedsuspendedrevoked
Allowed trust operations:
- Issue an enrollment token via
POST /api/enroll/token(admin protected). - Verify the enrollment token via
POST /api/enroll/verify. - Revoke or suspend via admin-protected enrollment endpoints.
Direct trust elevation:
POST /api/v1/node/trust/POST /api/node/trustis deprecated and should not be used for production trust transitions.- The preferred and documented path is enrollment/pairing.
A node manually started on a local machine is fully within scope of this model.
Expected flow:
- Start the node locally.
- Perform a manual enrollment/pairing step with the control plane.
- Approve or verify the node.
- Persist node identity locally.
- Continue operating with normal trusted-node behavior.
This means “manually joining the control plane” is part of the intended enrollment model, not a special exception.
For dynamic or ephemeral node deployments, the long-term preferred model is:
- enrollment establishes trust
- control plane issues a node-scoped token
- token is bound to node identity
- node refreshes or rotates token automatically
Preferred properties of a node token:
- per-node, not globally shared
- revocable
- auditable
- short-lived or refreshable
- bound to stable node identity and, where appropriate, node fingerprint
A recommended future model is:
- Node enrolls.
- Control plane marks node trusted.
- Control plane issues node token.
- Node uses that token for node-admin/API access.
- Token rotates or refreshes automatically.
Node trust does not imply that every channel/user may talk to every agent.
Agent access must be modeled separately.
An agent may be bound to:
- multiple channels
- multiple identities within the same channel
- optionally, group/chat identities as distinct bindings
Examples:
- one agent bound to one Feishu user and one Telegram user
- one agent bound to multiple Feishu users
- one agent bound to a specific Feishu group chat
If a channel identity is not bound to the target agent, it should not get normal conversation access.
- An unbound user or chat sends an initial message.
- Tix detects that the identity is not bound to an agent.
- Tix returns a pair code or pairing instruction.
- The owner/admin completes pairing in a trusted control surface.
- The channel identity becomes bound to the target agent.
- Normal conversation access is enabled.
This mirrors the OpenTix-style model where an initial greeting can generate a pair code that must be completed from a trusted surface.
A single agent may be paired with multiple users from the same channel.
Example:
feishu:ou_a -> agentAfeishu:ou_b -> agentAfeishu:ou_c -> agentA
This is allowed and considered a normal use case.
- multiple users may bind to the same agent
- conversations remain isolated by default
- management rights remain restricted to owner/admin roles
This means:
- shared agent
- separate user histories by default
- explicit shared/group behavior only when intentionally configured
A group or chat binding should be treated as a distinct kind of binding, not merely as several user bindings.
Examples:
feishu:chat_xxx -> agentAdiscord:channel_yyy -> agentA
This keeps direct-user bindings and shared-chat bindings conceptually separate.
Client-provided sender identity is not authoritative.
For trusted HTTP/WS entry paths:
- message actor identity must be derived from authenticated context
- client-provided fields such as
senderandsender_namemust be ignored or treated as untrusted display-only input
Current implementation direction:
/runsand authenticated WebSocket message handling derive sender identity from the authenticated HTTP admin context- this preserves audit integrity and avoids spoofed actor attribution
CORS policy is deny-by-default.
Behavior:
- if
ALLOWED_ORIGINSis configured, only matching origins are echoed back - if
ALLOWED_ORIGINSis empty or invalid, Tix does not emit wildcardAccess-Control-Allow-Origin: * - credentials are not broadly enabled for arbitrary origins
Implication:
- a browser frontend must be explicitly allowlisted
- sensitive node APIs should not be reachable cross-origin from arbitrary websites
Implemented controls:
- structured logging redaction for common secret/key/token fields
- child-process environment minimization:
- only base runtime keys + known skill secret patterns + optional
CHILD_ENV_ALLOWLIST
- only base runtime keys + known skill secret patterns + optional
- avoid logging API key fragments in routine lifecycle logs
Path safety controls:
- agent/session/schedule identifiers validated as safe path segments
- path resolution enforced inside expected roots
- workspace access constrained to the agent workspace root
- workspace roots constrained by
WORKSPACE_ALLOWED_ROOTS
Skill scope:
- managed skill operations blocked for out-of-root paths
- managed skill enable blocked on content hash drift diagnostics
- computer links only in-scope skill directories
Outbound endpoint controls:
- ACP and Hub client URLs are validated before connect
- allowed protocols enforced (
https/wssby default unless explicitly overridden) - optional host allowlist via
SECURITY_TRUSTED_REMOTE_HOSTS
Inbound hub controls:
- optional allowlists for enrolling/authenticating nodes:
HUB_ALLOWED_NODE_IDSHUB_ALLOWED_NODE_FINGERPRINTS
At minimum, configure:
HTTP_API_KEYALLOWED_ORIGINSSECURITY_TRUSTED_REMOTE_HOSTSWORKSPACE_ALLOWED_ROOTSCHILD_ENV_ALLOWLIST(minimal, explicit)HUB_ALLOWED_NODE_IDSand/orHUB_ALLOWED_NODE_FINGERPRINTSwhen Hub is used
Use this rule of thumb:
- local-only development: loopback fallback is acceptable
- anything reachable by another machine, browser, tunnel, reverse proxy, or public network: set
HTTP_API_KEYand explicitALLOWED_ORIGINS
Unsafe operator assumption to avoid:
- “the node is behind some other layer, so built-in auth is optional”
Recommended stance:
- built-in node auth is the primary protection
- private networking / reverse proxies / VPN / gateway auth are defense-in-depth
Tix currently allows a local-development fallback when HTTP_API_KEY is unset:
- admin/API endpoints are restricted to loopback clients only
- the HTTP listener binds to
127.0.0.1in that mode - this is intended only for same-machine development workflows
This should be treated as an explicit dev-only mode, not a production deployment strategy.
If an operator chooses to expose the node beyond localhost without HTTP_API_KEY, that is an unsafe configuration.
Recommended examples:
HTTP_API_KEY=<long-random-secret>
ALLOWED_ORIGINS=^https://(app\.example\.com|admin\.example\.com)$
SECURITY_TRUSTED_REMOTE_HOSTS=hub.example.com,api.example.com
WORKSPACE_ALLOWED_ROOTS=/srv/tix/workspaces
CHILD_ENV_ALLOWLIST=GITHUB_TOKEN,RENDER_API_KEYThese are the intended next-step directions and should guide implementation:
- require token auth for all node admin/API surfaces
- keep loopback-only fallback strictly for local dev
- remove wildcard CORS and require explicit origin allowlists
- derive actor identity from authenticated context, not payload fields
- keep node trust transitions on enrollment/pairing flows
- avoid generic direct trust-promotion endpoints
- add stronger audit logs around enrollment token issuance, verification, revoke, and suspend
- support local manually started nodes through explicit approval/enrollment flow
- introduce explicit agent/channel binding records
- require unbound identities to complete pair-code flow before conversation
- support multiple users bound to one agent
- support group/chat bindings as first-class records
- default to per-user conversation isolation
- move toward per-node tokens rather than shared static tokens
- bind tokens to node identity and lifecycle
- support rotation, revocation, and refresh
- add auditability around issuance and use
- signed enrollment assertions or stronger attestation
- optional mTLS for hub/node deployments
- security regression tests for auth, CORS, spoofing, trust, binding, and path escape
- more explicit egress/network controls per skill/runtime
- Loopback fallback is still a development convenience.
- Production deployments should always set
HTTP_API_KEY.
- Enrollment is stronger than direct trust toggles, but still benefits from stronger attestations.
- Signed proofs or mTLS remain future hardening steps.
- Agent pairing/channel binding is now the intended model, but may not yet be fully enforced across every channel path.
- Until fully implemented, some channel entry points may still rely on legacy assumptions.
- Skills are policy-constrained, not fully sandboxed.
- Strong multi-tenant isolation still requires OS/container controls.
- CORS is not an auth mechanism.
- Browser allowlists reduce exposure but do not replace token-based auth.
If compromise is suspected:
- Isolate the node/hub from the network.
- Set node trust state to
revokedorsuspended. - Rotate
HTTP_API_KEY, channel keys, LLM keys, and any skill secrets. - Review:
~/.tix/security/enrollment-state.json~/.tix/skills/registry.json~/.tix/skills/audit.log~/.tix/agents/*/sessions/*/messages.jsonl
- Re-enroll nodes and re-enable only required skills.
- Review agent/channel bindings and remove any unauthorized pairings.
Last updated: March 21, 2026