{"course":{"slug":"cloud-and-shipping","title":"Shipping It","url":"https://addaly.com/learn/cloud-and-shipping","level":"intermediate","track":"build"},"lesson":{"slug":"the-admin-surface","title":"The tools that can do anything","url":"https://addaly.com/learn/cloud-and-shipping/the-admin-surface","position":64,"of":74,"minutes":8,"answer":"The admin panel is unlimited authority with the fewest tests, so gate it with an explicit allowlist that answers 404, require a reason on every action, write an append-only audit log in the same transaction.","keyIdea":"The admin panel is unlimited authority with the fewest tests, so gate it with an explicit allowlist that answers 404, require a reason on every action, write an append-only audit log in the same transaction, and hold destructive powers as break-glass rather than by default."},"body":{"format":"markdown","text":"## The most dangerous page you will write\n\nYour admin panel can read every message, change any record, refund any payment and delete any account. It was built quickly, it has fewer tests than the rest of the application, and it usually has no audit trail. It is also the page an attacker most wants, and the page from which an internal mistake does the most damage.\n\nA useful way to hold it: an admin surface is not a feature, it is a set of powers granted to people, and every power needs a reason, a record and a limit.\n\n## Fail closed, and return nothing\n\nAccess should be an explicit allowlist: a list of accounts, checked on every request, defaulting to refusal. Not a role field that could be empty, not a check on an email domain, not the absence of a flag.\n\nAnd for an unauthorised caller, answer 404 rather than 403. A 403 confirms the path exists, which is exactly what somebody probing wants to learn. This is a rare case where hiding is worth the confusion, because the audience for the truthful answer is nobody legitimate.\n\nPut a second factor in front of it. If your identity provider supports it, require re-authentication for the admin session specifically, so a stolen ordinary session does not carry admin power.\n\n## Log every action, permanently\n\nAn admin audit log is a different thing from application logs: append-only, retained far longer, and containing who did what to which object at what time, with the before and after values where practical.\n\n```json\n{\"ts\":\"2026-09-06T11:20:04Z\",\"actor\":\"handle:priya\",\"action\":\"account.suspend\",\n \"target\":\"identity:8812\",\"reason\":\"report #4471\",\"before\":{\"status\":\"active\"},\n \"after\":{\"status\":\"suspended\"},\"request_id\":\"9ab3f1\"}\n```\n\nThe `reason` field is worth insisting on, and worth making mandatory. It changes behaviour: an action that must be justified in a sentence is an action somebody thinks about first. It is also the difference between a defensible moderation decision and an unexplained one when the affected person asks.\n\nWrite the log before performing the action, or in the same transaction. A log written afterwards is missing exactly the entries where something went wrong.\n\n## Impersonation, which you will eventually want\n\n\"View as this user\" is useful for support and dangerous. If you build it:\n\n- Log the start and end, with the reason.\n- Make it visible in the interface, to both parties where possible, so nobody forgets which account they are in.\n- Keep it read-only unless there is a specific reason not to, and log every write separately if there is.\n- Time-limit the session.\n- Never impersonate to read private content without a documented reason: a support ticket from that user, or a legal obligation. \"Curiosity\" is how staff-access scandals begin at every company that has had one.\n\n## Least privilege for the humans, too\n\nNot everybody who needs admin needs all of it. Three tiers usually cover a small team: read-only support, moderation actions, and destructive or financial operations. The third tier should be a short list.\n\nFor dangerous operations (bulk deletion, database access, refunds above a threshold) the pattern is break-glass: the power exists, it is not held by default, obtaining it is a deliberate act that is logged and announced to the team, and it expires. This is the same idea as a rotating key, applied to authority.\n\n## Do not run destructive operations from a text box\n\nAn admin page with an arbitrary SQL box is a shell on your production database with a nicer font. If people need to run queries, give them a read-only database user, a saved-query interface, or a proper tool (Metabase is free and does this well) and keep writes in named operations with a confirmation and a log entry.\n\nFor bulk actions, three habits: preview what will change and show the count before executing, require typing the count or the object name to confirm, and make the operation reversible or at least recoverable from the audit log.\n\n## Review who has access, on a schedule\n\nAccess accumulates. A contractor, a former colleague, a personal account added during an incident. Once a quarter, print the list of admins and confirm each one line by line, then remove the rest. Add the same review for API tokens, database users and cloud accounts.\n\nThis is dull and it consistently finds something, which is why it belongs on the same quarterly calendar as the restore drill and the key rotation."},"check":{"question":"An admin panel writes its audit entry after successfully performing each action. Which class of event is systematically missing from the log?","options":[{"text":"Read-only views, which are not actions and therefore not logged","correct":false,"tempting":"Reads often go unlogged by design, but their absence follows from what is instrumented rather than from where the write is placed."},{"text":"Actions by accounts whose access was later revoked","correct":false,"tempting":"Revocation removes future access; it does not remove entries already written for past actions."},{"text":"Actions that partially applied and then failed, which are exactly the ones an investigation needs","correct":true},{"text":"Bulk actions, since one entry covers many objects","correct":false,"tempting":"Coarse granularity for bulk operations is a real weakness, but it is a design choice about entry shape, not a consequence of ordering."}]},"prev":{"slug":"payments-and-what-you-must-not-store","title":"Taking money without holding card numbers","url":"https://addaly.com/learn/cloud-and-shipping/payments-and-what-you-must-not-store"},"next":{"slug":"the-day-you-are-breached","title":"The first day of a security incident","url":"https://addaly.com/learn/cloud-and-shipping/the-day-you-are-breached"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The tools that can do anything', in Shipping It. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/cloud-and-shipping/the-admin-surface (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The tools that can do anything","courseTitle":"Shipping It","module":{"index":7,"slug":"security-after-the-short-list","title":"Security after the short list"},"answer":null,"keyIdea":"The admin panel is unlimited authority with the fewest tests, so gate it with an explicit allowlist that answers 404, require a reason on every action, write an append-only audit log in the same transaction, and hold destructive powers as break-glass rather than by default.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-most-dangerous-page-you-will-write","runs":[{"text":"The most dangerous page you will write"}]},{"t":"p","runs":[{"text":"Your admin panel can read every message, change any record, refund any payment and delete any account. It was built quickly, it has fewer tests than the rest of the application, and it usually has no audit trail. It is also the page an attacker most wants, and the page from which an internal mistake does the most damage."}]},{"t":"p","runs":[{"text":"A useful way to hold it: an admin surface is not a feature, it is a set of powers granted to people, and every power needs a reason, a record and a limit."}]},{"t":"h2","id":"fail-closed-and-return-nothing","runs":[{"text":"Fail closed, and return nothing"}]},{"t":"p","runs":[{"text":"Access should be an explicit allowlist: a list of accounts, checked on every request, defaulting to refusal. Not a role field that could be empty, not a check on an email domain, not the absence of a flag."}]},{"t":"p","runs":[{"text":"And for an unauthorised caller, answer 404 rather than 403. A 403 confirms the path exists, which is exactly what somebody probing wants to learn. This is a rare case where hiding is worth the confusion, because the audience for the truthful answer is nobody legitimate."}]},{"t":"p","runs":[{"text":"Put a second factor in front of it. If your identity provider supports it, require re-authentication for the admin session specifically, so a stolen ordinary session does not carry admin power."}]},{"t":"h2","id":"log-every-action-permanently","runs":[{"text":"Log every action, permanently"}]},{"t":"p","runs":[{"text":"An admin audit log is a different thing from application logs: append-only, retained far longer, and containing who did what to which object at what time, with the before and after values where practical."}]},{"t":"code","lang":"json","label":"JSON","text":"{\"ts\":\"2026-09-06T11:20:04Z\",\"actor\":\"handle:priya\",\"action\":\"account.suspend\",\n \"target\":\"identity:8812\",\"reason\":\"report #4471\",\"before\":{\"status\":\"active\"},\n \"after\":{\"status\":\"suspended\"},\"request_id\":\"9ab3f1\"}"},{"t":"p","runs":[{"text":"The "},{"text":"reason","code":true},{"text":" field is worth insisting on, and worth making mandatory. It changes behaviour: an action that must be justified in a sentence is an action somebody thinks about first. It is also the difference between a defensible moderation decision and an unexplained one when the affected person asks."}]},{"t":"p","runs":[{"text":"Write the log before performing the action, or in the same transaction. A log written afterwards is missing exactly the entries where something went wrong."}]},{"t":"h2","id":"impersonation-which-you-will-eventually-want","runs":[{"text":"Impersonation, which you will eventually want"}]},{"t":"p","runs":[{"text":"“View as this user” is useful for support and dangerous. If you build it:"}]},{"t":"ul","items":[[{"text":"Log the start and end, with the reason."}],[{"text":"Make it visible in the interface, to both parties where possible, so nobody forgets which account they are in."}],[{"text":"Keep it read-only unless there is a specific reason not to, and log every write separately if there is."}],[{"text":"Time-limit the session."}],[{"text":"Never impersonate to read private content without a documented reason: a support ticket from that user, or a legal obligation. “Curiosity” is how staff-access scandals begin at every company that has had one."}]]},{"t":"h2","id":"least-privilege-for-the-humans-too","runs":[{"text":"Least privilege for the humans, too"}]},{"t":"p","runs":[{"text":"Not everybody who needs admin needs all of it. Three tiers usually cover a small team: read-only support, moderation actions, and destructive or financial operations. The third tier should be a short list."}]},{"t":"p","runs":[{"text":"For dangerous operations (bulk deletion, database access, refunds above a threshold) the pattern is break-glass: the power exists, it is not held by default, obtaining it is a deliberate act that is logged and announced to the team, and it expires. This is the same idea as a rotating key, applied to authority."}]},{"t":"h2","id":"do-not-run-destructive-operations-from-a-text-box","runs":[{"text":"Do not run destructive operations from a text box"}]},{"t":"p","runs":[{"text":"An admin page with an arbitrary SQL box is a shell on your production database with a nicer font. If people need to run queries, give them a read-only database user, a saved-query interface, or a proper tool (Metabase is free and does this well) and keep writes in named operations with a confirmation and a log entry."}]},{"t":"p","runs":[{"text":"For bulk actions, three habits: preview what will change and show the count before executing, require typing the count or the object name to confirm, and make the operation reversible or at least recoverable from the audit log."}]},{"t":"h2","id":"review-who-has-access-on-a-schedule","runs":[{"text":"Review who has access, on a schedule"}]},{"t":"p","runs":[{"text":"Access accumulates. A contractor, a former colleague, a personal account added during an incident. Once a quarter, print the list of admins and confirm each one line by line, then remove the rest. Add the same review for API tokens, database users and cloud accounts."}]},{"t":"p","runs":[{"text":"This is dull and it consistently finds something, which is why it belongs on the same quarterly calendar as the restore drill and the key rotation."}]}],"check":{"question":[{"text":"An admin panel writes its audit entry after successfully performing each action. Which class of event is systematically missing from the log?"}],"options":[{"text":[{"text":"Read-only views, which are not actions and therefore not logged"}],"correct":false,"tempting":[{"text":"Reads often go unlogged by design, but their absence follows from what is instrumented rather than from where the write is placed."}]},{"text":[{"text":"Actions by accounts whose access was later revoked"}],"correct":false,"tempting":[{"text":"Revocation removes future access; it does not remove entries already written for past actions."}]},{"text":[{"text":"Actions that partially applied and then failed, which are exactly the ones an investigation needs"}],"correct":true,"tempting":null},{"text":[{"text":"Bulk actions, since one entry covers many objects"}],"correct":false,"tempting":[{"text":"Coarse granularity for bulk operations is a real weakness, but it is a design choice about entry shape, not a consequence of ordering."}]}],"order":[1,2,3,0],"why":null},"prev":{"slug":"payments-and-what-you-must-not-store","title":"Taking money without holding card numbers","minutes":9},"next":{"slug":"the-day-you-are-breached","title":"The first day of a security incident","minutes":10}}}