Enterprise

Security model

The six security layers every request passes through, deny-by-default.

Maturity: stable (85 %)

Enterprise security and governance. Every request, from a person, an agent, a skill script, a scheduled run or an external MCP client, passes the same deny-by-default layers: one session identity, project membership, role and feature grants, path policy, package trust and audit.

  • Give the host access plane only to a single trusted operator's machine.
  • A package added to the host dependencies runs with full first-party trust; vet it like any dependency.
  • Login lockout is kept per process; a multi-node deployment gets proportionally more login attempts.
  • Encrypted credentials depend on the master key file; back it up with the data directory.

How maturity is measured

The security model is the spine of Neuralis, not a hardening guide bolted on afterwards — the platform exists to let AI agents do consequential work, and that is only acceptable if every actor is governed the same way. Neuralis is built for multi-tenant deployments where most users are employees of an organization, signing in through admin-configured auth. Every request — a human in the browser, an agent tool call, a skill script, a scheduled workflow run, an external MCP client — passes through six layers, and every layer denies by default:

  1. Authentication — verified identity; synthetic system identities are rejected at external boundaries. Example: registration is closed — user accounts exist only because setup or an admin created them — failed logins lock that account from that address after five tries, an address that sprays many accounts is slowed down, and the address is the connecting socket unless you declare your reverse proxy (see deployment). A disabled or deleted account is refused on its next request or connection everywhere — browser, MCP client, terminal socket or scheduled run — and the external MCP endpoint refuses any token whose claims name an internal system identity.
  2. Project membership — non-members cannot even infer that a project's packages, agents, sources, or runs exist. Example: workspace snapshots are filtered server-side before they leave the host, and a member removed mid-stream loses access on their agent's next call, not at next login (see multi-tenancy).
  3. Role / feature grants — every route and tool declares the feature it requires; checks happen server-side before data access. Example: mutating a credential requires project.credentials.write, and doing so outside your own scope additionally requires platform.scope, which no role holds by default — hiding the UI tab is never the defense (see roles and features).
  4. URI policy — per-package path-protection rules gate the filesystem at four layers: route, UI, tool (including the sandboxed shell), and the vector index. Example: a shell command's working directory and absolute path arguments are evaluated against the owning source's policy before the process starts; a path no policy covers is denied — and a path an immutable floor makes unreadable is never newly embedded into the search index either (see URI policies).
  5. Trust tier — first-party, trusted, and untrusted packages get different runtime bindings; untrusted code runs in a WASM sandbox. Example: a package dropped into a project's _packages/ directory loads untrusted until a project owner explicitly raises its trust — and even then it never becomes first-party.
  6. Audit — privileged operations and denials are recorded. Example: logins (including failures and rate-limit hits), credential writes, package trust changes, and every skill-originated API call land in an append-only audit log (see administration).

One session identity

Caller identity — user, project, agent, role, granted features — travels as a single canonical SessionContext. Skill scripts calling back into the host authenticate with a short-lived per-stream session ticket minted from that context; there is no parallel admin-token or service-account path, so a skill can never act with more authority than the user whose session activated it. The ticket lives exactly as long as its stream: when the stream ends, every ticket bound to it stops verifying. Failed verifications are audited with the failure reason. The same single-identity rule holds at the MCP boundary — external callers cannot override their scope with headers (see MCP access).

What the model sees is filtered too

System-prompt contributions — skills, instructions, rules, agents, docs, and the package overview — inherit both their package's enable/disable state and the caller's role/feature grants. A capability the caller cannot use is not rendered as "locked"; it is simply absent. An owner-only skill is not listed, not injected, and not activatable from a member's stream, and a package with no surface visible to the caller disappears from the package overview entirely.

Secrets

Long-lived secrets live in an encrypted credential store, scoped four ways (platform, user, project, agent) and resolved most-specific-wins. Skill scripts receive only the credentials they declare, projected into the child process environment; the shell environment is otherwise sanitized of token-like variables. The credential API never returns stored values — only whether a value exists. See credentials.

A member self-service write cannot reach another scope. The ports behind the member-facing flows — connecting your own chat channel, storing your own git token — take a user id and nothing else. There is no scope argument for the caller to supply, so "write this to the platform scope" is not an operation those paths can express: it is unrepresentable rather than merely denied, and each is additionally restricted to the credential id shapes its flow owns.

That structural property covers the self-service family, not every write in the system. Administrative writes (the admin credentials surface) and the outbound MCP token store do take an explicit target scope — they are ordinary policy-gated paths, guarded by a feature grant plus a scope-ownership check, and like any policy they can be misconfigured. Imported and untrusted packages have no write path at all.

Declaring a credential in a manifest is likewise not a grant. It adds a row to the catalog so an owner can give it a value; it confers no authority, and reading still resolves against the caller's real scope. The reverse direction is closed too: the id namespaces reserved for those self-scope writes can only be declared by first-party packages, so an imported package cannot present itself as the owner of a platform credential.

On this page