What stays in your cluster, and what leaves.
A collector runs inside your Kubernetes cluster and acts under its own ServiceAccount, holding only the permissions you scope it to. The control plane it reports to is never given your kubeconfig, your cloud credentials, or a shell, and it cannot widen what the collector can reach. What crosses that line is the result of a cast you ran, stored with its audit record. Cluster access stays in your cluster; the answer is what travels.
- Your kubeconfig
- You never hand cluster access to the control plane. A collector runs in your cluster and acts under its own in-cluster ServiceAccount, the way any other workload does, and it dials one outbound mTLS connection to the control plane; nothing dials in. The control plane holds no credential to your cluster and cannot reach the API server itself.
- Your cloud credentials
- No AWS, GCP or Azure keys, and no IAM role in your account. Where a spell needs the cloud, it uses a role that already lives in your cluster: the deploy spell reads image tags through your own IRSA role, scoped to ecr:DescribeImages, with no pull or push. Nothing is copied out.
- A shell
- No exec, no attach, no port-forward, no ephemeral container. The interactive path onto your nodes is not a feature that is switched off; it is not in any grant. A caster cannot reach a shell, and neither can anyone who takes over that caster's account.
- The ability to widen itself
- Confinement is set on your side and stays there. You scope the collector's RBAC and name the namespaces it may touch; the control plane cannot broaden either. A grant you did not write is a grant the collector does not have.
You keep the credentials and the reach; the control plane gets the request you made and the result it produced, and no way to take an action you did not cast.
What crosses to the control plane
A cast is a request the control plane authorizes and a result the collector produces. Both are stored there, with the audit record, so a cast can be replayed, attributed, and read weeks later. That store is the data that has left your cluster.
For most spells the result is small and structural: which deployment was restarted, which image is running, whether a rollout finished. Scry is the exception worth naming. Its result is pod log output, and log output can carry a token or a connection string. It is not redacted today, so a scry result is exactly as sensitive as the logs it read. If that boundary is one you cannot cross, the control plane can be self-hosted, which keeps the store inside your own infrastructure.
Caster has not launched, and there is no managed control plane on offer yet. This page describes how the boundary works, not a service you can buy today.
The permission model
A spell is a bounded action, and two independent layers keep it bounded.
The first layer is the control plane. It decides whether you may cast a spell at all: a role grants the entitlement spell.scry.cast or restart.revivify.cast, and a cast without the matching entitlement is refused before anything runs. This is where a caster is told which spells they hold.
The second layer is your own Kubernetes API server. The collector acts under a ServiceAccount whose RBAC you wrote, so the verbs a cast can use are the verbs that role grants, and nothing else. Scry's role holds get, list on pods and their logs; revivify adds patchon deployments. There is no exec, no secret access, and no delete in any of them. A bug in the control plane's own code cannot exceed this layer, because the enforcement is not there: it is in your API server.
Be precise about what each layer buys. The RBAC ceiling is the hard limit on what any cast can do to the cluster, and it holds even against a compromised control plane. The finer line, which caster may act on which workload, is drawn by the entitlement layer alone: every cast runs under the same ServiceAccount, so the API server cannot tell two casters apart. Grant a spell over a set of namespaces and everyone who holds it can act on every matching workload there. The bound is the namespace, not a notion of who owns what.
The dangerous spells are held apart
A spell that writes is not reachable through a spell that reads. Revivify and deploy are authorized by entitlements that sit outside the spell.* family, so a broad grant of read spells never adds up to a restart or a deploy. Both also refuse production by default: an unpinned target, or any target not on the allowlist, is denied before the cast runs and checked again as it runs.
caster · demo · simulation
deploy checkout-api image=web:9f2c1a0 --target prod
✗ prod is not an allowed target
This deployment refuses production. The allowed targets are dev and staging. Nothing was patched, and the cluster was not touched.
Refused at preflight, before the registry or the API server was called.
refused · target not allowed · logged
What one cast leaves behind
Every cast the control plane runs writes one record: what was cast, by whom, in which tenant, and how it ended. A cast refused before it runs does not become a record of an action, because no action was taken; the refusal is logged instead, so an attempt to reach past a permission is still visible.
The record is one row, isolated to your tenant. It also mirrors to a Slack channel you name, and to the control plane's structured logs, so it lands wherever you already read operational history.
cast record · example
- cast
- cst_4a7d21c0
- tenant
- storefront
- principal
- ana@northwind.example
- castable
- revivify
- state
- succeeded
- result
- checkout-api · 3/3 ready · 41s
The target, the surface, and the exact outcome ride in the result the row carries, next to the fields the control plane can always vouch for. A record is worth nothing if it claims more than the cast actually did.
An AI agent is another employee
It is enrolled as its own principal with its own entitlements: the spells you trust it to cast, and nothing else. It holds no credential and no shell, for the same reason the humans do not. Its casts land in the same record, attributed to it.
OWASP names excessive agency (an agent granted more capability, permission or autonomy than its task requires) as LLM06 in the Top 10 for LLM Applications. The mitigation it asks for is to limit the agent's tools and permissions to the minimum the task needs. A spell is that limit, and the RBAC ceiling underneath it holds even if the agent is fully hijacked. A prompt injection can make an agent want anything; it cannot make it hold a permission it was never granted.
One honest caveat. A workflow bundles several spells behind a single grant, and casting the workflow casts its steps, including steps the holder could not cast directly. A workflow is a real widening, so granting one is granting everything it runs. Each step still refuses a forbidden target at the moment it runs, so a workflow cannot smuggle a cast into production the target policy would reject.
The worst case
A page that will not say what can go wrong is not worth reading. Here is what can, and how far it gets.
A caster's account is taken over
The attacker can cast the spells that caster holds, on any workload in the namespaces those spells were granted over: read logs, restart or re-image a deployment there. They cannot exec into a pod, read a secret, delete anything, or reach a namespace the grant does not name, because none of those verbs exist in the role. The worst outcome available is a service restarted or re-imaged when it should not have been: an outage, attributed and logged, rather than a breach. The same account holding a kubeconfig would have been a much worse morning.
An agent is prompt-injected
The same answer, and deliberately so. The agent's reach is its entitlements, bounded by the RBAC ceiling. An injection can make it try things; it cannot give it a permission, and every attempt is in the log. The exception is a workflow you granted it: that runs everything the workflow contains, so scope a workflow grant to an agent as carefully as you scope the workflow.
The control plane is compromised
This is the real one, and the honest answer is in two parts. It cannot widen the collector: the RBAC ceiling is enforced by your API server, so a fully compromised control plane can do at most what the granted spells' verbs allow, in the namespaces you allowed, with no exec, no secrets, and no delete. What it does hold is the store of past results, including scry's log output. So the exposure is the data already gathered, not the cluster. Self-hosting the control plane keeps that store in your own infrastructure; not running scry keeps log output out of it entirely.
A spell is too powerful
The security of the system is the security of its spell set. If a spell can delete a namespace, then a compromised account can delete a namespace, and no amount of packaging changes that. The answer is a narrow spell set and a refusal to ship the wide ones, but you should read a spell's grant the way you would read a Role, and the collector's RBAC is checked in as YAML so you can.
A cast fires when it should not
A restart is still a restart, and a deploy is still a deploy. A cast that fires by mistake causes downtime. What it does not cause is data loss: today's spells read, restart, and set an image, and none of them deletes anything. Production is refused by default, so a mistaken cast lands on dev or staging unless someone deliberately allowed more.
What we have not done
Caster has not launched. There is no third-party penetration test, no SOC 2 report, and no compliance attestation, and we are not going to imply otherwise while there is nothing to show you. Scry ships log output to the control plane unredacted; that is a known gap, named here rather than buried. If one of these is what stands between you and running this, say so on the waitlist. It is useful to know which one.
Questions a review asks
Does the control plane get a kubeconfig or cloud credentials?
No. A collector runs inside your cluster and acts under its own ServiceAccount. The control plane never receives a kubeconfig, an IAM role, or a cloud key, and it cannot reach your API server directly. Where a spell needs the cloud (deploy reading image tags), it uses a role that already lives in your cluster, scoped to a single read.
Can a developer get a shell through Caster?
No. There is no exec, attach, port-forward, or ephemeral-container permission in any spell's grant. A caster holds a set of spells (bounded actions) and no cluster credential of their own, so an attacker who takes over that account inherits the spells and nothing else. A shell is not switched off; it is absent.
Where do my logs go when I run scry?
A scry reads pod logs inside your cluster and returns them as the cast result. That result travels to the control plane and is stored with the cast record. Log output can contain secrets, and it is not redacted today, so the result of a scry is data that has left your cluster. If that boundary is unacceptable, self-host the control plane so the store stays in your own infrastructure.
How do you stop an AI agent from doing more than it should?
An agent is enrolled as its own principal with its own entitlements, exactly like an employee. It holds no credential and no shell, so the actions it can take are the spells it was granted, bounded by the collector's Kubernetes RBAC that your own API server enforces. Every cast is attributed to it in the same audit record as a human's. This is the mitigation OWASP asks for under LLM06, excessive agency.
What happens if the control plane itself is compromised?
It cannot widen the collector. The collector's Kubernetes RBAC is enforced by your own API server, so a fully compromised control plane can do at most what the granted spells' verbs allow: read logs and patch deployment images or restart annotations, in the namespaces you allowed, with no exec, no secrets, and no delete. What it does hold is the store of past cast results, including scry's log output. That store is the exposure to weigh, not cluster takeover.
Read the grant before you run it.
Casteris pre-launch. Leave an address and you'll hear before it opens. Say what your review needs to see.