# Investigating a Leaked Kafka Credential with Conduktor Agents

Rotating a leaked Kafka password is quick, but the new password keeps every grant the old one had. Finding out what those grants exposed, and narrowing them, takes longer and usually more than one team.

Take a payments team with a reporting job on Kafka. Maya, debugging a failed build, posts in the team channel: "set -x was on, so the payments-reporting password is in it. The log is two days old."

Bob, who looks after the reporting job, replies: "Rotating it now. Then I'll check what that account could read." He can change what his team owns, but only sees his team's topics. Priya, on the platform team, can see the whole cluster, and this leak needs both of them.

## Bob Narrows the Grant Himself

With the password rotated, Bob opens Claude Code in the team's repo and types "what can payments-reporting read and who owns it". His assistant reaches Console through the [Conduktor MCP server](https://www.conduktor.io/mcp) and the Conduktor CLI, both using Bob's personal access token, so it sees and changes only what Bob can.

The account belongs to `payments-local`, the team's application, and it has Read on every topic starting with `pay`. The team only owns `payments-`. His assistant spells out what that [prefixed ACL](https://www.conduktor.io/glossary/kafka-acls-and-authorization-patterns) means: "a topic created later with a name like payroll or paypal-events would be readable straight away." Bob can't see whether topics like that already exist.

> **Every film here is live HTML.** Click one to pause it and scroll to read every line, or step through it with the bar underneath.

   ~/payments

**Claude Code** v2.1.322

Opus 5.5

~/payments

Called conduktor 3 times (ctrl+o to expand)

 Checking which actual topics the pay* prefix covers:

Called conduktor 2 times (ctrl+o to expand)

 payments-reporting is a service account on local-kafka-cluster. It can only read. It has no write access.

**What it can read**

Resource Pattern Permission Topics starting with pay Prefixed Read Consumer group payments-reporting Literal Read

**Who owns it**

The payments-local instance of the Payments application ("Payment processing and transactions") claims this account. The application's owner is the payments-owners group, which also owns all three topics.

- The grant is wider than it needs to be. pay matches more than the payments topics: a topic created later with a name like payroll or paypal-events would be readable straight away. Changing the prefix to payments- would limit it to these three.

 Sautéed for 27s

 **Write**(payments-reporting.yaml)

 Wrote **24** lines to **payments-reporting.yaml**

 **Bash**(conduktor apply -f payments-reporting.yaml --dry-run)

 Could not apply resource ServiceAccount/payments-reporting: At spec.authorization.acls[0]: appInstance `payments-local` doesn't have permissions or ownership to grant Read on TOPIC/pay

 The pay grant could not have been made through self-service as things stand today. Self-service rejected the account's current state. Nothing was applied.

Why it fails: the payments-local application instance owns only:
- topics with the prefix payments-

- consumer groups with the prefix payments-

… The audit log would settle it, but Bob's token doesn't have platform.auditlog.read.

 Brewed for 1m 5s

 **Update**(payments-reporting.yaml)

 Added **1** line, removed **1** line

11        - type: TOPIC

12 -        name: pay

12 +        name: payments-

13          patternType: PREFIXED

 **Bash**(conduktor apply -f payments-reporting.yaml --dry-run)

 ServiceAccount/payments-reporting: Updated

 The dry-run passes now.

 Cogitated for 10s

 **Bash**(conduktor apply -f payments-reporting.yaml)

 ServiceAccount/payments-reporting: Updated

 Verifying the live state:

Called conduktor (ctrl+o to expand)

 The change is live. The apply returned ServiceAccount/payments-reporting: Updated, and reading the account back from Console shows:

Resource Pattern Permission Topics starting with payments- Prefixed Read Consumer group payments-reporting Literal Read

The old pay* grant is gone, and the account can still read the same three payments topics. …

We still don't know where the pay grant came from. That needs someone with platform.auditlog.read.

 Baked for 14s

*Bob's assistant reads the account, dry-runs it as it stands, then narrows it.*

Bob has three ways to go from here:

- **Narrow it now.** Change `pay` to `payments-` and apply. It's quick, but if something depends on the wider grant, it breaks on his watch.
- **Hand it to the platform team.** Open a ticket and wait. Nothing breaks, but the grant stays wide until someone picks it up.
- **Check where the grant came from first.** If his team asked for `pay` on purpose, something of theirs may rely on it.

