learn-data-access-control-with-phoebe / Leader session 4 of 6
Learn Data Access Control with Phoebe · Leader session 4 of 6

Classify, then mask

Classification has a bad reputation with leaders, and it deserves most of it: it usually arrives as a seven-tier scheme, a spreadsheet nobody maintains and a workshop that produces labels no policy ever reads. Tonight is the version that survives contact with a warehouse. Four classes, each one attached to a default treatment. One named exception role per masked class, carrying a written reason and a review date. And one class with no exception role at all, which turns out to be the single strongest sentence in the whole design.

🟡 Core Data and analytics leaders 45 min
0-3 · Welcome 3-22 · Four classes and one exception rule 22-40 · Build-along: classify one table 40-45 · Q&A
Part 0

Where classification actually earns its place

Sessions 1 to 3 built the frame: three counters that trade against each other, roles against attributes, and least privilege that people can work with instead of route around. All three assume somebody has said which columns matter and how much. That statement is classification, and it is only worth writing if a policy reads it. Tonight it gets written in the form a policy can read, over saltbox.customers at Saltbox, a consumer lending startup: ten columns from a plan name to a national ID, three regions, five jobs.

Live = presented in the 45 minutes Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with tonight. Four classes with a default treatment attached to each, so the label decides the behaviour instead of describing it. The exception rule in its full form, which is a named role plus a written reason plus a date. And the finding most classification exercises miss entirely: the free-text column, which is almost always filed by what it usually holds rather than by what it can hold.

LiveA label that no policy reads is decoration3 min▶

The test for a classification scheme is not whether the tiers are sensible. It is whether a person reading a column name can say, without asking anybody, what happens to that column by default and who is allowed the exception. If the answer needs a meeting, the scheme is a vocabulary rather than a control.

That is why each class here carries a default treatment. "Contact PII" is not a description of name, email and phone; it is a commitment that those columns come back masked unless a named role asks, and that the named role is written down where the policy lives.

Real world

Saltbox ran a classification exercise a year before anybody wrote a masking policy. Every column got a tag, the tags went into a governance tool, and the warehouse never referred to them once. When masking finally arrived, the team re-did the whole exercise from scratch, because nobody trusted tags that had never had a consequence attached to them.

Self-studyThe four words this session turns on4 min▶
  • Class - a group of columns that share a default treatment. The design is written against the class, not the column, because a grid of jobs against columns stops being readable at around ten columns.
  • Default treatment - what happens to a column of this class when nobody has said otherwise. Masked, or readable, and never "it depends".
  • Exception role - the one named role allowed to read a masked class in clear, with a stated reason and a review date. A class may have exactly one, or none.
  • Masked - the column comes back transformed rather than removed, so the row stays usable and the value stays unreadable. It is the third value, and it is what lets a job proceed honestly instead of being blocked.

One thing that is deliberately not on that list is a sensitivity score out of five. Scores invite averaging, averaging invites a threshold, and a threshold is where a column with a national ID in it ends up on the same side of a line as a postcode.

Part 1 · covers the four working classes and the free-text rule

Three or four classes, never seven

10 min live

Three or four is not a rule of taste, it is the range where people file columns consistently. Past that, two reasonable colleagues put the same column in different classes, two policies end up disagreeing about it, and the disagreement is discovered by an auditor rather than by you.

Four classes, and what each one commits you to The design is written against the class, so the label decides the behaviour class example columns default treatment Non-sensitive no personal content region, plan, product Readable anybody with warehouse access Contact PII reaches a human name, email, phone Masked by default one exception role, with a reason Highly sensitive opens another account national_id, dob Masked, no exception the absence is the point Financial restricted, not PII mrr, balances, credit limit Restricted, readable where the argument happens support_notes: free text filed as non-sensitive on day one what it CAN contain moves it up Seven classes is where two policies start disagreeing about one column.
🔍 Click to zoom - each class carries its default, and free text does not stay where it was filed.
LiveThe four classes, and the treatment each one implies4 min▶

