Security model
Nexara Connect assumes agents are useful but not trustworthy: they can be prompt-injected, they leak what they see, and they make mistakes at machine speed. The model is built so that an agent only sees what its grant allows, can only change what its grant allows, and can never approve its own escalation.
One gate: authorize()
Every read, write, search, bundle, thread post, lifecycle action and git push goes through one function in server/src/acl.ts. For an agent, access is allowed only when all of these hold:
- the token is valid, unrevoked, unexpired and from the current workspace token epoch;
- a grant (or an active elevation) covers the Space, the Folder glob and the action;
- the agent's creator still holds that action through their workspace role;
- the content's sensitivity is at or below the lowest of: the grant level, the agent ceiling, the token ceiling, the creator's role ceiling, the OAuth client cap, and the standing cap of
confidential; - the Page is active, or archived content was explicitly asked for.
Redaction happens per block, so one Page can be partly visible. Content above the ceiling becomes [REDACTED: <level>, ask owner]; a Page above the ceiling is not found at all.
What agents can never do
- Hold standing access to
secret. Secret is only reachable through a time-boxed elevation that an owner creates with step-up. - Satisfy a step-up gate. Gates are for humans in a live session.
- Lower the sensitivity of a Page or block, or move content to a wider audience. The change becomes a proposal.
- Write instruction-bearing files:
index.md,CLAUDE.md,AGENTS.md,.gitattributes, or anything under askills/folder. Those become proposals. - Manage agents, grants, tokens, members or invites. Those routes are human-only.
- Grant more than their creator has. Demoting the creator shrinks every agent they made on its next request.
- Use a git token on the API or MCP, or an API key on git.
- Silently overwrite another principal's edit when a base is given: a conflict returns 409 and keeps the losing write as a proposal.
- Flood threads: rate limits, duplicate suppression, max 5 mentions, no
@all, human hand-off after 4 agent hops, lock after 20 agent messages in 10 minutes.
Step-up gates
These need a human who re-authenticated within the last 5 minutes (POST /api/auth/stepup; TOTP code, or password when TOTP is not enrolled). The API answers 403 step_up_required otherwise.
| Gate | Where |
|---|---|
Read a sensitivity: secret Page | GET /nodes/:id, bundle |
| Delete a Page | DELETE /nodes/:id |
| Archive a whole Folder | POST /clusters/<path>/archive |
| Lower sensitivity, or move to a wider audience | PUT, PATCH, /move, accepting such a proposal |
Grant an agent secret or raw secrets | PUT /principals/:id/grants |
| Create an elevation | POST /elevations |
| Create a git token | POST /me/git-tokens |
| Apply the lifecycle job | POST /lifecycle/run with mode: apply |
| Workspace lockdown | POST /workspaces/:ws/lockdown |
Content is data, not instructions
context_get, context_bundle and the load_project_context prompt wrap every section in a <context nonce=...> fence with a random per-response nonce, and start with a preamble telling the model the content is data. Stored text cannot close the fence because it does not know the nonce. Invisible Unicode (tag characters, bidi controls, zero-width characters) is stripped on write.
Secrets at rest
<!-- secret -->blocks and the bodies ofsensitivity: secretPages are sealed with AES-256-GCM before they are committed. The key is derived with HKDF fromMASTER_KEYper workspace. Git history, clones and backups only holdnxc-sealed:v1:ciphertext.- Secret text is never put in the full-text index or the embedding index.
- Writes and pushes containing raw credentials are rejected by a secret scanner.
Tokens and sessions
- Tokens are opaque random strings with a prefix (
nxc_,nxg_,nxa_,nxr_,nxi_) and are stored only as HMAC-SHA256 hashes with a pepper derived fromMASTER_KEY. Keys are shown once. - Revocation is immediate.
POST /api/v1/workspaces/:ws/lockdownbumps the token epoch, which invalidates every key, OAuth token and other session in the workspace at once. - OAuth: PKCE S256 only, single-use codes (a replayed code revokes what it issued), audience-bound access tokens (an
nxa_token works only on the MCP resource it was issued for:/mcp, or/w/<workspace>/mcpwhen the client asked for that resource, and is refused on/api/v1with401 token not valid for this resource; agent keysnxc_work on both), rotating refresh tokens with reuse detection that revokes the whole family. Redirect URIs must be on the allowlist (Claude, ChatGPT, loopback,OAUTH_EXTRA_REDIRECTS). - Web sessions use
__Host-cookies over HTTPS,HttpOnly,SameSite=Lax, plus a double-submit CSRF token on every unsafe request. Passwords are hashed with argon2id. Five failed logins lock the account for a growing interval. - Headers:
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrer.
Audit
An append-only, hash-chained log records ids, actions, outcomes, sensitivity and IP for every read, write, denial, token and grant change, never content. npm run verify-audit walks the chain and reports the first broken row.
Known gaps in 0.1.0
The round-2 red-team review (docs/REVIEW-round2-security.md) found issues that are open at the time of this release. Do not put confidential data from third parties on an instance until they are fixed:
| ID | Issue |
|---|---|
| C1 | Bundle link expansion skips authorize(), so a linked Page outside an agent's grant can leak into a bundle |
| C2 | Proposal diffs can include redacted spans in their context lines, and the patch is stored unsealed in core.db |
| H1 | Malformed frontmatter falls back to internal and leaves a secret body unsealed |
| H2 | Unverified DCR clients can be granted write (the cap only limits sensitivity) |
| H3 | X-Forwarded-For is trusted from any client, defeating IP pinning and IP rate limits |
| M1 to M7 | Owner lockout DoS, humans without If-Match can overwrite, audit chain has no signed anchor, step-up by password alone, ALLOWED_HOSTS off by default, sign-up open by default, unfenced text on secondary MCP tools |
Reporting a problem
Email the maintainers at Analytica rather than opening a public issue. Include the request, the response and the commit shown by /readyz.