He goes with the third, and tells his assistant why: "Before I change anything, I want to know whether this grant came from us." His team manages its access through Console's [federated ownership](https://www.conduktor.io/federated-ownership), where an application can only grant access to resources it owns. So the assistant writes the account out as it stands and dry-runs it:

```
$ conduktor apply -f payments-reporting.yaml --dry-run
Could not apply resource ServiceAccount/payments-reporting: At spec.authorization.acls[0]: appInstance `payments-local` doesn't have permissions or ownership to grant Read on TOPIC/pay
```

Console refuses. `payments-local` owns only `payments-`, so nobody set `pay` through the team's application, and Bob's token can't read the audit log to see who did. The reporting job only reads the three payments topics, so he has the assistant change `pay` to `payments-`, dry-run it again, and apply it. He didn't need a ticket or anyone else's time.

## Priya Traces the Exposure

Bob still doesn't know what the old grant could reach, so he posts in the platform channel: "What did pay cover? I can only see our topics."

Payments engineering

Threads

Mentions

Drafts and sent

Channels

payments-team

kafka-platform

incidents

random

Direct messages

M** Maya

P** Priya

S** Sam

B** Bob Active

payments-team  Builds, releases and on-call for payments

kafka-platform  Clusters, topics and access for every team

M P B 14

Yesterday

L

**Lena**5:41 PM

Standup moves to 10:15 tomorrow.

Today

M

**Maya**9:12 AM

The CI log from the failed build printed our client.properties. set -x was on, so the payments-reporting password is in it. The log is two days old.

B

**Bob**9:14 AM

Rotating it now. Then I'll check what that account could read.

Yesterday

S

**Sam**3:05 PM

Staging brokers are on the new version. Prod is Thursday.

Today

B

**Bob**11:02 AM

Narrowed payments-reporting from pay to payments- after a leaked password. Console wouldn't accept pay from our app, so the grant was made outside self-service. What did pay cover? I can only see our topics.

P

**Priya**11:03 AM

Looking now.

11:06

pay also matched payroll-employee and payroll-employee-location. Payroll has SSNs. I'm closing it off and checking who else has grants like that.

****** **Priya** is typing…

  Message #payments-team Message #kafka-platform

  **B** *I*

 @

*The leak in the team channel, then Bob's handoff to the platform team.*

