Topic 40

Accounts and Permissions

Safety

The Marquee's website needs to read films and screenings and to write bookings. That is its whole job. But the last page raised an uncomfortable question: if the application is ever tricked into running SQL it didn't mean to — by an injection, or simply by a bug — what else can it do? Could it drop a table? Read every customer's email? The answer depends entirely on something decided long before any attack: what the website's database account is allowed to do.

Databases have their own users and their own permissions, separate from anything the website knows about its visitors. The governing idea carries the most boring name in security — least privilege, meaning every account gets exactly the powers its job requires and not one verb more. Boring, and quietly decisive: it is the setting that fixes the blast radius of every future mistake in advance.

The Database Has Its Own Roster

First, an ambiguity to clear up, because the word "user" now means two different things. The Marquee website has thousands of users — customers with logins and passwords, managed by the application. The database has a much shorter roster of its own: database users, the accounts allowed to open a connection at all. Remember the connection string from Topic 38? It dialed in as marquee_site — that is a database user, and every one of the website's thousand visitors rides in through that single account.

A healthy setup has a handful of database accounts, each with one story. The website connects as marquee_site. Lora's reporting tool, which builds her Monday-morning sales summaries, connects as marquee_reports. And an administrator account exists for the rare occasions someone must change the schema itself. Different callers, different accounts — which only matters because of what comes next.

GRANT, Plainly

Permissions in SQL are handed out per account, per table, per verb, using a statement called GRANT. Here is the website's account being created and given precisely its job:

Creating the website's account and granting exactly its job — nothing more
CREATE USER marquee_site WITH PASSWORD '••••••••';

GRANT SELECT ON films TO marquee_site;
GRANT SELECT, INSERT ON bookings TO marquee_site;

Read the grants as the job description they are: marquee_site may read the film list, and may read and add bookings. The real list runs a little longer — screenings and customers get their SELECTs the same way — but the shape never changes: named verbs on named tables. Now notice everything the statements don't say. No UPDATE. No DELETE. And no DROP, ALTER, or CREATE — the account cannot touch the schema at all, because nobody ever granted it the power. In a database, abilities aren't taken away from accounts; they simply are never given.

If you want a picture to hang this on: hotel keycards. The cleaner's card opens the guest rooms, not the manager's office or the safe room — so a stolen cleaner's card is a bounded problem, and the bounds were set at hiring, not during the theft. That is GRANT: each card opens exactly the doors the job requires. Picture mapped; back to the real terms.

Two Defenses, Stacked

Now replay the last page's nightmare against this account. Suppose the worst: the search box was glued after all, and the hostile input arrives intact, so the database receives DROP TABLE bookings as a real statement over the website's connection. The injected SQL runs as the account that sent it — as marquee_site. And marquee_site was never granted that power:

The injected DROP, replayed against a least-privilege account — refused
DROP TABLE bookings;

ERROR: permission denied for table bookings

The exact wording varies by engine; the refusal doesn't. The attack that would have deleted forty thousand bookings instead bounces off a permission check, because a decision made years earlier — two GRANT lines — already determined that this account cannot destroy anything. This is why professionals stack the two defenses rather than choosing between them: parameters prevent injections from happening, and permissions contain whatever slips through anyway. Either one alone has saved companies; both together is the standard.

The Marquee's roster — each account gets its verbs, and only its verbs
database
the Marquee database — every connection dials in as someone
website
marquee_site — SELECT on films & screenings · SELECT, INSERT on bookings
reporting
marquee_reports — SELECT on everything, no writes at all
admin
full power over the schema — used rarely, and by a named person

Separate Accounts, Separate Stories

The same thinking extends across the roster. The reporting account reads everything and writes nothing: if Lora's summary tool is ever compromised, it can leak data — bad — but it cannot corrupt a single booking. A backup job gets an account that reads all tables and touches none. And the humans on a team get personal accounts rather than sharing an admin login, for a reason that has nothing to do with distrust: when something changes at 2 a.m., the database's records should say who, and a shared account makes every answer "somebody". That is the whole posture — one account per job, each telling one story.

Common Confusions
  • "The app should log in as the admin — it needs everything to work." It needs its verbs on its tables, nothing more. Admin-by-default turns every future bug and every injection into a total compromise; least privilege turns the same mistakes into refused statements.
  • "Permissions are paranoia for banks." They're the cheapest insurance in this book: two GRANT lines bounded the injection page's worst case from "all bookings deleted" to "permission denied". No other page buys so much safety with so little SQL.
  • "Database users are the website's users." The website's thousands of customers share one database account; the mapping from person to booking lives in the application and in the data itself. That's also why the app is the one holding database credentials, never the browser.
  • "If an attacker gets in, permissions won't matter." Injected SQL runs as the account that sent it, with that account's powers and no more. Permissions are precisely the defense that still works after the first one fails.
Why It Matters
  • Least privilege is the difference between "we got hacked and lost everything" and "they read the film list". The blast radius of a breach is either chosen in advance or discovered in the incident report.
  • GRANT literacy lets you ask the adult question on any team: "what can this account actually do?" — often the fastest security review a project ever gets.

Knowledge Check

The Marquee website has 12,000 registered customers. How many database accounts do they use between them?

  • 12,000 — one database user per customer
  • One — they all share the website's marquee_site account
  • Two — one account for brand-new customers, and one for the regulars
  • None — customers connect without any account

Why can't marquee_site drop the bookings table?

  • Because DROP statements can't travel over a website's connection
  • Because the database protects important tables from all accounts
  • Because nobody ever granted it that power — powers not given don't exist
  • Because its password simply isn't strong enough for such destructive statements

What does stacking parameters (Topic 39) with least-privilege permissions buy?

  • Nothing extra — either defense on its own already makes the other one unnecessary
  • Permissions alert the team whenever an injection is attempted
  • Parameters stop the attack; permissions bound the damage if one gets through
  • Together they make queries run measurably faster

Which account setup fits Lora's Monday-morning reporting tool?

  • SELECT plus INSERT and UPDATE, in case a report needs corrections
  • The admin account, since reports touch every table anyway
  • The same marquee_site account the website already uses
  • Its own account with SELECT on the tables and no write powers

You got correct