<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kyro Security Blog</title><description>Technical writing on workload identity, IAM, and what platform teams need to see before they trim a permission.</description><link>https://kyrosecurity.io/</link><language>en-us</language><item><title>Your service account looks unused. It probably is not.</title><link>https://kyrosecurity.io/blog/service-account-looks-unused/</link><guid isPermaLink="true">https://kyrosecurity.io/blog/service-account-looks-unused/</guid><description>Google flags service accounts that have not authenticated in 90 days. A GKE workload talking to anything outside Google Cloud can look idle while running.</description><pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Somewhere in your Google Cloud console there is an insight telling you a service
account has not been used in 90 days, and suggesting you disable or delete it.&lt;/p&gt;
&lt;p&gt;It is probably right. It might also be describing an account that a running
production workload depends on right now.&lt;/p&gt;
&lt;p&gt;Both of those can be true at once, and the reason is worth understanding before
you action the list.&lt;/p&gt;
&lt;h2 id=&quot;what-the-check-actually-measures&quot;&gt;What the check actually measures&lt;/h2&gt;
&lt;p&gt;The feature is service account insights, which Google labels Pre-GA. It flags an
account when it has not &lt;strong&gt;authenticated&lt;/strong&gt; in the last 90 days.&lt;/p&gt;
&lt;p&gt;That is a real signal and a reasonable one. Every call to a Google Cloud API
authenticates as some principal, and that authentication is recorded. If nothing
has authenticated as this account in three months, that is genuinely suspicious.&lt;/p&gt;
&lt;p&gt;Read the sentence carefully, though. It is not “has not done anything.” It is
“has not authenticated to a Google Cloud API.”&lt;/p&gt;
&lt;p&gt;Those are the same thing only if everything your workload does is a Google Cloud
API call.&lt;/p&gt;
&lt;h2 id=&quot;which-for-a-lot-of-workloads-it-is-not&quot;&gt;Which, for a lot of workloads, it is not&lt;/h2&gt;
&lt;p&gt;Picture a service running in GKE, using Workload Identity Federation for GKE
to impersonate a Google service account. Its Kubernetes ServiceAccount is bound
to that account:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role:   roles/iam.workloadIdentityUser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;member: serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That impersonation path is the one most clusters are on. Google’s current best
practice is the other one: reference the Kubernetes ServiceAccount directly as
an IAM principal, with a &lt;code&gt;principal://&lt;/code&gt; identifier and no intermediate service
account at all. On that path none of what follows applies, because there is no
service account left to flag. Impersonation stays documented and supported,
because direct access still carries per-API limitations, so plenty of clusters
will be on it for a long time yet. If yours is one, read on.&lt;/p&gt;
&lt;p&gt;Now think about what that service spends its day doing.&lt;/p&gt;
&lt;p&gt;It reads and writes to its own Postgres. It calls two internal services over the
cluster network. It hits a model endpoint. It pulls from a bucket in another
cloud, because that is where the data landed. Maybe once a night it writes a
summary to Cloud Storage.&lt;/p&gt;
&lt;p&gt;Every one of those is real work. Exactly one of them authenticates to a Google
Cloud API.&lt;/p&gt;
&lt;p&gt;If the nightly job is paused for a quarter, or moved, or the bucket write gets
refactored away, the account goes quiet by this measure while the service keeps
running perfectly. Ninety days later it appears on a list of things to clean up.&lt;/p&gt;
&lt;p&gt;Google is precise about what resets the clock:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Whenever a service account or key is used to call a Google API, including an
API that is not part of Google Cloud, it generates an authentication activity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cloud.google.com/policy-intelligence/docs/service-account-usage-tools&quot;&gt;Tools to understand service account usage&lt;/a&gt;,
Google Cloud documentation.&lt;/p&gt;
&lt;p&gt;Failed calls count. Calls to Google APIs outside Google Cloud count. What does
not count is everything else the workload does all day, because none of it is a
call to a Google API. A pod can saturate its node for a quarter and produce
nothing this measure can see.&lt;/p&gt;
&lt;p&gt;Delete the account and the pod loses its identity. Not immediately, and not
loudly: the GKE metadata server caches access tokens, and those are good for an
hour by default. It fails the next time it needs a new one.&lt;/p&gt;
&lt;h2 id=&quot;google-says-this-itself&quot;&gt;Google says this itself&lt;/h2&gt;
&lt;p&gt;This is not a gotcha, and it is not a criticism of the feature. The
documentation is upfront about it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We recommend cross referencing service account insights with Cloud Monitoring
service account usage metrics before you disable or delete a service account.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.cloud.google.com/policy-intelligence/docs/service-account-insights&quot;&gt;Service account insights&lt;/a&gt;,
Google Cloud documentation.&lt;/p&gt;
&lt;p&gt;That sentence is worth reading twice, because it tells you the two signals are
not the same. Insights and Activity Analyzer do not track authentication against
Google APIs that sit outside Google Cloud. The Cloud Monitoring usage metrics do,
including those APIs. That is the whole reason for the cross reference.&lt;/p&gt;
&lt;p&gt;There is one more stated gap that neither side covers: requests authenticated by
API keys bound to a service account are not recorded in the usage metrics at all.
Google’s own conclusion is that “these service accounts might be reported as
unused.”&lt;/p&gt;
&lt;p&gt;Then there is the arithmetic of the cross reference itself. The insight looks
back 90 days. Cloud Monitoring, per its
&lt;a href=&quot;https://docs.cloud.google.com/iam/docs/service-account-monitoring&quot;&gt;own documentation&lt;/a&gt;,
“retains service account metrics for 6 weeks.” The second source runs dry around
six weeks into the window the first source is judging. For anything that last authenticated two
months ago, the check you have been told to run has nothing left to say.&lt;/p&gt;
&lt;p&gt;A tool telling you to check its output against a second source before acting is
telling you something about the confidence of that output. It is worth doing.
It is also worth knowing that doing it fully is not possible.&lt;/p&gt;
&lt;h2 id=&quot;the-second-thing-which-is-quieter&quot;&gt;The second thing, which is quieter&lt;/h2&gt;
&lt;p&gt;There is a related problem in the same data, and it does not announce itself
with a recommendation you can decline.&lt;/p&gt;
&lt;p&gt;When a GKE pod uses Workload Identity Federation for GKE, the call is made &lt;strong&gt;as
the Google service account&lt;/strong&gt;. The audit log records that the service account called
&lt;code&gt;storage.objects.get&lt;/code&gt;, which is correct and which is what the usage analysis
reads.&lt;/p&gt;
&lt;p&gt;The interesting part is that the log knows more than that. For GKE workloads it
also carries &lt;code&gt;serviceAccountDelegationInfo[].principalSubject&lt;/code&gt;, and inside that
string is this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is the same shape as the IAM binding further up this page. The namespace
and the Kubernetes ServiceAccount are sitting in the log, in a field the usage
analysis does not look at. It will not tell you the pod or the deployment, but
it is a good deal more than nothing, and it is being ignored rather than
missing.&lt;/p&gt;
&lt;p&gt;Two things about where those entries live. Admin Activity logs always carry the
field and cannot be turned off, so for anything that writes configuration it is
reliably there. Data Access logs, which is where most ordinary data-plane calls
land, are disabled by default for nearly every service, so on a lot of clusters
those entries are never written at all.&lt;/p&gt;
&lt;p&gt;Which means the answer is partly sitting in a log you cannot switch off and
nobody is reading, and partly in one most people never switched on.&lt;/p&gt;
&lt;p&gt;Now suppose five workloads share that service account, which is a normal thing
to have done. They produce one usage profile between them.&lt;/p&gt;
&lt;p&gt;If workload A needs &lt;code&gt;storage.objects.delete&lt;/code&gt; and the other four do not, the
account keeps the permission. It is used, after all. Four workloads stay
over-permissioned and nothing in the data can tell you that, because from the
outside all five look like one very busy identity.&lt;/p&gt;
&lt;p&gt;Google’s own guidance is to create single purpose service accounts, and this is
one of the reasons why. Every workload you attach to an existing account is a
workload whose usage you can no longer see on its own.&lt;/p&gt;
&lt;h2 id=&quot;one-root-two-symptoms&quot;&gt;One root, two symptoms&lt;/h2&gt;
&lt;p&gt;Both of these come from the same place.&lt;/p&gt;
&lt;p&gt;The evidence is attributed to the &lt;strong&gt;identity that made the call&lt;/strong&gt;. Your risk
lives at the &lt;strong&gt;workload that holds the identity&lt;/strong&gt;. Those are different things,
and everything that goes wrong here goes wrong in the gap between them.&lt;/p&gt;
&lt;p&gt;Half of the bridge exists. The audit log carries the namespace and the
ServiceAccount, and the IAM binding carries them too. Nothing in the system
reads either one and turns it into a relationship you can act on, so that half
is sitting there unused rather than missing.&lt;/p&gt;
&lt;p&gt;The other half is not sitting anywhere. Read every audit log in the project
perfectly and you learn what the workload did &lt;strong&gt;to Google&lt;/strong&gt;. You learn nothing
about the Postgres, the internal services, the model endpoint, or the bucket in
the other cloud, because none of those produce a cloud audit record at all.
Those are the dependencies that decide whether trimming the role breaks
anything, and the cloud has never seen them.&lt;/p&gt;
&lt;p&gt;So the tooling can tell you, accurately, that an identity has been quiet. It
cannot tell you whether a workload depends on it. It can tell you, accurately,
that a permission was exercised. It cannot tell you which of the five things
behind that identity exercised it, or what happens to the other four if you take
it away.&lt;/p&gt;
&lt;h2 id=&quot;the-question-that-is-left&quot;&gt;The question that is left&lt;/h2&gt;
&lt;p&gt;None of this makes the recommendations wrong. Most of them will be correct, and
acting on them will genuinely reduce your exposure.&lt;/p&gt;
&lt;p&gt;It just means the list is a starting point rather than a work queue. Every row
on it comes with a question the row itself cannot answer:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If I delete this, what stops working?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The cloud’s own record gets you partway. It can name the workload behind the
identity, the day something bothers to read a field it already writes. It
cannot tell you what that workload depends on, because most of what a workload
depends on never touches a Google API and therefore never shows up.&lt;/p&gt;
&lt;p&gt;Which points at where the answer actually is. Not in a better reading of the
identity, which is what every tool here is already doing, but in watching the
workload: what it reaches for, what answers, and who goes quiet if the
permission disappears. The identity tells you what was allowed. Only the
workload tells you what was needed.&lt;/p&gt;</content:encoded><category>perspective</category><category>gcp</category><category>gke</category><category>workload-identity</category><category>iam</category></item><item><title>Three kinds of agent, and only one is yours</title><link>https://kyrosecurity.io/blog/three-kinds-of-agent/</link><guid isPermaLink="true">https://kyrosecurity.io/blog/three-kinds-of-agent/</guid><description>Ask how many AI agents you are running and you will not get an answer. Here is the one question that sorts them, and why only one pile is yours to fix.</description><pubDate>Tue, 04 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Someone at your company has been asked how many AI agents you are running.&lt;/p&gt;
&lt;p&gt;They cannot answer it. Not because nobody looked, but because “agent” turns out
to mean three completely different things, and only one of them is yours.&lt;/p&gt;
&lt;p&gt;Here is the test. Pick any agent someone names and ask one question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who issued its credentials?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That sorts it into one of three piles. The piles have almost nothing to do with
each other, and mixing them together is why the count never lands.&lt;/p&gt;
&lt;h2 id=&quot;1-you-issued-them&quot;&gt;1. You issued them&lt;/h2&gt;
&lt;p&gt;A nightly job that reconciles inventory. Someone wrote it a role in Terraform,
and a second person reviewed it.&lt;/p&gt;
&lt;p&gt;Now there is a model in the loop, so people call it an agent. Nothing else
changed. Your account issued the identity. Your team wrote the permissions.
Every call it makes shows up in your own logs.&lt;/p&gt;
&lt;p&gt;This is the boring pile, and the boring pile is the one with answers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can list them.&lt;/li&gt;
&lt;li&gt;You can read what each one is allowed to do.&lt;/li&gt;
&lt;li&gt;You can see what it actually did.&lt;/li&gt;
&lt;li&gt;You can delete one this afternoon and know by tomorrow what broke.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All four of those are true. Hold onto the last one, because it is doing more
work than it looks like it is.&lt;/p&gt;
&lt;h2 id=&quot;2-someone-elses-software-brought-them&quot;&gt;2. Someone else’s software brought them&lt;/h2&gt;
&lt;p&gt;Your document tool ships an assistant that answers questions by reading across
every file in the workspace.&lt;/p&gt;
&lt;p&gt;Nobody on your team built it. It showed up in a release note.&lt;/p&gt;
&lt;p&gt;It runs on the vendor’s servers, under the vendor’s identity, and it reaches
your data through a permission an admin granted once. Probably by clicking
through a consent screen. Possibly by leaving a default switched on.&lt;/p&gt;
&lt;p&gt;You cannot list these. You can only list the permissions you granted, and that
is not the same thing. “Read files in this workspace” does not tell you whether
one assistant uses it or forty, whether the vendor added another one last
Tuesday, or what any of them did once they were in.&lt;/p&gt;
&lt;p&gt;Your only real lever is that consent, and consent is usually all or nothing.
Keep the integration and its access, or rip it out and lose the feature a whole
department asked for. Nobody who has tried to revoke half an OAuth scope
mid-quarter would call that control.&lt;/p&gt;
&lt;p&gt;You fix this pile through contracts and procurement, not through your cloud
console.&lt;/p&gt;
&lt;h2 id=&quot;3-nobody-issued-them&quot;&gt;3. Nobody issued them&lt;/h2&gt;
&lt;p&gt;A coding assistant on an engineer’s laptop, running on a personal access token
they minted from their own account and pasted into a config file. Scoped
generously, because scoping it properly would have taken twenty minutes and the
work was due.&lt;/p&gt;
&lt;p&gt;There is nothing to revoke here, because nothing was ever issued. What exists is
a credential that works, belonging to a human whose access was sized for a human
working at human speed.&lt;/p&gt;
&lt;p&gt;When that person leaves, offboarding closes their accounts. It does not know
about the token in a config file on a laptop that has already been wiped, or the
copy in a personal repository, or the small script a teammate now quietly
depends on.&lt;/p&gt;
&lt;p&gt;This is the pile that keeps people up at night, and it is the one you can least
count by asking. Surveying engineers about which assistants they run measures
memory and honesty, not credentials.&lt;/p&gt;
&lt;h2 id=&quot;so-why-does-the-big-number-keep-coming-up&quot;&gt;So why does the big number keep coming up?&lt;/h2&gt;
&lt;p&gt;Because of forecasts like this one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By 2028 an average global Fortune 500 enterprise will have over 150,000 agents
in use, up from less than 15 in 2025.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Gartner Press Release,
&lt;a href=&quot;https://www.gartner.com/en/newsroom/press-releases/2026-04-28-gartner-identifies-six-steps-to-manage-artificial-intelligence-agent-sprawl&quot;&gt;Gartner Identifies Six Steps to Manage AI Agent Sprawl&lt;/a&gt;,
28 April 2026.&lt;/p&gt;
&lt;p&gt;The forecast is not the problem. The question people attach to it is.&lt;/p&gt;
&lt;p&gt;Suppose you did the counting and came back with one big number. What would you
actually do with it?&lt;/p&gt;
&lt;p&gt;Headcount for managing identities in your own accounts comes out of pile one.
How hard you push on vendor review is pile two. Secret scanning, shorter token
lifetimes, and making the approved path fast enough that nobody works around it
is all pile three.&lt;/p&gt;
&lt;p&gt;Three piles, three owners: your platform team, whoever signs the contracts, and
for the third one, quite often nobody.&lt;/p&gt;
&lt;p&gt;“150,000 agents” cannot be handed to anyone, because its three parts come out of
three different budgets and get fixed by three different groups of people.&lt;/p&gt;
&lt;h2 id=&quot;ask-the-smaller-question-instead&quot;&gt;Ask the smaller question instead&lt;/h2&gt;
&lt;p&gt;Not “how many agents do we have.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How many agents are using credentials we issued?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That one you can answer from records you already keep. It is also the only pile
where you can see behaviour rather than paperwork: what the thing actually did,
not what somebody once agreed it could do.&lt;/p&gt;
&lt;h2 id=&quot;the-fair-objection&quot;&gt;The fair objection&lt;/h2&gt;
&lt;p&gt;This is convenient, is it not. Draw a circle around the easy pile, call the rest
someone else’s job, go home.&lt;/p&gt;
&lt;p&gt;Worth answering directly: piles two and three matter. Three is probably where
your worst single incident comes from.&lt;/p&gt;
&lt;p&gt;But neither gets better by counting. Pile two improves when consent is
negotiated narrowly and renegotiated at renewal. Pile three improves when the
reason to mint a personal token goes away, which usually means the sanctioned
path was slower than the shortcut. Neither of those starts with a census.&lt;/p&gt;
&lt;h2 id=&quot;the-count-was-never-the-hard-part&quot;&gt;The count was never the hard part&lt;/h2&gt;
&lt;p&gt;Back to that last bullet in pile one.&lt;/p&gt;
&lt;p&gt;You can list those identities today, and most teams already have. Somewhere in
your organisation there is a spreadsheet of service accounts with a “last used”
column, and it has been accurate and ignored for eighteen months.&lt;/p&gt;
&lt;p&gt;The list is not what is missing. What stops anyone deleting a row from it is the
question the list cannot answer:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If I take this permission away, what stops working?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An inventory can tell you a permission has not been used in ninety days. It
cannot tell you whether the job that needs it runs quarterly. It cannot tell you
that two services share the identity and only one of them went quiet. Unused and
safe to remove are not the same thing, and everything expensive lives in the gap
between them.&lt;/p&gt;
&lt;p&gt;That is why the pile you own is fully catalogued and completely untouched. Not
because nobody counted. Because counting was never the part that was hard.&lt;/p&gt;</content:encoded><category>perspective</category><category>identity</category><category>cloud</category><category>saas</category><category>credentials</category></item></channel></rss>