How the builder track works
Eight sessions, 45 minutes each, one artifact: a policy set over one real table, built control by control. Tonight the grant model and the written access design. Then a role hierarchy that survives a reorg in session 2, row-level security in session 3, column security and masking in session 4, tokenisation in session 5, secrets and service accounts in session 6, access review as a pipeline in session 7, and in session 8 the whole set is scored against 150 concrete access requests. The table throughout is saltbox.customers at Saltbox, a consumer lending startup, whose ten columns run from a plan name to a national ID number.
★ What you walk out with tonight. The six system roles and why handing somebody the top one is not a shortcut but a reset. The direction privileges flow through a role hierarchy, which is the single most common thing people get backwards. And a written access design for five real jobs, which is the difference between arguing about access and measuring it.
LiveThree counters, and why one of them is not "requests denied"3 min▶
Access control gets discussed as though the only failure were a leak. There are three, and they trade against each other. A leak is somebody holding data they have no business seeing. An over-block is somebody unable to do the job they were hired for. A standing privilege is a grant nobody currently needs, sitting there waiting for a compromised account to make it interesting.
The reason to count all three is that each one has a cheap fix that wrecks another. Leaks go to zero if you deny everything. Over-blocks go to zero if you grant everything. Standing privileges go to zero on a spreadsheet and come back the following week. A policy set is good when all three are low at once, and that is a design problem rather than a tightening exercise.
Saltbox's support team could not see phone numbers after an audit tightened the warehouse. Tickets still had to be answered, so the team started asking one senior analyst to look things up and paste them into the ticket. Leaks on the dashboard went to zero. Actual exposure went up, because now the phone numbers were in a ticketing system with a wider audience and no masking at all.
Self-studyThe vocabulary for the whole track4 min▶
- Securable object - anything a privilege can be granted on. They nest: organization, account, database, schema, then tables, views and functions inside the schema.
- Privilege - a specific permission on a specific object, such as SELECT on one table or USAGE on a schema.
- Role - the thing privileges are granted to. Users get roles; roles get privileges. A grant straight to a user is the exception you have to justify.
- Ownership - the OWNERSHIP privilege on an object. It is not just a label: an owner can grant privileges on what it owns, which is how access spreads without anybody deciding it should.
- RBAC and ABAC - roles decide in RBAC; in ABAC a policy evaluates attributes of the subject, the object, the action and the environment. Session 2 does the comparison properly.
- Row and column policies - rules that filter rows or transform column values at query time, applied on top of whatever the grants allowed. Sessions 3 and 4.
- Access review - the recurring exercise that finds standing privileges and takes them back. Session 7 makes it a pipeline instead of a spreadsheet.
Objects nest, and privileges flow upward
10 min liveTwo structures matter and they run in opposite directions. Objects nest downward from the account into the individual table. Roles nest too, and when one role is granted to another, the privileges travel up the hierarchy to the role above. Getting that direction backwards is how a analyst role quietly ends up holding everything a senior engineer can do.
LiveThe six system roles, and what each is actually for4 min▶
Warehouse platforms ship a small set of built-in roles. The names below are Snowflake's, because it documents the model most explicitly, and the shape carries across to the equivalents elsewhere.
| Role | For |
|---|---|
| GLOBALORGADMIN | Organization-level administration, above any single account |
| ACCOUNTADMIN | The top-level role, combining SYSADMIN and SECURITYADMIN. Two named humans, break-glass only |
| SECURITYADMIN | Manages grants globally, plus users and roles |
| USERADMIN | Creating and managing users and roles, and nothing else |
| SYSADMIN | Creating warehouses, databases and the objects inside them |
| PUBLIC | Automatically granted to every user. Anything you grant here, you grant to everyone |
Two operating rules follow from that table and both get broken constantly. PUBLIC is not a convenient place to put a shared reference table, because "everyone" includes the contractor who starts on Monday. And ACCOUNTADMIN is not a seniority badge; it is the role that makes every policy in the next seven sessions stop applying, which session 8 demonstrates with a number.
LiveOwnership is a privilege, not a label3 min▶
Whichever role creates an object holds OWNERSHIP of it, and an owner can grant privileges on the thing it owns. That is how access spreads without any decision being made: a role that happened to run a CREATE TABLE six months ago can hand SELECT to anybody who asks.
The counter to it is a managed access schema, where only the schema owner or a role holding MANAGE GRANTS can grant privileges on the objects inside. Object owners keep their objects and lose the ability to widen access to them, which is the right split for anything holding personal data.
Run both on a table you care about before you design anything. The first says who can already read it. The second is usually the surprising one, because roles accumulate grants nobody remembers requesting.
Self-studyPrimary and secondary roles, and the CREATE exception3 min▶
A session has one primary role and may activate secondary roles alongside it. Privileges aggregate across all of them for ordinary statements, which is convenient and makes "which role did that" harder to answer.
- The exception worth knowing: CREATE statements are authorised by the primary role only. That is what stops a new object silently landing under a role somebody happened to have active.
- The consequence for ownership: the primary role at creation time becomes the owner, so a person switching roles casually will scatter ownership across their whole role set.
- The consequence for audit: when a query is attributed to a user with five active roles, the query history tells you what was read and not which grant permitted it. Session 7 deals with reconstructing that.
The artifact that ends the argument
8 min liveAccess debates are unresolvable while the requirement lives in people's heads, because everybody is arguing from a different unstated version of it. Writing it down as a grid of job against column, with three possible values, turns it into something you can implement, review and score.
LiveWhy three values and not two4 min▶
A binary grid of allowed against denied forces a bad choice on every column that somebody needs the shape of without needing the value. A churn model needs a stable customer key to join on; it does not need to know whose key it is. A binary grid either hands over the real identifier or removes the join, and both answers are wrong.
The third value, masked, is what lets those jobs proceed honestly, and it is also what makes the design reviewable: a cell marked masked is a commitment that somebody will check the column is actually transformed rather than merely undisplayed.
Fill in the empty cells deliberately. An empty cell should mean somebody looked at that pairing and decided no. Most grids have empty cells because nobody thought about them, which is how a contractor ends up with a revenue column that nobody ever intended to hand over.
Self-studyClassification tiers, and keeping them to three4 min▶
The grid gets unwieldy past about ten columns, so columns get grouped into classes and the design is written against the class. Three or four classes is the working range; teams that define seven end up with columns misfiled and policies that disagree.
- Non-sensitive - region, plan, product. Readable by anybody with warehouse access.
- Contact PII - name, email, phone. Masked by default with one named exception role that has a stated reason.
- Highly sensitive - national IDs, full dates of birth, anything that unlocks an account somewhere else. Masked with no exception, and the absence of an exception is the point.
- Financial - revenue, balances, limits. Not personal, still restricted, and usually the class where the argument actually happens.
One trap: free-text columns belong in the most sensitive class they can contain, not the class they usually contain. A support notes column holds whatever an agent typed in a hurry, which over a year includes card numbers, medical details and the occasional password.
Write the access design, then read what you already granted
★ 18 min · everyone buildsBring a table with something sensitive in it. If you have nothing to hand, use saltbox.customers: customer_id, name, email, phone, dob, national_id, region, plan, mrr, support_notes, with rows in EU, UK and APAC.
List the jobs, not the people. Five is a good number to start. For Saltbox they are EU analyst, support agent, data scientist, finance analyst and APAC contractor. Write one sentence per job saying what it is for. If two jobs have the same sentence, they are one job.
Fill the grid with three values. clear, masked, or empty. Do the sensitive columns first and argue about them properly, because that is where the value is. Expect to discover that at least one column nobody can name a use for is currently readable by everybody.
Add the region list per job. Rows are a separate dimension from columns and a design that only covers columns will pass review and still leak. The APAC contractor needs no EU rows in any column, including the harmless ones.
Now read reality. Run show grants on table for the table and show grants to role for every role that appeared. Write the actual state next to your design. The gap is your backlog for the next six sessions.
Count standing privileges. For each role, count the columns it can read in clear that your design does not mark clear for that role. That single number is what an access review will report, and it is worth knowing before somebody else tells you.
Find out who owns the table. Then find out who else can grant on it. If the answer is "any role that has ever created something in this schema", you have found the reason the grants list looks the way it does, and managed access schemas are the fix.
Write the design where the policies live. A grid in a slide deck is a decision nobody can diff. In the repo next to the policy definitions, it becomes something a reviewer can compare a pull request against, and session 7 can check automatically.
Before session 2
30 min- Finish the grid for one real table, including the region dimension and including the cells you would rather not decide.
- Get the current grants into a text file next to it. Not a screenshot; something you can diff next month.
- Count how many humans currently hold ACCOUNTADMIN or its equivalent. Write down the two who should, and what the others were unblocked for.
- Find every grant made to PUBLIC. Each one is a decision to include the next person who joins, so check that was the intent.
- Ask one support or operations colleague what they cannot currently see that they need. Over-blocks are invisible from the platform side, and they are the reason shared logins exist.
Three questions
Role support_agent is granted to role analyst_eu. What happens?
Granting a role to a role hands the lower role's privileges to the higher one. This is the direction people get backwards, and it is how an analyst role quietly accumulates contact details.
A churn model needs a stable customer key to join on. The access grid offers only allowed or denied. What goes wrong?
This is exactly what the third value is for. A pseudonymised key keeps rows joinable and people unidentifiable, and a binary grid cannot express it.
Which of these is the strongest reason not to grant a shared reference table to PUBLIC?
A grant to PUBLIC is a grant to the whole future population of the account. That is occasionally what you want and it should always be a deliberate decision.