Four classes cover a customer table without anybody having to think hard, and thinking hard is exactly what a classification scheme must not require at the moment somebody adds a column.

  • Non-sensitive - region, plan, product. Readable by anybody with warehouse access. This class exists so the other three stay small; if it is empty, the scheme is too suspicious to be used.
  • Contact PII - name, email, phone. Masked by default with one named exception role, because these are the columns whose entire purpose is reaching a specific human.
  • Highly sensitive - national IDs, full dates of birth, and anything that unlocks an account somewhere else. That last phrase is the working definition and it is more useful than any list, because it catches the recovery answer, the partial card number and the passport scan reference nobody thought about.
  • Financial - revenue, balances, limits. Not personal, still restricted, and reliably the class where the actual argument happens, because the people who want it are senior and their reason is genuinely good.

Notice what the classes do not encode: who is allowed to see them. That comes from the job, and keeping the two apart is what stops the scheme being rewritten every time somebody joins.

LiveWhat goes wrong at seven classes3 min▶

A seven-tier scheme fails in a specific and boring way, and it is worth naming because the failure is never attributed to the tier count.

  • Columns get misfiled. With four classes, the boundaries are obvious enough that two people agree. With seven, "sensitive personal" and "restricted personal" are both defensible for a date of birth, so the column lands wherever the person filing it happened to be that afternoon.
  • Two policies end up disagreeing. Once a column sits in one class in the governance tool and another in the warehouse tag, both are true and both are enforced somewhere. The disagreement is invisible until an audit reads both.
  • Nobody can hold it in their head. The scheme has to be usable by an engineer adding a column at five o'clock. Four classes fit in a sentence; seven need a page, and the page is not open.

The recoverable version of a seven-tier scheme is usually four classes with three of them empty. Merging the empty ones costs nothing and removes the ambiguity that was doing the damage.

LiveThe free-text rule3 min▶

A column is classified by what it can contain, not by what it usually contains. That single sentence catches the biggest classification miss in most estates, and it applies to every free-text field: support notes, comments, internal descriptions, the "other" box on a form.

Look at what a support notes column accumulates over a year of ordinary use. Agents paste in whatever the conversation required. That means card numbers read out over the phone, medical details offered as the reason for a missed payment, and every so often a password the customer volunteered before anybody could stop them. Nobody intended any of it, and all of it is now sitting in a column filed as operational text.

Sample before you argue. Take 200 random rows of your largest free-text column and read them. The conversation about which class it belongs in ends in about four minutes, and it ends without anybody having to be persuaded.

Self-studyThe two boundaries that actually get argued4 min▶

Two columns cause most of the debate in a first classification pass, and both are worth deciding in advance so the meeting does not stall on them.

  • Date of birth. Argued as contact PII because it feels mild, and it belongs in highly sensitive because it is a standard identity verification answer at banks, telecoms and utilities. A birth date plus a name is often enough to pass a phone check somewhere else, which is precisely the "unlocks another account" test.
  • The customer identifier. Argued as non-sensitive because it is a meaningless integer, and it is not meaningless once it can be joined back to a table that carries a name. Identifiers are usually the column that needs the third value rather than either binary answer.

A third one shows up later and rarely in the first pass: derived columns. A risk score computed from a national ID does not carry the ID, but it can leak information about it, and a churn probability sitting next to a name is a statement about a person. Derived columns inherit the class of their most sensitive input until somebody demonstrates otherwise.

Part 2 · covers exception roles, approval, review and the third value

The exception is the whole decision

8 min live

Masking a class is the easy half and it is not where the risk lives. Everything interesting is in the exception: which single role reads the class in clear, for what stated reason, approved by whom, reviewed when. A masked class with a vague exception is weaker than an unmasked one, because it looks decided.

The Saltbox exception map Two masked classes. One has a gate, one does not. column default exception who reads it in clear email, phone contact PII masked by default GATE role: support_agent reason on file, reviewed support_agent must reach the human national_id highly sensitive masked by default NO GATE exception list: none nobody, in any form An empty exception list is a decision on the record, not an oversight. Masking national_id takes leaks from 109 to 94. Adding email and phone: 94 to 70. The remaining 70 are a role modelling problem, not a masking one.
🔍 Click to zoom - one gate with a name on it, and one absence that is deliberate.
LiveMasked by default, with one named exception4 min▶

