Octelium is a free and open source, self-hosted zero trust access platform. An agent gets an identity of its own rather than a copy of someone's credentials, reaches resources through a proxy that holds the secrets, has each tool call and each inference request authorized on its own, and leaves an audit trail that names it.
Unified identity management for humans, workloads and AI agents
Adaptive authentication for humans and non-human identities
Identity-based, application-layer-aware access control
OpenTelemetry-native real-time visibility and auditing
Unified access architecture for private and SaaS resources
Every request from a human, a browser, a CLI, a workload or an AI agent is authenticated against your identity providers, authorized per request at the application layer by the Octelium Service in front of the resource, exported to your SIEM and observability stack whether it is allowed or denied, and only then routed to the upstream HTTP API, database, SSH server, Kubernetes cluster, LLM or MCP server.
The platform
A unified secure access platform
Agents are covered by the same six as everyone else. Each one is described in detail below, in the same order.
Each resource an agent uses, whether a model provider, an MCP server, an internal API or a database, is represented by a Service behind an identity-aware proxy. Agents reach them with a client or without one, under the same Policies.
A client running on this device is asking to log in to acme.com. Approve it only if you have just started a login yourself.
Approve
Reject
Sign in with a passkey
acme.com
J
jane@acme.com
Passkey
Touch the sensor to continueVerifying…Verified
Cancel
~
~❯
New Tab
Search or type a URL
grafana
argocd
jenkins
kibana
Signing in once, then reaching Services with and without a client
A browser opens grafana.acme.com and is redirected to the Cluster sign-in page at acme.com/login.
jane signs in with a passkey verified by a biometric on the device, and the browser returns to the Grafana dashboard at grafana.acme.com with no client installed.
In a terminal, octelium connect -d opens the browser at acme.com/callback/success/approval, where the same browser session approves the login with one click.
~ ❯ octelium connect -d
Please authenticate yourself using Octelium web Portal
Waiting for you to approve the login in your browser. Press Ctrl-C to cancel.
You are now authenticated as jane
Octelium has started running in detached mode.
~ ❯ curl -s my-api/v1/users/42 | jq
{
"id": 42,
"name": "Jane Doe",
"email": "jane@acme.com"
}
~ ❯ psql -h pg-prod -d shop
psql (17.4)
Type "help" for help.
shop=> SELECT * FROM orders LIMIT 3;
id | status | total
-----+---------+--------
981 | paid | 129.00
982 | shipped | 48.50
983 | paid | 310.25
(3 rows)
shop=> \q
~ ❯ ssh bastion
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-1021-aws x86_64)
Access control
Layer-7 aware access control with policy-as-code
Access is authorized on a per-request basis, using CEL or Open Policy Agent, against the identity behind the request and the content of the request itself. For an agent that means the tool it is calling and the model it is asking for, not merely the endpoint it reached.
jane, a member of the finance Group, runs curl -i billing/invoices/42 and gets HTTP/1.1 200 OK Content-Type: application/json {"id":42,"status":"paid","total":1290}. The access log records ALLOWED GET /invoices/42 with POLICY_MATCH.
jane, a member of the finance Group, runs curl -i -X DELETE billing/invoices/42 and gets HTTP/1.1 403 Forbidden X-Octelium-Unauthorized: true Content-Length: 0. The access log records DENIED DELETE /invoices/42 with NO_POLICY_MATCH.
LLM: the LLM Service openai is configured as follows.
kind: Service
metadata:
name: openai
spec:
mode: LLM
config:
authorization:
inlinePolicies:
- spec:
rules:
- effect: ALLOW
condition:
any:
of:
- match: '"ai-admins" in ctx.user.spec.groups'
- all:
of:
- match: '"ai-agents" in ctx.user.spec.groups'
- match: ctx.request.llm.operation == "GENERATE"
- match: ctx.request.llm.model == "gpt-5-mini"
support-bot, a member of the ai-agents Group, runs curl openai/v1/responses --json '{"model":"gpt-5-mini","input":"Hello"}' and gets { "id": "resp_68e8e1f0c4d0", "object": "response", "created_at": 1791628935, "status": "completed", "model": "gpt-5-mini-2025-08-07",. The access log records ALLOWED RESPONSES gpt-5-mini with POLICY_MATCH.
support-bot, a member of the ai-agents Group, runs curl openai/v1/responses --json '{"model":"gpt-5","input":"Hello"}' and gets {"error":{"message":"Octelium: unauthorized request","type":"permission_error","param":null,"code":"octelium_unauthorized"}}. The access log records DENIED RESPONSES gpt-5 with NO_POLICY_MATCH.
Kubernetes: the KUBERNETES Service k8s-prod is configured as follows.
kind: Service
metadata:
name: k8s-prod
spec:
mode: KUBERNETES
config:
authorization:
inlinePolicies:
- spec:
rules:
- effect: ALLOW
condition:
all:
of:
- match: '"sre-agents" in ctx.user.spec.groups'
- match: ctx.request.kubernetes.verb in ["get", "list"]
ops-agent, a member of the sre-agents Group, runs kubectl get configmaps -n production and gets NAME DATA AGE app-config 4 12d feature-flags 2 3d kube-root-ca.crt 1 12d. The access log records ALLOWED list configmaps · production with POLICY_MATCH.
ops-agent, a member of the sre-agents Group, runs kubectl get secrets -n production and gets Error from server (Forbidden): Octelium: Unauthorized request. The access log records DENIED list secrets · production with NO_POLICY_MATCH.
Secretless access
Credentials an agent never holds
Application-layer credentials are stored in the Cluster as Secrets and injected into the upstream connection once a request is authorized. This is the part of the problem that grows fastest with agentic systems, because the number of non-human identities holding credentials grows faster than the number of people.
Downstream Authorization, X-Api-Key and Api-Key credentials are removed before proxying, so an agent's own token is never forwarded to a model provider or an MCP server.
Upstream credentials injected per request by identity in the HTTP, SSH and PostgreSQL modes
HTTP: the HTTP Service stripe proxies https://api.stripe.com and picks the upstream credential of every request with dynamic configuration rules.
elena, a member of the finance-eu Group, runs curl -s stripe/v1/account | jq '{id, country}' without holding any credential. The rule "finance-eu" in ctx.user.spec.groups selects the eu config, so Octelium injects the Secret stripe-eu and the upstream authenticates the request as acct_1Pq8EuGmbH2bXvK3. The terminal shows { "id": "acct_1Pq8EuGmbH2bXvK3", "country": "DE" }
pay-agent, a member of the finance-us Group, runs curl -s stripe/v1/account | jq '{id, country}' without holding any credential. The rule "finance-us" in ctx.user.spec.groups selects the us config, so Octelium injects the Secret stripe-us and the upstream authenticates the request as acct_1Nf2UsQ7cLm4RtW9. The terminal shows { "id": "acct_1Nf2UsQ7cLm4RtW9", "country": "US" }
SSH: the SSH Service web-01 proxies ssh://10.0.4.21 and picks the upstream credential of every request with dynamic configuration rules.
sam, a member of the sre Group, runs ssh web-01 without holding any credential. The rule "sre" in ctx.user.spec.groups selects the root config, so Octelium injects the Secret root-key and the upstream authenticates the request as root. The terminal shows Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.14.0-1015-aws x86_64) root@web-01:~# id uid=0(root) gid=0(root) groups=0(root)
noah, a member of the developers Group, runs ssh web-01 without holding any credential. The rule "developers" in ctx.user.spec.groups selects the deploy config, so Octelium injects the Secret deploy-key and the upstream authenticates the request as deploy. The terminal shows Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.14.0-1015-aws x86_64) deploy@web-01:~$ id uid=1001(deploy) gid=1001(deploy) groups=1001(deploy)
PostgreSQL: the POSTGRES Service orders-db proxies postgres://10.0.2.8:5432 and picks the upstream credential of every request with dynamic configuration rules.
priya, a member of the backend Group, runs psql -h orders-db orders without holding any credential. The rule "backend" in ctx.user.spec.groups selects the rw config, so Octelium injects the Secret pg-rw and the upstream authenticates the request as app_rw. The terminal shows psql (17.2) Type "help" for help. orders=> UPDATE orders SET status = 'shipped' WHERE id = 42; UPDATE 1
bi-agent, a member of the analytics Group, runs psql -h orders-db orders without holding any credential. The rule "analytics" in ctx.user.spec.groups selects the ro config, so Octelium injects the Secret pg-ro and the upstream authenticates the request as app_ro. The terminal shows psql (17.2) Type "help" for help. orders=> UPDATE orders SET status = 'shipped' WHERE id = 42; ERROR: permission denied for table orders
Identity
One identity model for humans, workloads and AI agents
Human and workload Users share the same identity management, authentication, access control and visibility model. An agent is not a separate system to administer, and it is not a person's session being reused by a program.
Every request produces an AccessLog identifying the User and Session alongside the application-layer detail of the request itself, emitted in real time to your OpenTelemetry OTLP receivers.
Self-hosted, Kubernetes-native and fully programmable
Run the Cluster on Kubernetes infrastructure you control, scale the data plane horizontally, manage resources declaratively, automate the platform through APIs and SDKs, and optionally deploy containerized Service upstreams with the Cluster itself.
Cluster resources applied declaratively from YAML with octeliumctl and managed programmatically with the Go, TypeScript, Python and Rust SDKs
GitOps: cluster.yaml in the main branch of the ~/octelium repository describes the desired state of the Cluster. Its diff reads as follows, with + for added, ~ for changed and - for removed lines.
+ kind: Group
+ metadata:
+ name: payments
+ spec: {}
+ ---
+ kind: User
+ metadata:
+ name: alice
+ spec:
+ type: HUMAN
+ email: alice@acme.com
+ groups: ["payments"]
---
kind: User
metadata:
name: bob
spec:
type: HUMAN
~ groups: ["payments"]
---
kind: Service
metadata:
name: billing
spec:
mode: HTTP
config:
upstream:
~ url: http://10.0.3.24:8080
- ---
- kind: Service
- metadata:
- name: billing-legacy
- spec: ⋯
- ---
- kind: User
- metadata:
- name: john
- spec: ⋯
~/octelium ❯ octeliumctl apply --prune .
Group: payments Created
User: alice Created
User: bob Updated
Service: billing.default Updated
Service: billing-legacy.default Deleted
User: john Deleted
Cluster Core resources successfully applied
2 resources created
2 resources updated
2 resources deleted
Go: main.go uses the octelium-go SDK to list the Users of the Cluster and update the email and Policies of linus.
What an autonomous agent needs, and what issues it
An agent that can act on real systems needs the same things a new employee needs, on a shorter timescale and in larger numbers. These are the requirements, next to the mechanism in an Octelium Cluster that provides each one.
01
An identity of its own
An agent is a workload User, not a shared service account and not a copy of a person's session. It has its own groups, its own Policies, its own Session and its own entries in the audit trail, which is what makes revoking one agent a contained action.
A way to prove that identity without a stored secret
Agents authenticate with federated OpenID Connect assertions issued by the platform running them, such as GitHub Actions, a cloud provider or a Kubernetes cluster, so no long-lived token has to sit in the environment they run in. Authentication tokens and OAuth2 client credentials remain available where assertions are not.
Access to resources it never holds credentials for
Upstream credentials stay in the Cluster and are injected after a request is authorized, which covers HTTP API keys, SSH passwords and private keys, database passwords, kubeconfigs and mTLS certificates. A prompt injection cannot exfiltrate a credential the agent never had.
Authorization on the tool call, not on the connection
The MCP mode parses JSON-RPC messages, so the method, the tool name and the tool arguments are each available to a Policy. Discovery can be open while individual tools are restricted, and an argument can be bounded rather than a tool allowed outright.
The LLM mode normalizes the operation, requested model, streaming flag, declared tools, input modalities and declared output-token maximum, so which model an agent may use and under what conditions is a policy decision rather than which key it was given.
Request body size, streamed event size, estimated input tokens, declared output tokens and declared tool count are enforced by the gateway before a request leaves the Cluster, independently of what the agent was asked to do.
Cordium, built on Octelium, provides reproducible sandboxes for agents and developers on the same Kubernetes infrastructure. Processes inside a Workspace reach authorized Services through the Workspace identity, with no secrets placed inside the sandbox.
Every request produces an identity-aware AccessLog naming the User and Session alongside the tool called, the arguments, the model used and the tokens consumed, streamed in real time over OpenTelemetry to your own log management and SIEM providers.
Most of this is not specific to AI. It is ordinary identity and access management, applied to identities that are created faster, act more often and are harder to interview afterwards.
On every Octelium Cluster
The same platform capabilities ship with every deployment, whichever way you use it.
As a workload User, using the same identity model as a human User. It can present an authentication token, use the standard OAuth2 client credentials flow against the Cluster's token endpoint, use an access token directly as a bearer token, or authenticate secretlessly with a federated OpenID Connect assertion issued by the platform running it, such as GitHub Actions, a cloud provider or a Kubernetes cluster. No proprietary SDK is required.
Can individual tool calls be authorized?
Yes. The MCP mode understands JSON-RPC messages, so the method and the tool, prompt or resource name are separate normalized fields, and the parsed request body is available for arguments that have no normalized field. That lets tools/list stay open for discovery while tools/call is restricted per tool, per group and per argument value.
What stops an agent from leaking a credential?
It never receives one. Upstream credentials are stored in the Cluster as Secrets and injected into the upstream connection after a request has been authorized, and downstream Authorization, X-Api-Key and Api-Key credentials are stripped before proxying. The agent authenticates as itself and reaches the resource through the proxy, so there is no provider API key, database password or SSH key inside its environment to exfiltrate.
Can different agents have different privileges on the same resource?
Yes. Dynamic configuration selects the upstream, the upstream account and the credential per request from identity and request context, so a release agent and a research agent can share one Service URL while reaching different backends with different rights. Policies attach to a Service, a Namespace, a User, a Group or a Credential.
Where do agents actually run?
Wherever you run them. If you want the execution environment as well, Cordium is a free and open source sandbox platform built on Octelium and on the same Kubernetes infrastructure. Workspaces are built from OCI images, Dockerfiles, git repositories or devcontainers, run rootless, and reach authorized Services through the Workspace identity with no secrets inside the sandbox.
Are prompts and tool arguments recorded?
It depends on the mode and is configurable per Service. MCP request and response bodies are captured by default, because inspecting JSON-RPC messages is usually the point of the audit, and can be disabled. LLM request and response bodies are not captured by default, because prompts and outputs commonly contain sensitive data, and can be enabled where the audit backend and its retention policy are appropriate for them.