Internal admin panels start as “just a page to refund that order” and become the highest-privilege service you run—often with weaker auth than customer login. Attackers know this. So do bored employees. A minimal admin can be safe if you treat it as production security work, not a weekend UI.

Why unsafe admins ship

  • Prod DB credentials in a personal script
  • Shared basic-auth password in chat
  • No audit trail for money-moving actions
  • CSRF-free cookie sessions on the open internet
  • Raw SQL box “for emergencies”

Speed is not an excuse for a second production control plane without controls.

Threat model (one page)

ThreatExampleControl
External attackerstolen session / exposed URLSSO, private network, short sessions
Malicious insidersilent refundsRBAC + audit + dual control
Honest mistakewipe userconfirmations, soft delete
DependencyXSS in adminstrict CSP, no HTML from users
Supply chaincompromised npm in admin applockfiles, minimal deps

AuthN / AuthZ model

AuthN: company SSO (OIDC/SAML) or equivalent. No local shared passwords.

AuthZ: server-side roles, for example:

  • support_read
  • support_refund
  • ops_reprocess
  • admin_roles (break-glass)

Check roles on every mutating request, not only in the React router.

Audit log fields (minimum)

ts, actor_id, actor_email, role, action, target_type, target_id,
before_json, after_json, request_id, ip, user_agent, result

Money and PII actions are incomplete without before/after. Keep audit storage append-only from the app’s perspective.

Destructive actions

  • Require re-auth or typed confirm (DELETE) for irreversible ops
  • Dual control for high-impact refunds above threshold
  • Rate limit privileged POSTs
  • Prefer soft flags (disabled_at) over hard deletes in v1

Minimal v1 feature set

Ship only:

  1. Search entity by id/email
  2. View status + recent events
  3. One or two mutations with audit
  4. Health of the admin app itself

Deliberately omit from v1:

  • Arbitrary SQL console
  • Shell access
  • Bulk CSV update without dry-run
  • Impersonation without banner + audit

Checklist file: content/labs/internal-admin-safely/checklist.md.

Implementation notes

  • Separate hostname (admin.internal.example) and auth policy
  • No admin routes mounted on the public API process if avoidable
  • CSRF tokens for browser forms; same-site cookies
  • Never echo secrets back to the UI
  • Paginate everything; admins love SELECT * accidents

Review before production

  • [ ] SSO enforced in non-dev
  • [ ] Role matrix written
  • [ ] Audit verified with a test mutation
  • [ ] Network restriction documented
  • [ ] On-call knows how to revoke access
  • [ ] Data retention for audit logs agreed

Summary

A safe internal admin is small, authenticated, authorized, audited, and boring. Add power only when the control plane already logs who did what. The breach headline writes itself when refund buttons ship faster than access reviews.