The Saltbox design masks contact PII for everybody and names exactly one exception: the support agent role, because support has to reach the actual human on the other end of a ticket. That reason is written next to the exception, in one sentence, and it is deliberately specific enough to be falsified. If support ever stops making outbound contact, the sentence is no longer true and the exception goes.

National ID has no exception role at all, and that absence is the point rather than a gap somebody has not got to yet. Nobody at Saltbox has a job that requires reading a national ID out of the warehouse, so there is no role to name. An empty exception list survives a review, because there is nothing to justify. A list with one vague entry does not.

  • One role, not a group of roles. Two exception roles on one class is the beginning of "and also", and "and also" is how a masked class quietly becomes readable.
  • A reason a stranger can evaluate. "Operational necessity" is not a reason. "Has to phone the customer back" is, because somebody can check whether it is still true.
  • Absence beats vagueness. The strongest line in a design is a class with no exception at all, and it only stays strong if nobody adds a hedge to it.
LiveWho approves an exception, and how it comes back3 min▶

An exception needs three things attached to it before it is deployed, and the third is the one that is always missing.

Attached to the exceptionWhy it is there
A named role, never a personPeople join and leave the role; the decision is about the job, so it survives them
A one-sentence reasonThe only thing a reviewer can evaluate later. It has to be checkable, not respectable
An approver, by nameWhoever owns the table. Not the platform engineer who has the ability, and not the requester's manager alone
A review dateWithout one, the exception outlives the reason and nobody notices, because nothing prompts anybody to look

The review itself is short if the reason was written well: read the sentence, ask whether it is still true, keep or remove. A review that requires reconstructing why an exception exists has already failed, and it fails at the moment the exception is granted rather than at the review.

LiveThe third value, and why a binary grid cannot work4 min▶

Some jobs need a column to be present without needing it to be readable. That is not a compromise, it is the honest requirement, and a grid offering only allowed or denied forces a wrong answer every time it comes up.

The churn model at Saltbox is the clean example. It needs a stable customer key so rows join across tables and across months. It does not need to know whose key it is. Allowed hands over a real identifier the work never asked for. Denied removes the join and the model with it. The third value, masked, keeps the rows joinable and the people unidentifiable, and it is the only answer that is true to the requirement.

Find these before the meeting. Any column used in a join, a group-by or a segment definition is a candidate for shape-without-value. In most customer tables it is the identifier, the postcode and the birth date, and all three usually arrive at the meeting classified as a binary fight.

Self-studyWhat masking is worth, counted4 min▶

Saltbox scored every combination of five jobs, ten columns and three regions against the written design: 150 requests, of which 41 legitimately need a value in clear, 3 need it masked and 106 have no business being answered. Masking gets added in two steps.

Policies onLeaksOver-blocksStanding privileges
One shared analyst role109030
+ mask national_id for everybody94025
+ mask email and phone, except support70017
+ split the shared role five ways2201

Read that honestly and it is uncomfortable. Two rounds of masking, which is the work everybody associates with data protection, take leaks from 109 to 94 to 70. The next row, splitting one shared role into five, takes 70 down to 22 and standing privileges from 17 to 1. The remaining 70 are a role modelling problem rather than a masking one.

The lesson is not that masking is optional. It is that masking is the visible control and role modelling is the effective one, so a programme that spends its whole first year on classification and masking will report real progress and still be holding most of its exposure.

Build-along

Classify one real table, exceptions included

★ 18 min · everyone builds

One table, every column, no abstentions. If you have nothing to hand, use saltbox.customers: customer_id, name, email, phone, dob, national_id, region, plan, mrr, support_notes, across EU, UK and APAC.

Put every column in one of the four classes. Non-sensitive, contact PII, highly sensitive, financial. Every column, including the boring ones, because an unclassified column defaults to readable and that default was never approved by anybody. Work fast; the columns that take longest are the interesting ones and they get their own step.

For each masked class, name the exception role and write the reason. One role, one sentence, in the present tense, specific enough that somebody could tell you it is no longer true. If no job needs the class in clear, write none deliberately and say so out loud, because an empty list written on purpose reads completely differently from an empty list nobody filled in.

