Somebody on r/kubernetes asked the question properly this week: what should a read-only debugging bot be able to see in your cluster?
The situation behind it is the modern one. The bot is not a dashboard. It is an AI helper that wakes up with the page, reads the cluster, and tries to explain — or fix — whatever just broke. And the answer most of us reach for is one line long: bind it to view, the built-in read-only role, and relax. It cannot change anything. It cannot delete anything. It is literally called view.
Here is the scene that answer skips. It is 3 AM. Something in the payments namespace is down, and the bot gets to work exactly as designed. It reads the pod specs. It reads the logs. It reads the ConfigMaps. It assembles a beautiful, complete, accurate picture of your system — including, very possibly, the database password your app prints in its startup banner, and the connection string sitting in a ConfigMap since three migrations ago. The bot changed nothing. It deleted nothing. It simply read — and reading was the whole breach.
Read-only describes the verbs. It says nothing about the value of what gets read.
A debugging bot needs eyes, absolutely. But read-only is a property of verbs — get, list, watch — not a property of risk. The stock view role is broader than most teams realise, it quietly grows broader over time, and it exposes exactly where credentials live outside the Secret store. The fix is not to blind the bot. The fix is to write it a role of its own: one namespace, named resources, audited in five minutes.
1. What the view role actually grants
Start with the manual. Kubernetes ships four user-facing roles, and the documentation describes view plainly: read-only access to most objects in a namespace. Two exclusions are deliberate, and the docs state the reason for the big one. view cannot see Secrets, because reading Secrets means reading ServiceAccount credentials, which means operating the API as any ServiceAccount in that namespace — privilege escalation. It also cannot see roles or role bindings, so it cannot map out who else holds access.
Comforting. Now read what “most objects” actually covers — in list form, because this is the part nobody reads:
Pods and pod logs
Verbs: get, list, watch.
Why it matters: pod specs carry environment variables in plain text, and logs carry whatever your applications print.ConfigMaps
Verbs: get, list, watch.
Why it matters: application configuration in plain text — the natural home of URLs, usernames, feature flags, and the occasional connection string.Services, endpoints, and PersistentVolumeClaims
Verbs: get, list, watch.
Why it matters: together they are your internal network map and storage layout, readable end to end.Workloads
Covers: deployments, replicasets, statefulsets, daemonsets, jobs, cronjobs.
Verbs: get, list, watch — every image, command, argument, and env block, for every workload in scope.Events and ServiceAccounts
Verbs: get, list, watch.
Why it matters: what failed, in what order — and which identities exist in the namespace.Secrets, roles, and role bindings
Status: excluded.
Why it matters: the one fence Kubernetes built into this role — built, note, around escalation through ServiceAccount tokens.
And here is the role itself, as the API server defines it — an excerpt of the stock definition, trimmed and grouped for readability:
# Excerpt — the stock view ClusterRole (rules trimmed) apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: view labels: kubernetes.io/bootstrapping: rbac-defaults aggregationRule: clusterRoleSelectors: - matchLabels: rbac.authorization.k8s.io/aggregate-to-view: "true" rules: - apiGroups: [""] resources: ["pods", "pods/log", "configmaps", "services", "endpoints", "serviceaccounts", "persistentvolumeclaims", "events"] verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list", "watch"] - apiGroups: ["batch"] resources: ["jobs", "cronjobs"] verbs: ["get", "list", "watch"] # No secrets. No roles or rolebindings. That part is true. One more detail decides how much of this lands on your bot: the binding. view is a ClusterRole, and if it is attached with a ClusterRoleBinding, every line above applies in every namespace in the cluster — production, staging, the database namespace, kube-system, all of it. Plenty of teams do exactly this — it is the shortest YAML that works — and it becomes the before picture below.
2. The aggregation trap: your view is not the documented view
Everything so far is the stock definition. Your cluster’s copy may grant more — and nobody on your team added it by editing view.
The built-in roles use a feature called ClusterRole aggregation. A controller in the control plane watches for ClusterRoles carrying the label rbac.authorization.k8s.io/aggregate-to-view: "true" and folds their rules into view automatically. It is a sensible feature: install an operator or an add-on, and its custom resources become visible through the standard roles without anyone hand-editing RBAC.
Sensible, and silent. Every install that labels a ClusterRole for aggregation widens view for every subject bound to it — including your bot — with no diff in your repo and no entry in any change log you own. What gets folded in is the operator’s choice. Most aggregate read access to their own custom resources. But the mechanism does not grade what it folds; it folds whatever carries the label.
So the question “what can our bot see?” has exactly one honest answer, and it is not in the documentation. It is in the live object: kubectl get clusterrole view -o yaml, on your cluster, today. If the rules list runs longer than the manual describes, aggregation has been shopping on your behalf.
3. Where the credentials actually live
Now the part the thread was really about. Kubernetes fenced the Secret store — correctly, and for a stated reason. But a credential does not stop being a credential because it is stored somewhere else, and the same values have a habit of turning up in three unfenced places. The examples below are illustrative — the shapes repeat everywhere; the values are invented:
# A ConfigMap — "just configuration", stored in plain text by design apiVersion: v1 kind: ConfigMap metadata: name: app-config data: DATABASE_URL: "postgres://payments_app:a-plain-text-password@db.internal:5432/payments" CACHE_TTL: "300" ConfigMaps
What they are: the cluster’s standard store for application configuration, unencrypted at rest and fully readable underview.
The problem: a connection string is configuration. It is also the keys to the database.Pod specifications
What they are: every pod’s full spec — image, command, arguments, environment.
The problem: environment variables set as literal values sit in the spec, fetchable by anyone with pod read. Tokens and keys end up there precisely because it was the fastest way to ship.Pod logs
What they are: stdout and stderr, exposed as thepods/logsubresource.
The problem: applications print their configuration at startup, connection strings when a retry fails, the occasional token in a debug line. Logs are where secrets go to be photocopied.
Kubernetes fenced the Secret store because Secrets escalate. Nobody fenced the photocopies.
The point is not that your team is careless. The point is that view‘s one exclusion was designed around Kubernetes’ threat model — escalation through ServiceAccount tokens — not around yours, which is “an automated reader goes through everything it can reach and repeats the interesting parts into a transcript.” Different reader, different risk.
4. And do not reach for edit instead
A short warning about the neighbouring role, because somebody suggests it in every team the first time the bot hits a wall: “just give it edit — then it can fix things too.” Read the manual before that meeting. edit grants read and write access to most objects in a namespace, and unlike view it can access Secrets and run pods as any ServiceAccount in the namespace. In the role’s own definition, that means impersonating serviceaccounts and creating serviceaccount tokens. A bot holding edit is not a debugging helper with extra powers. It is an identity that can become any other identity in the namespace. Whatever problem you were solving, it was smaller than that.
5. The fix: one namespace, one small role
So write the bot a role of its own. The principles are boring, which is how you know they are right. A namespaced Role, not a ClusterRole. Bound with a RoleBinding in exactly one namespace. Only the resources the bot demonstrably needs. Sensitive resources pinned by name, using resourceNames, wherever the API allows it. No wildcards, in resources or verbs, anywhere. If the bot later needs a second namespace, that is a second RoleBinding and a conversation — not a ClusterRoleBinding and a shrug.
apiVersion: v1 kind: ServiceAccount metadata: name: debug-bot namespace: payments --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: debug-bot namespace: payments rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["pods/log"] verbs: ["get"] - apiGroups: [""] resources: ["events"] verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list", "watch"] - apiGroups: ["batch"] resources: ["jobs", "cronjobs"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["configmaps"] resourceNames: ["app-config", "feature-flags"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: debug-bot namespace: payments subjects: - kind: ServiceAccount name: debug-bot namespace: payments roleRef: kind: Role name: debug-bot apiGroup: rbac.authorization.k8s.io Three choices in that YAML do the security work. First, it is a Role: its reach ends at the namespace border by construction, not by discipline. Second, pods/log is its own rule with a single verb — get — because reading a log is one deliberate action, not a standing subscription. Third, ConfigMaps are get-only and named. That third choice has a quirk worth knowing: for list and watch, Kubernetes honours resourceNames only when the client sends a metadata.name field selector with the request — so the honest minimal grant is get, by name. If your bot truly must list ConfigMaps, grant that in the one namespace with your eyes open, rather than handing over get-list-watch by habit.
Notice what is not in the role: Secrets — still out, but now because we left them out, not because a default happened to. No roles or role bindings. Nothing cluster-scoped. The bot’s world is one namespace wide, and every object class inside it earned its place.
6. Audit it like you mean it
A role you have not audited is a hope, not a control. Kubernetes ships the audit tool: kubectl auth can-i, which pairs with impersonation through the global --as flag — you ask the API server questions while wearing the bot’s identity. For a ServiceAccount, that identity is system:serviceaccount:<namespace>:<name>.
BOT=system:serviceaccount:payments:debug-bot # Everything the bot can do in payments, printed plain kubectl auth can-i --list --as=$BOT -n payments # The answers you actually want kubectl auth can-i get secrets --as=$BOT -n payments # no kubectl auth can-i get configmap/app-config --as=$BOT -n payments # yes — it is on the named list kubectl auth can-i get configmap/db-settings --as=$BOT -n payments # no — it is not # And outside its one namespace, the bot should not exist kubectl auth can-i list pods --as=$BOT -n staging # no Two of those checks carry the whole audit. The named ConfigMap checks: an unnamed can-i get configmaps tells you almost nothing about a resourceNames rule, so ask about specific objects — one on the list, one off it — and require a yes and a no respectively. And the Secrets check must say no. If it ever says yes, something changed your effective permissions — an install, an upgrade, an aggregation you did not review — and the role in your repo is no longer the role in your cluster. Re-run the full list after every operator install and every cluster upgrade. Those are exactly the moments aggregation moves under you.
7. The checklist I would tape to the PR
Never bind
viewwith a ClusterRoleBinding for a bot. Cluster-wide read is cluster-wide exposure, whatever the verbs are.Write a custom Role per namespace , and one RoleBinding per namespace the bot genuinely works in.
Grant get, list, watch only on what the bot demonstrably needs — pods, events, workload status — and make every addition argue its way in.
Keep
pods/logas its own rule, verbget. Log access is a decision, not a default.Pin ConfigMaps with
resourceNamesandget— and remember that list and watch only honour the restriction when the client sends ametadata.namefield selector.Leave Secrets out of the role entirely — then audit for them anyway, because aggregation can add what you did not.
No wildcards in resources or verbs, anywhere, for any reason that fits in a PR description.
Audit with
kubectl auth can-i --list --as=before deploy, after operator installs, and after upgrades — including named-object checks, not just the summary.Never substitute
editfor a debugging bot. It reads Secrets and can act as any ServiceAccount in the namespace. That is not a debugging role; that is a master key.
8. The one-line version
view is a reasonable role for a human glancing at a namespace they already understand. A bot is a different reader: tireless, literal, and attached to a transcript. Give it the smallest set of eyes that does the job — one namespace, named resources, verbs you chose on purpose — and then audit it the way you would audit anything else that can read your production configuration at 3 AM.
Do this in staging first. Apply the Role to a throwaway namespace, run the audit commands against a test ServiceAccount, and confirm the yes/no pattern matches the YAML before the bot’s identity goes anywhere near production.
Running bots, agents, or automation against a Kubernetes cluster? I write about the operational side of that every week.
Subscribe for the next one — and if your bot currently holds view through a ClusterRoleBinding, run the audit in section 6 and tell me in the comments what the list said. That thread is always interesting.
So here's my question for you: when you run the audit on your bot's identity, what does the list say it can actually read?
Disclaimer
This post describes my personal experience with my own systems and accounts, in my own words. It is a first-person account of what I observed and what I do in my own work — not a statement of any company's intentions, policies, or internal decisions. Where I do not know something, the post says so.
I am not affiliated with, endorsed by, or speaking for any company, product, or vendor named in this post. All product and company names are trademarks of their respective owners and are mentioned only to describe the experience accurately.
Any error messages, documentation excerpts, or figures quoted here are reproduced from my own records or from the public sources linked in the post, believed accurate at the time of writing. Product behavior changes over time; verify against current official documentation before relying on any detail.
Nothing in this post is professional, financial, legal, or security advice. Examples are illustrative — validate in your own environment before running anything in production.
Corrections are welcome: if a company named here publishes an official statement, or a detail above is wrong, I will update this post and note the correction. The goal is an accurate public record of my experience, not a verdict on anyone.
© 2026 Honey Kiran. All rights reserved.
Honey Kiran · DevOps & Platform Engineering — writing about Kubernetes, cloud infrastructure, and AI in operations.
Kubernetes · RBAC · AI Agents · DevOps · SRE
Examples are illustrative — validate in your own environment before running in production.




![AI Safety Alignment and Interpretability — Ch.01: AI Safety [KR]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv6vb5y3xnjut9qcy4c9j.png)
![AI Safety Alignment and Interpretability — Ch.04: Fairness and Bias [KR]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqxr5ty6tpnochxxh136.png)
![AI Safety Alignment and Interpretability — Ch.08: Engineering and Deployment [KR]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kl9qonydtmy3pbn593l.png)