Priya runs the Kafka platform, and she knows security will start with one question: what that password could read for two days. She opens [Ask Conduktor](https://www.conduktor.io/console/agents?src=blog-investigating-a-leaked-kafka-credential) on the cluster page, asks what `payments-reporting` could read before Bob's change and what's in those topics, and adds: "Go by the schemas; don't read any records."

Ask finds Bob's change in Console's [audit log](https://www.conduktor.io/blog/kafka-audit-logging-compliance-forensics), expands the old `pay` prefix into today's topics, and reads their schemas. Five topics match. Two are payroll topics, and `payroll-employee` has fields named `ssn` and `hourly_rate`. The other three have no schema, and Ask stops there: "I can't tell what they contain without reading records. You asked me not to, so I didn't."

Home

Ask Conduktor

Agents

Console

Topics

Schema registry

Consumer groups

Kafka Connect

Kafka Gateway

Brokers

Service accounts

SQL engines

Analytics

Insights

Chargeback

Lineage

Settings
main

 Local Kafka local-kafka-cluster

Ask   A

Local Kafka local-kafka-cluster

*Topics***17*****Partitions***106*****Subjects***5*****Consumer Groups***6*****Connectors***8*****Brokers***3**

**Cluster state**Broker graphs

 Healthy

Partitions

*Under Replicated***0*****Offline Partitions***0*****Under Min ISR***0**

kafka-1:9092

**Produce rate**101.42

**Consume rate**46.35

**Indexing state**

a few seconds ago**
**Brokers & Topics**every 30s

a few seconds ago**
**Consumer Group Lag**every 30s

a few seconds ago**
**Kafka Connect**every 30s

a few seconds ago**
**Schema Registry**every 30s

**Recently viewed**

Topics Subjects Consumer groups Connectors

 payments-reporting access b…

How can I help?

Ask about your topics, consumer groups, schemas or how Conduktor works.

Bob narrowed payments-reporting through self-service a few minutes ago. What could it read before his change, and what kind of data is in those topics? Go by the schemas; don't read any records.

Thinking…

Looking at your cluster… Looked at your cluster · 8 calls · 23s

Before Bob's change, payments-reporting could read the 3 payments-* topics and also the 2 payroll-* topics. Going by the schemas, the payroll topics hold employee personal data, including Social Security numbers.

**The change:** At 21:44 UTC, Bob (token bob-editor) changed the service account's topic Read ACL from the prefix pay to the prefix payments-. The consumer group grant (payments-reporting, Read) didn't change, and the live broker ACLs now show only payments-.

**What pay* matched before the change:**

- payroll-employee: registered schema (Avro, payroll.payroll_employee). Fields are employee_id, first_name, last_name, age, ssn, hourly_rate, gender and email. That's sensitive personal data: identity, a government ID, pay and demographics.
- payroll-employee-location: registered schema (Avro). Fields are employee_id, lab, department_id and arrival_date (a date). It shows where each employee works and when they started, and it joins to the topic above on employee_id.
- payments-incoming, payments-outgoing and payments-failed: **no registered schema**, so I can't tell what they contain without reading records. You asked me not to, so I didn't. The account can still read these three.

Check who consumed payroll topics Show the earlier audit diffs

Run “ACL hygiene watch”

Thinking…

Looking at your cluster… Looked at your cluster · 7 calls · 28s

A suggestion, not a change

Nothing happens unless you act on it. Dismissing it is a valid answer.

Finding

I read all 8 ACL bindings on **local-kafka-cluster** in full, not a sample. Four of the five principals that hold ACLs belong to no self-service application. Only payments-reporting (owned by payments-local) is scoped correctly: Read on prefix payments-, which matches the 3 payments-* topics, plus its own group.

**legacy-etl has All on every topic. Remove or tighten this first.** User:legacy-etl holds an Allow for operation **All** on Topic * (Literal, host *). It can read, write, delete and change the config of every topic on the cluster, including payroll-employee and the payments-* topics.

**bi-tool reads more than half the cluster through the 1-letter prefix p. Tighten it.** That prefix matches 6 of the 11 non-internal topics: pageviews, payroll-employee, payroll-employee-location, payments-incoming, payments-outgoing and payments-failed.

**reporting-svc's Allow on payroll-employee never takes effect. Remove the Allow.** In Kafka a Deny always wins, so the Allow does nothing.

**migration-job can write to a topic that no longer exists. Remove it.** User:migration-job holds Write on Topic orders-v1-legacy. That topic isn't on the cluster.

Proposed

USER:LEGACY-ETL ON TOPIC * (ALL) tighten to Read/Write on its specific topics, or remove USER:LEGACY-ETL ON GROUP * (ALL) tighten to Read on Prefixed legacy-etl- USER:BI-TOOL ON TOPIC P (PREFIXED, READ) tighten to literal Read on the topics it needs, without payroll-* and payments-* USER:REPORTING-SVC ON TOPIC PAYROLL-EMPLOYEE (ALLOW READ) remove (the Deny overrides it) USER:MIGRATION-JOB ON TOPIC ORDERS-V1-LEGACY (WRITE) remove (stale: topic no longer exists) UNCLAIMED PRINCIPALS LEGACY-ETL, BI-TOOL, REPORTING-SVC, MIGRATION-JOB claim in a self-service application or retire

Affected resources

User:legacy-etl User:bi-tool User:reporting-svc User:migration-job Topic:* Group:* Topic prefix:p payroll-employee payroll-employee-location pageviews payments-incoming payments-outgoing payments-failed orders-v1-legacy

Expand Schedule it Ran on demand · not saved to Outputs

Run a skill now

Cluster summary*Report**Per-cluster size, health and topic-type split from Console's insights. Reads everything, writes nothing.*

Chargeback report*Report**Cost per application this period against the previous one, with the movers explained.*

Lineage report*Report**The most and least connected topics and principals, and whether the ACL set matches.*

Risk insight*Report**Topics Console flags as low replication, skewed or lopsided, each joined to its owner.*

VIP insight*Report**The most valuable topics per cluster with their readers, owners and owner cost.*

Low-use insight*Report**Empty, stale and tiny topics and empty or dead consumer groups, each with an owner to ask and the owner's cost.*

Ownership coverage*Report**Self-service coverage per cluster and topic family, and the uncovered topics.*

Certificate expiry watch*Suggestions**Certificates approaching expiry, with a rotation call to action.*

ACL hygiene watch*Suggestions**Over-broad, contradictory, stale or unclaimed ACL grants, most risky first, with what a tighter grant would be.*

 Ask anything…

/for skills

AI can make mistakes, check what matters.

*Ask finds the old grant in the audit log, then reads what it could reach.*

Priya could hold her answer until she knows whether anyone used the password. That proof is in the broker's authorizer log, if it was set to record allowed requests, or in [Gateway's audit log](https://docs.conduktor.io/guide/reference/data-security#audit-interceptor) if clients connect through Gateway, and it may not exist at all. So she tells security what she knows now: for two days, the leaked password could read [sensitive payroll data](https://www.conduktor.io/blog/how-to-handle-pii-in-kafka), including employee social security numbers and pay rates.

Then she replies to Bob: "Payroll has SSNs. I'm closing it off and checking who else has grants like that."

## Check Who Else Can Read Payroll

She tries Kafka's own CLI first. Asked about the payroll topic, it lists one account, and that account is blocked:

```
$ kafka-acls --list --topic payroll-employee
Current ACLs for resource `ResourcePattern(resourceType=TOPIC, name=payroll-employee, patternType=LITERAL)`:
	(principal=User:reporting-svc, host=*, operation=READ, permissionType=ALLOW)
	(principal=User:reporting-svc, host=*, operation=READ, permissionType=DENY)
```

By default, `kafka-acls --list` only shows grants on that exact name. `--resource-pattern-type match` adds the prefixed and wildcard grants, but it still doesn't say which grant wins, which team owns each account, or what the topic holds.

So Priya types `/` in Ask and picks **ACL hygiene watch**, one of the tasks built into Console. It reads every ACL binding on the cluster and comes back with four problems, all on accounts with no [application owner](https://www.conduktor.io/blog/no-kafka-data-platform-without-ownership) in Console:

- **`legacy-etl` has All on every topic and every consumer group.** It can read, write, and delete anything on the cluster.
- **`bi-tool` reads through the one-letter prefix `p`.** That matches six of the eleven topics, payroll included.
- **`reporting-svc` has an Allow on `payroll-employee` that a Deny cancels out.** The Allow does nothing.
- **`migration-job` can write to `orders-v1-legacy`.** That topic doesn't exist.

It's a recommendation, and the card says so: "Nothing happens unless you act on it. Dismissing it is a valid answer."

## Remove the Grants That Do Nothing First

Two of the four are safe to fix straight away, because neither grant does anything. Priya asks Ask to remove both, and adds: "Take me to each one and show me what to press."

Ask opens each account's page and highlights the row to delete, then the Save Changes button. Priya presses both herself. Ask then checks the brokers, and neither account has any grants left.

Home

Ask Conduktor

Agents

Console

Topics

Schema registry

Consumer groups

Kafka Connect

Kafka Gateway

Brokers

Service accounts

SQL engines

Analytics

Insights

Chargeback

Lineage

Settings
main

 Local Kafka local-kafka-cluster  Service Accounts / migration-job

Ask   A

migration-job View in Lineage

Add label

ACLs

All resource ACLs assigned to this service account. Save Changes

**Topics** *(1 0)*

orders-v1-legacy Host: Any *

All Alter AlterConfigs Create Delete DescribeConfigs Describe Read Write

+ Add Topic ACL

**No Topics ACLs assigned**+ Add Topic ACL

**Consumer Groups** *(0)*

**No Consumer Groups ACLs assigned**+ Add Consumer Group ACL

**Transactional ID** *(0)*

**No Transactional ID ACLs assigned**+ Add Transactional ID ACL

**Delegation Token** *(0)*

**No Delegation Token ACLs assigned**

 payments-reporting access b…

Remove reporting-svc's Allow Read on payroll-employee and migration-job's Write on orders-v1-legacy. Take me to each one and show me what to press.

Thinking…

migration-job is ready on this page. Nothing is removed yet:

- Press the remove (trash) icon on the orders-v1-legacy row (highlighted). This drops Write, its only grant.
- Press **Save Changes** to apply it. After that the account has no ACLs at all.

Done. Check reporting-svc and migration-job ACLs on the broker.

Thinking…

Reading your cluster… Looked at your cluster · 3 calls · 9s

 Listed acl bindings

 Listed acl bindings

 Listed acl bindings

Both changes are on the broker now. Neither account has any ACLs left.

payroll-employee holds SSNs, and two other accounts can still read it:

- **bi-tool:** Allow Read on the prefix p. That covers every topic starting with "p", including both payroll topics and all the payments topics.
- **legacy-etl:** Allow All on *, every operation on every topic.

Both look much broader than they need to be.

Review bi-tool and legacy-etl

 Ask anything…

/for skills

AI can make mistakes, check what matters.

Ask Conduktor

This drops Write, its only grant.

Dismiss

Ask Conduktor

Press Save Changes to apply it. After that the account has no ACLs at all.

Dismiss

*Ask points at each grant, Priya removes it, and Ask checks the brokers.*

That leaves the two that matter. Nobody has claimed `bi-tool` or `legacy-etl`, both can still read payroll, and Priya can't tell from their names which jobs depend on them.

## See Where Broad Access Meets Sensitive Data

Before she decides, Priya checks the latest report from an agent that runs on this cluster every day. It runs a custom task written for this cluster: a short prompt and seven read-only tools that inspect schemas, sample records when a topic has no schema, and list who can read each sensitive topic.

The report puts `payroll-employee` at the top, readable by `bi-tool` and `legacy-etl`. It also turns up `aws.identity.cdc.customers.json.v1`, a schemaless copy of the customer data. The agent samples records from every topic without a schema, and in this one it found names, emails, income, and credit scores. The run's trace shows each of those reads.

Agentic

Home

Identities

Tasks

Models

Agents

Scheduled

Event-driven

Output

Reports

Recommendations

Approvals

Direct updates

Activity

** Sensitive data in Kafka (PII / HR)

Settings
main

Output / Sensitive data in Kafka (PII / HR) / dae0fb36-b2c2-43b3-ae2b-cc6183750662

Ask   A

Output

Everything your agents hand back, reports to read, recommendations to act on, approvals to decide, and a record of direct updates.

Reports Recommendations Approvals Direct updates

Search by title, task or agent Agent

Output Agent Produced****

Sensitive data audit: local-kafka-cluster (PII / HR) Sensitive data in Kafka (PII / HR) Sensitive data in Kafka (PII / HR) an hour ago

Sensitive data audit: local-kafka-cluster (PII / HR) Report Sensitive data in Kafka (PII / HR)

Produced an hour ago · agent Sensitive data in Kafka (PII / HR)

Output Trace

Read-only task: nothing was changed

*Topics listed***17**  *Schema registry subjects read***5**  *Schemaless topics sampled***5**  *Topics with sensitive data***4**  *Critical risks***1**  *High risks***3**

Risk ranking

Rank Topic Severity Sensitive fields Format Why

1 payroll-employee Critical ssn, hourly_rate (pay), first_name, last_name, age, gender, email Avro (payroll-employee-value v1) Social security number and pay rate are in clear. User:bi-tool (Read on prefix 'p') and User:legacy-etl (All on '*') can read the topic, on top of the owning consumer hr-payroll-sync.

2 customers High first_name, last_name, email, gender, income, fico (credit score) Avro (customers-value v1) Customer PII and financial profile are in clear. The only consumer is crm-customer-sync, but User:legacy-etl has All on every topic ('*').

3 aws.identity.cdc.customers.json.v1 High first_name, last_name, email, gender, income, fico JSON with embedded Connect schema (no registry subject) Same customer PII as 'customers', copied by CDC as plain JSON. No consumer group reads it right now. User:legacy-etl can read it through its '*' wildcard ACL.

*Run***7641a5d5-93df-4f5c-b66e-1b8555121c99**Copy 49 steps · 6 model turns · 21 tool calls · 50 s in the model · 5,935 tokens

0 Briefing Sensitive data in Kafka (PII / HR) · 8 tools offered · claude-opus-5-5

1 Model Asks for get-trigger-context*1.3 s · 34 tokens*

2 Tool call get-trigger-context

3 Tool result get-trigger-context returned in 4 ms*4 ms*

4 Model Asks for list-topics, list-subject-names*1.8 s · 177 tokens*

5 Tool call list-topics

6 Tool call list-subject-names

7 Tool result list-topics returned in 13 ms*13 ms*

8 Tool result list-subject-names returned in 14 ms*14 ms*

9 Model Asks for get-schema-version, get-schema-version, get-schema-version, get-schema-version, get-schema-version*4.1 s · 564 tokens*

10 Tool call get-schema-version

11 Tool call get-schema-version

12 Tool call get-schema-version

13 Tool call get-schema-version

14 Tool call get-schema-version

15 Tool result get-schema-version returned in 20 ms*20 ms*

16 Tool result get-schema-version returned in 21 ms*21 ms*

17 Tool result get-schema-version returned in 23 ms*23 ms*

18 Tool result get-schema-version returned in 24 ms*24 ms*

19 Tool result get-schema-version returned in 27 ms*27 ms*

20 Model Asks for get-last-messages, get-last-messages, get-last-messages, get-last-messages, get-last-messages*5.4 s · 675 tokens*

21 Tool call get-last-messages

22 Tool call get-last-messages

23 Tool call get-last-messages

24 Tool call get-last-messages

25 Tool call get-last-messages

26 Tool result get-last-messages returned in 63 ms*63 ms*

27 Tool result get-last-messages returned in 549 ms*549 ms*

28 Tool result get-last-messages returned in 559 ms*559 ms*

29 Tool result get-last-messages returned in 572 ms*572 ms*

30 Tool result get-last-messages returned in 580 ms*580 ms*

31 Model Asks for list-consumer-groups-by-topic, list-consumer-groups-by-topic, list-consumer-groups-by-topic, list-consumer-groups-by-topic, list-acl-bindings, list-acl-bindings, list-acl-bindings*9.2 s · 1,207 tokens*

32 Tool call list-consumer-groups-by-topic

33 Tool call list-consumer-groups-by-topic

Home

Ask Conduktor

Agents

Console

Topics

Schema registry

Consumer groups

Kafka Connect

Kafka Gateway

Brokers

Service accounts

SQL engines

Analytics

Insights

Chargeback

Lineage

Settings
main

**Local Kafka***local-kafka-cluster* / Service Accounts / bi-tool

Ask   A

bi-tool

Add label

ACLs

All resource ACLs assigned to this service account. Save Changes

**Topics** *(1)**(2)*

Prefixp Host: Any *

All Alter AlterConfigs Create Delete DescribeConfigs Describe Read Write

Prefixpayroll- Host: Any *

All Alter AlterConfigs Create Delete DescribeConfigs Describe  Read Allow ****Deny Write

  Create

+ Add Topic ACL

**Consumer Groups** *(0)*

No Consumer Groups ACLs assigned

+ Add Consumer Group ACL

*The sensitive data report, its trace, and the Deny that closes payroll.*

Priya has three options for the two accounts:

- **Cut them back now.** Payroll is safe at once, but any job still using them breaks, and nobody knows which jobs those are.
- **Wait for an owner.** Nothing breaks, and payroll stays readable for as long as the search takes.
- **Close payroll only.** A Deny on the `payroll-` prefix blocks the sensitive topics and leaves everything else running.

She picks the third. In Kafka a Deny always beats an Allow, so she adds Deny Read on `payroll-` for `bi-tool`, and Deny All for `legacy-etl`, which could otherwise still write to or delete the payroll topics. If a legitimate job reads payroll through one of these accounts, it breaks now, and Priya would rather find out that way than leave social security numbers readable while she looks for an owner.

## Turn a One-Time Review Into a Weekly Agent

The review she ran from Ask ran once, in the chat, and wasn't saved. Priya doesn't want the next over-broad grant to wait for the next leak.

She presses **Schedule it** under the review, and Ask fills in the agent wizard: the name, the task, the model, and a weekly run on Monday at 08:00 UTC. It points at the button that creates the agent's own identity, with only the permissions this task needs, and then at Create and enable. Priya presses both.

Home

Ask Conduktor

Agents

Console

Topics

Schema registry

Consumer groups

Kafka Connect

Kafka Gateway

Brokers

Service accounts

SQL engines

Analytics

Insights

Chargeback

Lineage

Settings
main

Agents / ACL hygiene watch – local-kafka-cluster

Output / ACL hygiene watch – local-kafka-cluster / ACL hygiene: legacy-etl holds All on every topic and group; bi-tool has a 1-letter prefix grant

Ask   A

ACL hygiene watch – local-kafka-cluster Enabled Running

Next run: 16 Nov 2026, 03:00 · Never ran Next run: 16 Nov 2026, 03:00 · Last run: Succeeded a few seconds ago

   Run once Edit

History

**No runs yet**Output appears here after the agent runs.

ACL hygiene: legacy-etl holds All on every topic and group; bi-tool has a 1-letter prefix grant Recommendation a few seconds ago
Open

Instructions

ACL hygiene watch Claude Sonnet 5.5

Over-broad, contradictory, stale or unclaimed ACL grants, most risky first, with what a tighter grant would be.

Show task prompt

Repeats

Frequency**Weekly**At**08:00**Day**Monday**Timezone**UTC**Catch-up**Skip missed**

Permissions

Runs as**acl-hygiene-watch-local-kafka-cluster**

ACL hygiene: legacy-etl holds All on every topic and group; bi-tool has a 1-letter prefix grant Recommendation ACL hygiene watch Open

Produced a few seconds ago · agent ACL hygiene watch – local-kafka-cluster

Dismiss Mark as actioned

Output Trace

**A suggestion, not a change**Nothing happens unless you act on it. Dismissing it is a valid answer.

Finding

I read all 7 ACL bindings on local-kafka-cluster in full, so nothing was sampled. There are 4 findings. The first two are the highest risk.

**1. Over-broad: User:legacy-etl has operation All on every topic and every group.**

**2. Deny shadows Allow: User:legacy-etl has Deny All on Topic prefix payroll- (host *).** A Deny always wins, so the Allow All on * is dead for payroll-employee and payroll-employee-location (the 2 matching topics). The principal can do everything on the other 9 topics but nothing on payroll.

**3. Short prefix: User:bi-tool has Allow Read on Topic prefix p (host *).**

**4. Unclaimed principals:** bi-tool and legacy-etl hold ACLs, but no self-service application instance claims either one (appInstance is null).

Task  Access  Condition  Review

Set up your agent Continue

Name the agent, pick the task it runs and the model that runs it.

Name***

Task*** Select a task ACL hygiene watch

Model*** Select a model Claude Sonnet 5.5 · claude-sonnet-5-5

**ACL hygiene watch**Recommendation

Over-broad, contradictory, stale or unclaimed ACL grants, most risky first, with what a tighter grant would be.

Trigger kind

Schedule

Required permissions

Cluster
View ACL
Platform
Can list clusters
Topic
Consume

What can it see? Back Continue

Pick the identity the agent runs as, and how often it is allowed to run.

Identity*** Select an identity acl-hygiene-watch-local-kafka-cluster · admin@demo.dev The task only sees what this identity can see. Actions are attributed to it.

**Need a new identity?**Create one without leaving this page. Permissions are taken from what this task needs on the selected cluster.

Cluster*** Select a cluster Local Kafka

Create new identity with permissions for ACL hygiene watch ******

**acl-hygiene-watch-local-kafka-cluster** was created and selected for this agent.

Customize an identity for this task instead

When should it run? Back Continue

Run on a fixed cadence, or react to changes in your clusters.

**On a schedule**Hourly, daily, weekly or monthly.

**On an event**When a topic, subject, consumer group or connector changes.

ACL hygiene watch can only run on a schedule.

Frequency
 Hourly Daily Weekly Monthly

Day Monday

Time*** 09:00 AM 08:00 AM

Timezone UTC

If a run is missed Skip missed

Review Back Create and enable ******

Check the agent before creating it. You can edit any section later.

Task
Name**ACL hygiene watch – local-kafka-cluster**

Task**ACL hygiene watch**

Model**Claude Sonnet 5.5 · claude-sonnet-5-5**

Access
Identity**acl-hygiene-watch-local-kafka-cluster · admin@demo.dev**

Condition
Type**Schedule**

Frequency**Weekly**

Day**Monday**

Time**08:00**

Timezone**UTC**

Catch-up**Skip missed**

**Enable immediately**Starts running on its next schedule or event as soon as it is created.

Ask Conduktor

Creates a new identity with only the permissions ACL hygiene watch needs on Local Kafka.

Dismiss

Ask Conduktor

Creates the ACL hygiene watch agent and enables it to run every Monday at 08:00 UTC.

Dismiss

 payments-reporting access b…

Schedule “ACL hygiene watch” as an agent

Thinking…

Working on the page… Worked on the page · 15 calls · 32s

 Looked up the app's pages

 Went to /agents/agents/create

 Read the page

 Filled textbox with ACL hygiene watch – local-kafka…

 Clicked Continue

 Read the page

The agent wizard is open but the agent isn't created yet. It needs an identity first, and that's yours to create: press **Create new identity with permissions for ACL hygiene watch** (highlighted). That creates an identity with only the permissions this task needs on Local Kafka: viewing ACLs and service accounts, listing clusters, and reading topic metadata.

You didn't say how often it should run. Unless you tell me otherwise, I'll schedule it like the PII review: **weekly, Monday 08:00 UTC**, skipping missed runs. After that I'll take you to the last step and point you at the button that creates the agent.

Done. Carry on from where you stopped.

Thinking…

Working on the page… Worked on the page · 12 calls · 26s

 Read the page

 Clicked Continue

 Read the page

 Clicked Weekly

 Read the page

 Filled Time with 08:00

 Clicked Continue

 Read the page

 Clicked Enable immediately

 Read part of the page

 Read the page

 Pointed you to Create and enable

The agent is ready to create but doesn't exist yet. Press **Create and enable** (highlighted) to create it. It will run the ACL hygiene watch every Monday at 08:00 UTC.

This task only reads ACLs and topic metadata. Unlike the PII review, it won't read any records.

Done, continue

Who owns legacy-etl and bi-tool, and is there any record of who granted their access?

Thinking…

Looking at your cluster… Looked at your cluster · 7 calls

 Read service account legacy-etl on local-kafka-cluster*11 ms*

 Read service account bi-tool on local-kafka-cluster*11 ms*

 Searched audit log*0 ms*

 Searched audit log*0 ms*

 Searched audit log*5 ms*

 Searched audit log*5 ms*

 Searched audit log on local-kafka-cluster*5 ms*

Neither account has an owner, and the only record of their access is one audit event each. Those events show who added a Deny rule, but nothing shows who granted the original access.

**Ownership**

- No self-service application claims either account.
- They exist only as broker ACLs, so Console has no owner, team or contact for them.

**What the audit log shows**

- No Create event for either account appears, either by exact name or by searching for "etl" since 2020. Whoever first granted the broad access did it outside Console's audit trail.

 Ask anything…

/for skills

AI can make mistakes, check what matters.

*Ask fills in the wizard, Priya creates the agent, and runs it once.*

She presses **Run once**. This time the recommendation is saved on the Output page. The two dead grants are gone from it, both accounts are flagged as unclaimed, and it says this about her Deny: "The Deny is the only thing protecting payroll, so keep it until the wildcard Allow is removed."

## Bob Fixed What He Owns. Agents Checked the Rest.

Bob narrowed the part his team owns from his own terminal. What the grant reached, and what other accounts reach, took Priya, who can see the whole cluster, and two agents that keep checking it. A person made every change. Here's what each piece does:

| Piece | What it does | Where it showed up |
|---|---|---|
| MCP server | Gives your AI assistant Console's tools, with your permissions | Bob's Claude Code session |
| Ask Conduktor | Answers questions inside Console, runs built-in tasks, and fills in forms | Everything Priya did in Console |
| Task | The job: what to check, the tools it may call, and what it hands back | ACL hygiene watch (built-in), the sensitive data check (custom) |
| Agent | A task with a model, its own identity, and a schedule or event that wakes it | The weekly ACL review, the daily sensitive data check |
| Identity | The account an agent runs as, with only the permissions its task needs. Every run records the permissions it held | `acl-hygiene-watch-local-kafka-cluster` |
| Output | A run's report, recommendation, or approval, saved on the Output page | The sensitive data report, the weekly recommendation |
| Trace | Every model turn and tool call in a run | The sensitive data run's record samples |

When Priya asks Ask who owns `legacy-etl` and `bi-tool`, the answer starts with "Neither account has an owner". Console's audit log shows who added the payroll Deny, "but nothing shows who granted the original access." Until someone claims the two accounts, the weekly agent keeps flagging them. In the next use case, Priya finds their likely owners and cuts each account back to what it uses.

## See It Live on Thursday

Watch us demo use cases like this live on Thursday, 19 November, at 11:00 AM ET / 8:00 AM PT: [Leave the Chores to Kafka Agents](https://www.conduktor.io/events/webinars/conduktor-agents-live?src=blog-investigating-a-leaked-kafka-credential).

---

Related: [If Kafka Credentials Leak, Does the Attacker Get Plaintext or Ciphertext? →](https://www.conduktor.io/blog/kafka-credentials-leak-plaintext-or-ciphertext) · [How to Manage Kafka ACLs and Security at Scale →](https://www.conduktor.io/blog/how-to-manage-kafka-acls-at-scale) · [Introducing Conduktor Agents →](https://www.conduktor.io/blog/conduktor-agents) · [Conduktor MCP + Skills for AI Agents →](https://www.conduktor.io/blog/conduktor-mcp-and-skills-for-ai-agents)