Find your free-text columns and reclassify them. Notes, comments, descriptions, the "other" box, the internal reason field. Reclassify each by what it can contain. If somebody objects, sample 200 rows in the room; the objection usually withdraws itself.

Mark every column where somebody needs the shape but not the value. Walk the joins and the group-bys. Each one you find is a place where a binary grid would have forced you into either handing over an identifier or breaking the work, and both of those decisions are currently being made somewhere in your estate by default.

Write an approver and a review date next to each exception. A name, not a team, and a real date within the next two quarters. An exception with no date attached is a permanent grant that has been described as temporary.

Keep the classification where the policies live. A class list in a governance tool that the warehouse never reads is the exercise Saltbox had to do twice. Next to the policy definitions it becomes something a change can be checked against, and something an auditor can be handed without a rehearsal.

Homework

Before session 5

30 min
  1. Finish the classification for one real table and count how many classes you actually used. If it is more than four, find the two you can merge without losing a decision.
  2. Write the exception register for that table: class, exception role, reason, approver, review date. One row per masked class, including the rows where the exception is none.
  3. Sample 200 rows of your largest free-text column and note the three most surprising things in it. Bring the count, not the contents.
  4. List the columns that need shape without value, and for each one say what currently happens instead. In most estates the honest answer is that the real identifier is handed over.
  5. Ask whoever owns your most sensitive table whether they know they are the approver for its exceptions. An approver who has not been told is a signature waiting to be a surprise.
Check yourself

Three questions

A support notes column mostly holds shift handover text. Over a year it has also collected a card number, a medical detail and a password a customer volunteered. Which class does it belong in?

Free text accumulates whatever the conversation required. Classifying it by typical content guarantees the exceptions are unprotected, and the exceptions are the whole reason the column matters.

The national_id class is masked and has no exception role. Someone proposes adding one for a team with no stated use yet. What does the empty list currently mean?

Absence beats vagueness. An empty exception list survives a review because there is nothing to justify, and anything added to it has to arrive with a reason somebody outside the team can evaluate.

Starting from one shared analyst role at 109 leaks, what do the two masking steps achieve?

109 to 94 to 70. The next step, splitting the shared role five ways, is what takes 70 to 22 and standing privileges from 17 to 1. Masking is the visible control; role modelling is the effective one.

Sources covered

What tonight maps to

✓ Three or four working classes, and why not seven The four classes with a default treatment attached to each, the two failure modes of a larger scheme, and the merge that recovers one.
✓ Exception roles and their justification One named role, a checkable reason, an approver and a review date. The Saltbox design in full, including the class with no exception role at all.
✓ The free-text rule Classified by what a column can contain rather than what it usually contains, with the sampling method that ends the argument.
✓ The third value: present but not readable Why a binary grid forces a wrong answer on every join key, and how to find the shape-without-value columns before the meeting.
◐ Masking policy mechanics, policy tags and evaluation order The builder track builds the policies, including the rule that a row policy is evaluated first and a column carries one policy type.
◐ Tokenisation, pseudonymisation and re-identification risk Named tonight as the mechanism behind the third value. The builder track covers surrogate keys and quasi-identifiers properly.
◐ Evidence, the exception register and recertification Session 5. Tonight produces the register; session 5 is what happens when somebody asks you to prove it is deployed.
· Inventory and classification as a policy programme Not covered by design. Classification appears here only as the input that decides which column gets masked and who the exception is.
· Data protection law and consent Not covered by design. What the law requires is a separate discipline; this is the machinery that carries out a decision once it has been made.

Session 4 cheat sheet

Four classesNon-sensitive, contact PII, highly sensitive, financial. Three or four is the working range.
The label decides behaviourEach class carries a default treatment. A label no policy reads is decoration.
Highly sensitive, definedAnything that unlocks an account somewhere else. Better than any list of examples.
Free textClassified by what it can contain, not what it usually contains. Sample 200 rows.
One exception roleA name, a checkable reason, an approver, a review date. Two roles is the start of "and also".
None is an answernational_id has no exception role, and that absence is the strongest line in the design.
The third valuePresent but not readable. A binary grid forces a wrong answer on every join key.
Masking is not the win109 to 94 to 70. The remaining 70 are role modelling, not masking.