Security review · NVIDIA OpenShell
OpenShell connect, reviewed from the code.
ArtzAIn, the model-agnostic agent control plane, checks every governed change your OpenShell gateway makes before it commits, and signs each decision into a ledger nobody can quietly edit. This page tells your security team what ArtzAIn does on your host, what leaves it and where it goes, how the credentials work, and what happens when something fails or is attacked. We wrote every answer from the shipped code.
- Release covered
- artzain 0.6.42: the sidecar, the connect command and the connect script
- Sidecar image
- sha256:93a8d5dc…63193
- OpenShell approved by default
- 0.1.2
- As of
- 2026-10-09
01 · At a glance
ArtzAIn decides and records. OpenShell enforces.
Your OpenShell gateway calls a small ArtzAIn sidecar, running beside it on your host, before it commits a governed write. The sidecar asks the hosted ArtzAIn engine for a decision. ArtzAIn evaluates your policy bundle and seals the decision as a signed, hash-chained receipt, so you can prove later what was allowed and why. The sidecar then tells your gateway to commit the write or refuse it.
| Promise | Status | As built |
|---|---|---|
| ArtzAIn never reaches into your network | Holds | The sidecar only calls out, over HTTPS, to one ArtzAIn origin. Nothing calls in. It listens on a Unix socket for your gateway and on a loopback port for its own command-line tool, both on your host. |
| ArtzAIn never sees your prompts or secrets | Holds, with limits | It sees no prompts. The sidecar drops every key named like a secret before it builds a decision. A secret you place in a field with another name, such as a setting value, a policy or a draft's reason, is sent. Section 04 lists every field that goes. |
| No governed change commits without a decision | Holds, with limits | True for the writes the registration binds, on a gateway started with that registration, and every failure refuses the write. A gateway restarted without the registration is not governed: its heartbeat reports the registration missing, and ArtzAIn raises a finding for you. While ArtzAIn cannot answer, your host's user can open a break-glass window: the writes it lets through are journaled first, then receipted and reviewed (section 06). |
The sidecar, the connect command and the connect script are open source under the Apache License 2.0. You can read them at github.com/CogNEXUSlabs/cognexus-tools, under python/src/artzain/openshell/.
02 · Scope
What ArtzAIn does, and does not
ArtzAIn governs the changes your gateway makes to what your sandboxes may do. It governs the action, never the model.
Governed writes
- creating a sandbox, or deleting one;
- changing a sandbox's policy, or a setting;
- approving, rejecting, editing, undoing or clearing a policy draft;
- creating, changing or deleting a provider or its profiles, and rotating its credential;
- attaching a provider to a sandbox, or detaching it;
- exposing a service, or removing one;
- opening or revoking an SSH session.
What ArtzAIn does not do
- Sandboxing, Landlock, seccomp and credential injection stay in OpenShell.
- It does not see prompts, model input or output, or your sandboxes' traffic.
- It does not receive provider credentials, environment variables or file contents.
- It claims no proof for MCP, GraphQL, WebSocket, SQL or JSON-RPC traffic. Its policy prover covers filesystem, process, L4 and REST rules, and refuses to promote a bundle that relies on another protocol.
- It does not run your OpenShell. You run your own gateway, from NVIDIA's deb or rpm package, on Linux x86_64 or aarch64.
03 · Trust boundaries
Two processes on your host, one connection out
Your gateway and the ArtzAIn sidecar run side by side as systemd user services. The only connection that leaves your host is the sidecar's outbound HTTPS to ArtzAIn.
Your host
Linux, OpenShell from its deb or rpm package
ArtzAIn
AWS us-west-2
| Boundary | What crosses it | Control |
|---|---|---|
| Gateway to sidecar | Each governed write, before it commits | A Unix socket, 0600 in a 0700 folder, so only your gateway's user can open it. When your gateway signs its extension calls (gateway_jwt), the sidecar checks each call's Ed25519 token. Without that key, the host user is the boundary. |
| Local processes to sidecar | The sidecar's loopback HTTP routes | A sidecar holding a gateway credential answers nothing but /healthz unless the caller presents the sidecar's own random token, compared in constant time. The connect command writes that token. |
| Sidecar to ArtzAIn | Decisions, reports, heartbeats, your base policy | Outbound HTTPS to one origin. The client refuses any other origin, follows no redirect, and reads at most 256 KiB of an answer. Only the gateway credential authenticates it. |
| Your team to ArtzAIn | Connect requests, approvals, revokes | A signed-in dashboard session. An approval is itself a sealed decision, under separation of duties (section 05). |
04 · Data flows
What leaves your host, and what we keep
Every flow that crosses a boundary, when it happens, and what ArtzAIn keeps from it.
| Flow | When | What is sent | What ArtzAIn keeps |
|---|---|---|---|
| You ask for a connect | Once per gateway | A display name and the configuration: OpenShell release, interceptor and decision timeouts, telemetry choice | The request; the gateway's id, which ArtzAIn assigns (gw_<ulid>); a sealed decision |
| An owner or admin approves | Once | The approval. An account on its own adds a written attestation | A sealed decision |
| ArtzAIn shows you the command | On "Issue the command" | The enroll token, shown once and marked not to be cached | The token's peppered SHA-256 only |
| Your host installs | When the command runs | Downloads only, from GitHub and PyPI: the connect script, uv, and the wheels the script names by SHA-256 | Nothing |
| Your host redeems the token | Once | The enroll token, and the digest of the configuration you approved | When it was used, and from which address. You see both on your dashboard. Deleting your account clears the address |
| The sidecar asks for a decision | Each governed write | Your gateway as the agent (openshell:<gateway>), the action and target, and a payload: the method and the write's facts (sandbox id and name, the policy or the setting key and value, a draft's ids, reason or proposed rule, the provider, service, port and domain). Keys named like a secret are dropped and sizes are capped. No principal, prompt, credential, environment variable or file | The sealed receipt: agent, action, target, verdict, the Artzain crew's votes, the bundle version, and the payload's SHA-256, not the payload |
| The sidecar reports a commit | After a governed create or policy update commits | Sandbox id, policy hash, decision id, method | The projection that drift is checked against |
| Heartbeat | Every minute | Sidecar and OpenShell versions, the installed registration's digest, what gateway.toml holds now, decision latency (p50 and p95), undelivered reports | The latest values |
| Inventory | Every five minutes, and on change | Each sandbox's id, name, phase and effective policy hash | Catalog rows, and drift findings |
| The sidecar fetches your base policy | At start, and every five minutes | Nothing. It receives your team's compiled base policy, its digest and the prover's result | Nothing new |
| OpenShell's own telemetry | Only if you turn it on | OpenShell's own data, to NVIDIA. ArtzAIn only writes your choice into the gateway's settings | Nothing. The sidecar sends nothing to anyone but ArtzAIn |
| ArtzAIn reads OpenShell advisories | Hourly | Nothing about you. ArtzAIn reads NVIDIA's published list | A finding on each of your gateways running an affected release |
| Your host sends its break-glass journal | After a break-glass window, once ArtzAIn answers | Each window's length, reason and opener (your host's user name), and each write it let through: the method, the action and target ArtzAIn would have decided, the payload's SHA-256, the operation digest, the caller's subject id, kind and provider, and the request id | Flagged receipts, not billed; a Review queue item for each window; each window's reason and opener, cleared when you delete your account |
The sidecar logs no payload, target, key or body. It logs a failure by its kind, and logs how it reaches ArtzAIn without the credential.
05 · Credentials
Credentials and approvals
Two credentials, each with one job: a short-lived enroll token that connects a gateway once, and the gateway credential the sidecar uses after that.
The enroll token
cnxt_and 256 random bits. It is valid for 30 minutes and works once.- ArtzAIn stores only its peppered SHA-256, and redeems it under a row lock, so two redeems at once yield one credential.
- It is bound to the digest of the configuration you approved. A different configuration is refused, and the token is not spent. Issuing a new token retires the earlier one.
- The command that carries it is four lines joined by
&&. A failed download, or a script whose SHA-256 is not the pinned one, stops it before the token is exported or anything runs. - It goes to ArtzAIn over HTTPS only. Plain
http://is refused unless the engine runs on the same host.
The gateway credential
cnxg_and 256 random bits: a key kind of its own, stored as a peppered hash. It never works as an account key.- It may call ten routes and no others: decisions, the commit report, announcing the gateway, and, for its own gateway only, its inventory, heartbeat, break-glass journal, base policy, revoke and the two key-rotation routes.
- A decision must name the credential's own gateway as agent and as target. ArtzAIn refuses anything else before the engine runs, so nothing is sealed, whatever your account's identity-binding setting.
- It does not expire. You rotate it (at most two live keys during a rotation) and you revoke it.
On your host
- The credential lives in
~/.config/artzain/openshell/sidecar.env: mode0600, in a0700folder. The sidecar's own token sits beside it. - systemd passes it to the sidecar as an
EnvironmentFile. The sidecar's unit setsUMask=0077andNoNewPrivileges=yes. - Your gateway's binary is asked its version without the credential in its environment.
Revocation
A revoke takes effect on the next call: the gateway's keys get 401. You can revoke three ways:
artzain connect openshell removeon the host, which also restores your gateway's files;- Revoke… on your dashboard;
- deleting your account.
Approval
- On a team, the owner or admin who asked for a connect cannot approve it.
- An account on its own approves with a written attestation.
- The approval is itself a sealed decision in your ledger.
06 · Threat model
What we defend against, and what remains
Each threat, the control as built, and what is left. We list the residuals plainly so you can weigh them.
| Threat | Control as built | What remains |
|---|---|---|
| The enroll token leaks (shell history, a CI log, a screenshot) | 30 minutes, one use, bound to the approved configuration, passed by environment. You see where and when it was used, and can revoke. | Within its 30 minutes, whoever runs it first binds a gateway of their own to that configuration. Spotting and revoking it is yours. |
| The gateway credential is stolen from the host | Ten routes, its own gateway only. Rotate and revoke. A file only its owner reads, and never logged. | Anyone with the host user's access can decide as that gateway until you revoke it. The credential does not expire. |
| A compromised host forges receipts for another gateway or tenant | ArtzAIn assigns the gateway id. Agent and target must be the credential's own gateway, checked before anything is sealed. | None found |
| A local process talks to the sidecar | The socket is your gateway user's alone. The loopback routes need the sidecar's token. gateway_jwt when your gateway signs its calls. | Without gateway_jwt, any process running as your gateway's user can call the socket. That user is the boundary. |
The interceptor is removed from gateway.toml | The heartbeat reports what gateway.toml holds. Anything but the installed registration raises a finding, in your dashboard's bell and by webhook. Policy drift is checked independently, sandbox by sandbox. | A gateway started from another configuration file is not seen this way. Your host's administrator can always unbind their own gateway: ArtzAIn detects it, and cannot prevent it. |
| Automatic approval of policy proposals is turned back on | Any approval mode but manual is denied at any scope, by a vote no bundle can soften. The connect command sets the gateway-wide mode to manual first. | None found |
| Supply chain | See section 09. | The connect script is checked by its SHA-256, not signed. The compatibility manifest is not signed yet. |
| ArtzAIn is down or slow | Every governed write is refused. Running sandboxes keep their policy. Your host's user can open a break-glass window of up to 240 minutes: a write ArtzAIn gave no answer for goes through, journaled first, and is sealed as a flagged receipt and reviewed once ArtzAIn answers. A deny, a review, a rate limit, a refused credential and a gateway-wide write still refuse. | Writes in a window go unchecked until you review them. A host user who rewrites the journal before ArtzAIn sees it can leave writes out: a journal that stops continuing is flagged, and drift still shows each sandbox it changed. |
| Flooding denied writes to run up your bill | Each gateway has its own hourly bucket. Over it, the write is refused. | Within the bucket, denials are billed like any decision. |
| Personal data in the principal | Not sent: the decision carries no principal. | Names, a draft's reason and a proposed rule are your own text, and are sent. |
07 · Failure
When something fails, the write is refused
The registration that decides fails closed. The one that observes, which reports after the commit, fails open and never decides. Our conformance run proves each row below on a real OpenShell gateway.
| Failure | What your gateway is told |
|---|---|
ArtzAIn's verdict is not allowdenyreview | PERMISSION_DENIED, with the decision id |
| ArtzAIn is unreachable, times out, or answers with an error, a redirect or something unreadable | UNAVAILABLE: refused |
| ArtzAIn gives no answer, or a 5xx, while your host has a break-glass window open | Allowed, once the sidecar has journaled it. It becomes a flagged receipt, and the window a review, when ArtzAIn answers |
| The gateway's hourly bucket is used up | RESOURCE_EXHAUSTED, with when to try again: refused |
| The sidecar is down | Your gateway refuses on its own, and will not start while its interceptor does not answer |
| A gateway-wide policy write | Refused by the sidecar, without asking ArtzAIn |
| A sandbox created without a policy while the base policy is unknown | Refused |
| The commit report cannot be delivered | Kept in the sidecar's journal and retried. The write has already committed |
08 · Integrity
Integrity over time
Connecting once is not enough. ArtzAIn keeps checking that your gateway stays bound, and tells you when it does not.
- A silent gateway. Five minutes without a heartbeat raises
openshell_gateway_silent. The next heartbeat resolves it. - The registration. At each heartbeat the sidecar reads
gateway.tomland reports the digest of the registration it finds, or that it ismissingorunreadable. ArtzAIn compares that with the registration the sidecar was installed for, and raisesopenshell_registration_changedwhen they differ. - Drift. Each sandbox's effective policy hash is compared with what ArtzAIn last allowed.
- OpenShell advisories. ArtzAIn reads NVIDIA's published security advisories hourly, and a gateway on an affected release gets a finding. Our response is due within two business days, with a conformance run on the fixed release.
doctor, which you run on your host, checks the registration, the socket's and the credential file's modes, the sidecar's token, the clock, and the route to ArtzAIn.
09 · Supply chain
Supply chain, and how to check it yourself
Every piece that reaches your host is pinned by its hash, and you can check each one yourself.
| Piece | Pinned and checked |
|---|---|
| The connect script | Your dashboard pins its version and SHA-256, and the command checks it before anything runs. The release also carries a .sha256 file beside the script. |
| uv, which the script runs | Its version, and each build's SHA-256, are written into the script. A build with another hash is refused. |
| artzain and its dependencies | Every wheel by its SHA-256, in an environment of the script's own: uv pip install --require-hashes --only-binary :all:. The artzain wheel carries PyPI provenance (PEP 740) from CogNEXUSlabs/cognexus-tools, workflow publish-pypi.yml, environment release. |
| The sidecar image | Its base image is pinned by digest, and every wheel installs by hash. It is signed keyless with cosign by sidecar-image.yml on a sidecar-v* tag, and carries an SPDX SBOM attestation. Built for amd64 and arm64. |
| OpenShell | ArtzAIn does not install it. The connect command checks the installed release is the approved one (same minor line), and our conformance runs pin the deb by SHA-256. |
| Publishing | Every release tag waits for a named approver before anything publishes. |
Check it yourself
For the self-hosted engine's own images, see verifying a release on the install page.
10 · Figures
Figures
Taken from the code of artzain 0.6.42 and the hosted engine. A test holds each figure in our review pack to the code, so a change that makes one wrong fails our build.
| Figure | Value |
|---|---|
| Enroll token | cnxt_ and 256 random bits; 30 minutes; used once |
| Redeems from one address | 30 an hour |
| Gateway credential | cnxg_ and 256 random bits; no expiry; at most 2 live keys |
| Key rotations per gateway | 12 an hour |
| Governed writes per gateway | your plan's hourly rate, at most 600 an hour; bursts up to 10 times the rate |
| Interceptor timeout (your gateway's) | 1500ms |
| Decision deadline (the sidecar's) | 1200ms |
| Heartbeat | every 60s; silent after 300s |
| Inventory | every 300s and on change; at most 200 sandboxes |
| Gateway socket | 0600, in a 0700 folder |
| Answer the sidecar reads from ArtzAIn | at most 256 KiB |
| Decision payload | strings cut to 500 characters, lists to 40 items, nesting to 8; 200000 characters in all |
| A review's approval, remembered | 24 hours, 1024 entries, in memory only |
| uv the connect script runs | 0.8.15, checked by SHA-256 |
| Python of the connect environment | 3.12 |
| Connect script | connect-0.6.42.sh, SHA-256 67741fc70c2893c1044026f86d58db290fd6c2b3bbf91f009a1622f5660c6e4b |
| Sidecar image | sha256:93a8d5dc055a2ab81a120d0692fa119fdd10c043319c81338432a2cc0eb63193 (artzain 0.6.42) |
| OpenShell release approved by default | 0.1.2 |
| Our response to an OpenShell advisory | 2 business days |
11 · Hosting
Where ArtzAIn runs
The engine, your ledger and the key that signs your receipts, as our infrastructure code defines them.
- The engine runs on AWS in us-west-2.
- Its PostgreSQL database is not publicly reachable, is encrypted at rest, keeps 7 days of backups, and has deletion protection.
- The key that signs your receipts is held wrapped by a KMS key, with rotation on.
- Your sidecar reaches the engine through Cloudflare, which ends TLS and applies the WAF and rate limits, and then a load balancer that accepts TLS 1.2 and 1.3. Cloudflare therefore sees what the sidecar sends, in transit.
- Only our Cloudflare zone can reach the engine. Cloudflare presents a client certificate that our own certificate authority signed, and our load balancer refuses any connection without it (mutual TLS). A site in another Cloudflare account that points at our load balancer fails at the handshake, so nobody can route around our WAF and rate limits.
- Our status page, status.cognexuslabs.ai, publishes the decision API's and connect's availability.
12 · Fixed in 0.6.41
What writing this review found, and fixed
Holding this review to the code found five gaps. We fixed each with a test that failed first, and shipped the fixes in artzain 0.6.41 and its sidecar image.
| Gap | Now |
|---|---|
| The sidecar's loopback port answered any local caller, as your gateway | A sidecar holding a gateway credential needs its own token on every route but /healthz |
| The dashboard's command could go on after a failed download or SHA-256 check | Its lines are joined by &&, so a failure stops it before the token is exported |
Removing the registration from gateway.toml raised nothing | The heartbeat reports what the file holds, and ArtzAIn raises a finding |
| The install on a deb or rpm host pinned artzain's version, not any wheel's hash | Every wheel is installed by its SHA-256 |
Smaller items: where a token was used from was not shown, and the address was kept after account deletion; the credential was in the version check's environment; plain http:// engines were accepted | Each fixed |
A gateway you connected with artzain 0.6.40 or earlier keeps working, but does not get these fixes, or break-glass, until you run the current connect script (0.6.42) on its host again, as the gateway's user. It needs no new enroll token:
upfinds the credential it saved.The run installs the hash-locked environment, gives the sidecar its token and its view of
gateway.toml, and restarts the sidecar once, with your gateway. Until then, that sidecar does not report its registration, so ArtzAIn cannot tell you if it is removed.
13 · Open items
Open items, and the independent review
What is not done yet, said plainly.
| Item | State |
|---|---|
| An independent review of enroll and credential handling | PlannedIts scope and questions are below. |
| Signing the connect script and the compatibility manifest | Not yetThe script is checked by its SHA-256. |
gateway_jwt on deb and rpm hosts | The sidecar checks it when your gateway signs. The connect command does not turn it on. |
| Kubernetes, Homebrew, snap and Compose gateways | Not supported yetThe sidecar image is published for them. |
| Gateway credentials expire | No. You rotate and revoke them. |
| OpenShell leaves secret fields out of interceptor calls | Not verified by usThe sidecar strips secret-named keys itself. |
| The address a token was used from | Kept while your account lives, and shown to you. Cleared when the account is deleted. |
Independent review: scope
How a gateway gets and uses its credential:
- issuing, carrying and redeeming the enroll token;
- the gateway credential: minting, its route allowlist and binding, rotation, revocation;
- the sidecar's local surfaces: the socket, the loopback routes and their token;
- the connect script's install path.
Independent review: questions
- Can an enroll token be redeemed twice, or for a configuration other than the one approved?
- Can a gateway credential decide as, report for, or revoke another gateway, or reach any route beyond its ten?
- Can a local user who is not the gateway's user reach the sidecar's decisions?
- Can anything the connect script installs differ from what it names by hash?
- Does any log line, error message or answer carry an enroll token, a credential or the sidecar's token?
- On a team, can the person who asked for a connect also approve it?
Reporting a vulnerability
Email security@cognexuslabs.ai, and please keep an unfixed defect out of public issues. We acknowledge a report within 3 business days and aim for a fix and coordinated disclosure within 90 days. The full policy is SECURITY.md in the cognexus-tools repository.
Next step
Bring this page to your security review.
We walk your security team through it, and answer what it leaves open.