Field Note 40 current
No anonymous "system" actor
TL;DR
Every automated action runs as a named principal. Cron jobs, cleanup tasks, migrations, archival, schedulers, agents — each one:
- has its own identity, named for what it does (
journal-archiver,stale-lock-reaper), with its own credentials and minimal scope; - appears by name as the actor in every audit record it produces — and when it acts on behalf of a user, the record names both the actor and the principal;
- has a human owner and can be disabled, re-scoped, or rotated without touching anything else.
Actor values like system, root, admin, or cron are banned — reject them at write time. The
test: from any audit record, can you get to the exact job and code that performed the action, and
turn that one thing off without turning off everything? If the answer is “it was the system,” you
don’t have an audit log; you have a diary that says something happened.
Context
The anonymous “system” user is never designed; it accretes. It starts as a convenience — the
migration runner needs a user id, the nightly cleanup needs credentials, so someone creates
system with broad permissions and moves on. Two years later every privileged background job runs
as it, its credentials are embedded in a dozen places, nobody can rotate them without an outage,
and its permission set is the union of everything any job ever needed — which is to say,
everything.
The audit cost is the worst part. An audit log exists to answer who did this, with what authority,
and what else can that identity do? The system actor answers none of these. “System deleted
3,000 rows at 03:14” tells you a machine was involved; it doesn’t tell you which job, which
code path, which schedule, or what to revoke. Investigations stall exactly there.
We solved this on the human side long ago: nobody would accept a shared employee login, because
attribution, scoping, and revocation all demand individual identities. Machine actors deserve —
and with workload identitySPIFFE — Secure Production Identity Framework for EveryoneThe CNCF standard for workload identity: every workload gets its own verifiable, named identity (a SPIFFE ID) and short-lived credentials, instead of shared secrets or ambient platform trust.spiffe.io ↗ now standard, can cheaply have — the same discipline.
And the pressure is rising: background automation increasingly means LLM agents acting on
schedules and triggers. A fleet of agents all acting as “the system” is unaccountable in a way
that compounds; an agent acting under its own name, on behalf of a named user, is just another
principal in the log.
This is the machine-side twin of ZFN-8: don’t hide behind anonymous “people”, and don’t let your automation hide behind an anonymous “system”.
The password nobody will rotate
The recurring artifact is the shared service credential that predates everyone on the team, granted permissions nobody can enumerate, used by jobs nobody has inventoried — so rotating it is treated as a change-freeze-level event. That fear is the system telling you the identity is doing too many jobs.
Recommendation
One identity per automation. Each job, scheduled task, worker, or agent gets its own identity, named for its function. Naming is not cosmetic: the name is what shows up in the audit log, the permission policy, and the incident channel at 3 a.m.
Own credentials, minimal scope. Issue each identity its own credentials — short-lived and federated, per ZFN-9, through one platform mechanism, per ZFN-5 — scoped to what that automation actually does. The archiver can write to the archive; it cannot touch user tables. Least privilege is only real when identities are this granular.
Actor on every mutation; principal when delegated. Every state change records the named actor. When automation acts on a user’s behalf, record both: the actor (the automation) and the principal (the user). Delegation is explicit and audited — an automation never silently becomes a user.
Ban the strings. Reject system, root, admin, cron, and friends as actor values at the
API or storage boundary, not in a code-review checklist. If the write path accepts an anonymous
actor, you will eventually have one.
Every identity has an owner. Keep a registry: automation identity → what it does → which team
or human owns it → where its runbook lives. An identity nobody owns is the shared system user
waiting to re-form.
Audit symmetrically. Scheduled runs that fire, fail, or are skipped are attributed events too. “The reaper didn’t run last night” should be answerable from the same log as “the reaper deleted this.”
Consequences
Easier:
- Forensics: every audit record resolves to a specific workload, its code, and its owner. “Who did this?” stops being a research project.
- Revocation and rotation are surgical — disable or re-key one automation without a change freeze.
- Least privilege becomes enforceable, because each policy is scoped to one job’s actual needs.
- A misbehaving automation (or agent) can be killed by name, immediately, with known blast radius.
Harder:
- Provisioning an identity per job is real work — make it a one-liner in your infrastructure templates, or people will share identities out of friction.
- More identities to inventory and govern. Federated, short-lived credentials (ZFN-9) keep this from becoming a key-management burden; a registry keeps it from becoming sprawl.
- Some platforms still make fine-grained machine identities awkward; budget for the seam.
New obligations:
- The actor field is mandatory and validated on every mutation; delegated actions carry actor and principal.
- The automation-identity registry exists, has owners, and is kept current — an unowned identity is treated as a finding.
References
- ZFN-8 — the human-side twin: don’t attribute positions to anonymous “people”; don’t attribute actions to an anonymous “system”.
- ZFN-5 — the one platform mechanism that makes per-workload identities cheap.
- ZFN-9 — federated, short-lived credentials, so many identities don’t mean many static keys.
- SPIFFE — the CNCF workload-identity standard: named, verifiable identities per workload.
Changelog
- 2026-06-12: First published as a Field Note.