Dev.to AI ๐Ÿค– Ai ๐Ÿ‘ 0 ๐Ÿ“– 3 min read

No Bootstrap, No Principal: The Day My Audit Log Became Fiction

My v0.1.0 series couldn't talk about authorization, because there wasn't any. v0.2.0 shipped RBAC-lite, scoped API keys, and an encrypted secret vault on HivePlane โ€” and the first thing the field test taught me is that a

My v0.1.0 series couldn't talk about authorization, because there wasn't any. v0.2.0 shipped RBAC-lite, scoped API keys, and an encrypted secret vault on HivePlane โ€” and the first thing the field test taught me is that attribution has a cold-start problem.

I shipped a control plane with roles, permissions, and an audit trail โ€” and for an hour, none of it was reachable, because there was no way to mint the first API key.

This is the article about the prerequisite for every governance claim I made in the earlier series: a principal. Without one, your audit log is fiction.

The cold start

The finding was D-10. RBAC was wired. /auth/login existed. Scoped keys were implemented. And the entire model was unreachable end-to-end, because there was no HTTP bootstrap to mint the first admin key. The roles existed; nobody could assume one.

The fix was a dev-grade env admin key โ€” boot the first principal from the environment, then issue scoped keys from there:

# the first principal has to come from outside the API
export HIVEPLANE_AUTH__ENABLED=true
export HIVEPLANE_AUTH__ADMIN_KEY=$(openssl rand -hex 32)
# now you can POST /keys to issue scoped, tenant-bound keys

The lesson is that authn is the admission gate for the operators. Until there's a principal, every "who did this" question is unanswerable, and every other governance feature is theater.

The audit log that named the wrong person

Then I found the worse one. Pause, resume, stop, kill-switch, quarantine โ€” every privileged operator action โ€” was attributed to a client-supplied actor field. The caller put --operator alice in the request. The audit log recorded alice. Any caller could sign an action as anyone.

tip: A who column populated by the request body is a confession that you don't know. The actor must come from the authenticated principal, not the payload. A client-supplied actor is repudiation by construction.

Findings #560, #561, and #541 were all the same shape: the action was real, the attribution was a string the caller chose. The fix is that the principal resolves the actor; the field is read-only. You can attribute an action to yourself. You cannot attribute it to someone else.

The endpoints that had no permission at all

Then the field test found the gaps RBAC was supposed to cover and didn't:

  • #537 โ€” registry mutation endpoints had no permission checks. A viewer could DELETE /workloads.
  • #526 โ€” destructive immune-system endpoints (quarantine, kill-switch) were ungated.
  • #563 โ€” pause/resume/stop had no permission gate; a viewer could stop any run.
  • #566 โ€” the UI session secret shipped a hardcoded public default.

A role system that doesn't gate the destructive verbs is a role system in name only. The fix is server-side authorization on every privileged action: approve, promote, kill-switch. A viewer cannot approve. A viewer cannot stop. A viewer cannot delete a workload. The 401/403 is the product.

HMAC-first, not bearer-first

The one I didn't see coming was #515. With auth enabled, GitHub and Alertmanager webhooks got 401 before the HMAC signature was verified. The bearer-first model broke event ingest from โ‰ฅ3 sources โ€” which is a release gate. Triggers are the autonomy layer; if they can't authenticate, the fleet doesn't run itself.

The fix is that webhook ingest is HMAC-first, not bearer-first. The signature is verified before any bearer token is required; the trigger source is trusted by its HMAC, not by a login. Autonomy and authorization have to coexist, or you've secured the plane against its own purpose.

What I learned

Bootstrap the first principal, or none of it exists โ€” the env admin key is the admission gate for operators. Attribution comes from the principal, never the payload: a client-supplied actor is repudiation by construction. Gate the destructive verbs โ€” a viewer who can delete a workload doesn't have a role, they have root. And webhooks are HMAC-first: bearer-first auth breaks event ingest, and autonomy and authorization have to coexist or you've secured the plane against its own purpose.

The v0.2.0 field test asserts per-tenant budget/policy/key isolation with a viewer who cannot approve, and a secret that never appears in logs/traces/agent context (verified by test). Full evidence in the field-test report; the auth bootstrap and key-rotation runbook is at docs/runbooks/auth-bootstrap-and-key-rotation.md.

References

Open your audit log. Pick one privileged action. Is the who populated by the authenticated principal โ€” or by a field the caller wrote? If it's the second, your audit log is a story someone else is telling you.

๐Ÿ“ฐ Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.