Exercises: Privacy Engineering

Solutions and grading notes are in the instructor companion. Exercises marked ๐Ÿงช use code/pii_scan.py. Nothing here is legal advice, and several exercises ask you to identify where a legal determination is required rather than to make one.


Warm-Up

Exercise 31.1 โ€” Six capabilities

Difficulty: โ˜…โ˜†โ˜† ยท Time: 15 minutes

ยง31.1 reduces the major regulations to six capabilities: find, export, delete, correct, restrict, account.

For a system you know (or Kestrel, from ยง31.13), rate each green (a query), amber (a script somebody would have to write), or red (an investigation).

Then: ยง31.1 claims capability 1 carries the other five. Test the claim โ€” is there one of the six that would still be hard even with a perfect resolver?


Exercise 31.2 โ€” Personal or not

Difficulty: โ˜…โ˜†โ˜† ยท Time: 15 minutes

For each, say whether you would treat it as personal data, and in one line why:

  1. A salted SHA-256 hash of an email address.
  2. An IPv4 address in a web log.
  3. A device_id cookie value.
  4. A row in dim_product.
  5. postcode alone.
  6. postcode + birth_year + sex.
  7. An aggregate: revenue by state and month.
  8. An aggregate: revenue by ZIP3, age band, and sex, where some cells have a count of 1.

Then: which of the eight is a judgment you should not be making alone, and who should?


Exercise 31.3 โ€” Read the scan

Difficulty: โ˜…โ˜†โ˜† ยท Time: 20 minutes ยท ๐Ÿงช

python code/pii_scan.py --scan
  1. Before reading the summary table, predict which strategy has the better precision and which has the better recall. Most people get this backwards.
  2. Nine columns are flagged and are not personal data. Pick the three you find most annoying and say what rule change would suppress them โ€” and what that change would cost.
  3. Two columns are missed. Explain why neither is a tuning problem.
  4. email_hash is flagged by name, not confirmed by values, and is personal data. Which of the three confidence levels should it get, and why is this the hardest case in the fixture?

Core

Exercise 31.4 โ€” The false-positive budget

Difficulty: โ˜…โ˜…โ˜† ยท Time: 40 minutes ยท ๐Ÿงช

ยง31.2's โš ๏ธ callout argues that false positives, not false negatives, destroy a scanning program.

  1. At the tool's measured 64.0% precision, how many false positives does a warehouse with 400 genuinely personal columns produce? Show the arithmetic.
  2. Modify the tool so medium-confidence findings are reported in a separate section. Re-run. How many findings are in the high section, and what is its precision?
  3. Add a suppressions file. A dismissed column stays dismissed, with a reason and a date. What should happen when a suppressed column's values start matching a pattern?
  4. ยง31.2 says never route a medium finding anywhere but the column's owner. What do you do when the column has no owner (ยง30.4)?

Exercise 31.5 โ€” Hash, tokenize, or drop

Difficulty: โ˜…โ˜…โ˜† ยท Time: 30 minutes

Apply ยง31.3's question โ€” will anyone ever need the original value back? โ€” to each:

  1. email on dim_customer, used for joining and for a weekly marketing send.
  2. ip_address on clickstream, used for a monthly country-level report.
  3. date_of_birth, used for an age-band breakdown and for an age-restricted product check.
  4. phone, used by support agents to call customers.
  5. card_last4, shown on an order confirmation.
  6. A free-text notes column on support tickets.

For each: keep / hash / tokenize / drop, plus the one sentence you would write next to the decision and the date. Two of these are traps โ€” say which and why.


Exercise 31.6 โ€” Measure your own k

Difficulty: โ˜…โ˜…โ˜† ยท Time: 45 minutes ยท ๐Ÿงช

python code/pii_scan.py --k-anon
  1. Run it. Only one rung reaches k โ‰ฅ 5. Which, and what has been thrown away to get there?
  2. Modify K_LEVELS to add two rungs of your own. Can you find one that reaches k โ‰ฅ 5 and keeps age?
  3. Take a dataset you actually have. Pick three quasi-identifiers and compute the equivalence-class sizes. Report k, the singleton count, and the share of rows below k = 5.
  4. If you have no such dataset, use the case study's shape: a segment of ~40,000 from a base of ~1.9 million, and explain why the segment's k is worse than the base's.

Exercise 31.7 โ€” The suppression trade

Difficulty: โ˜…โ˜…โ˜† ยท Time: 40 minutes

Case Study 2 chose state + age band + sex with 3.1% suppression over ZIP3 + age band + sex with 48.4%.

  1. Reproduce the trade table for a dataset of your own, or for the chapter's fixture.
  2. At what suppression percentage would you refuse to release at all? Justify the threshold.
  3. The suppressed rows are systematically unusual people. Write the two sentences of disclosure you would attach to the file.
  4. Construct a case where suppression is the wrong tool and generalization is the only honest answer.

Exercise 31.8 โ€” Coverage on your own manifest

Difficulty: โ˜…โ˜…โ˜† ยท Time: 60 minutes ยท ๐Ÿงช

python code/pii_scan.py --coverage
python code/pii_scan.py --coverage --deadline 7
  1. Run both. Three locations move from "acceptable" to "gap" at the shorter deadline. Which, and why does that matter operationally?
  2. Write your own manifest. Every place a person's data lives in a system you know โ€” including logs, caches, vendor systems, and backups. Aim for at least fifteen entries.
  3. Run the coverage report against it. Report the three group sizes.
  4. For each gap: which of ยง31.5's three kinds is it (undeletable format, backup, uncontrolled copy)?
  5. Pick the gap you would close first, and say why it is not simply the easiest one.

Exercise 31.9 โ€” Build the resolver

Difficulty: โ˜…โ˜…โ˜† ยท Time: 45 minutes

Case Study 1 found five identifier spaces for one person.

  1. Enumerate the identifier spaces in a system you know. Include the ones held by vendors.
  2. Write the resolution graph: which identifier maps to which, and via what table or API.
  3. Which edges require full history rather than current state? Case Study 1's resolver reads the SCD Type 2 history for exactly this reason.
  4. What happens to your resolver when an identifier space has no join back โ€” for example, a device that never logged in?
  5. ยง31.10 requires the same resolver for deletion and export. What breaks if they diverge, and how would you notice?

Difficulty: โ˜…โ˜…โ˜† ยท Time: 40 minutes

ยง31.7's ๐Ÿ” callout: an export is reproducible given an as-of timestamp, and the as-of for a send is the send time, not the build time.

  1. Write the SQL for a marketing export that is reproducible given :as_of.
  2. A file is built at 02:00 and sent at 09:00. A customer withdraws consent at 07:00. Trace what happens under (a) build-time filtering, (b) send-time re-filtering, (c) both.
  3. Now the send is a batch to a vendor who transmits over six hours. What is the as-of? There is more than one defensible answer; pick one and say what you would document.
  4. ยง31.7 warns that adding a consent check to a flow running on a different lawful basis is a bug. Construct the failure โ€” what gets silently dropped, and how long before anyone notices?

Advanced

Exercise 31.11 โ€” Extend the scanner

Difficulty: โ˜…โ˜…โ˜… ยท Time: 90 minutes ยท ๐Ÿงช

Add three of the following to pii_scan.py, with fixtures that trigger and do not trigger each:

  1. Entropy-based detection of opaque identifiers โ€” would it have caught session_id? Measure.
  2. A --suppressions file (Exercise 31.4).
  3. Column-name similarity to a known-personal column in another table โ€” cust_ref in one table resembling customer_ref in another.
  4. A check that a confidential column has a masking policy attached (ยง31.6).
  5. Free-text scanning that reports a rate rather than a classification โ€” notes has 3 emails in 40 rows; report 0.075, not a label.
  6. A luhn-check on candidate payment columns, to separate real card numbers from 16-digit identifiers.

Then: re-run --self-check (it must still pass) and report the new precision and recall. If your additions did not change both numbers, say what that means.


Exercise 31.12 โ€” Design the deletion DAG

Difficulty: โ˜…โ˜…โ˜… ยท Time: 90 minutes

ยง31.5's ๐Ÿงฑ callout gives a six-step DAG. Implement it for a system you know, as an Airflow DAG or as pseudocode with the same structure.

Required:

  1. A generated manifest, not a written one. Say what it is generated from.
  2. A resolver step covering every identifier space from Exercise 31.9.
  3. The pre-deletion snapshot, with its own retention.
  4. A verification step that fails the DAG when anything remains.
  5. An attestation record with per-location evidence.

Then answer: step 5 fails a request that has a statutory deadline attached. Is failing loudly correct? Design the escalation path for a verification failure at day 27 of 30.


Exercise 31.13 โ€” The residency audit that follows the undrawn flows

Difficulty: โ˜…โ˜…โ˜… ยท Time: 60 minutes

ยง31.8's failure was a log line, at a 0.02% parse-failure rate, shipping EU rows to a US aggregator.

  1. List every place your system emits data that is not a data flow anyone drew. Logs, metrics, traces, error payloads, crash reports, support exports, screenshots, alerts with sample rows.
  2. For each: could it carry a field tagged personal? How would you know?
  3. Implement the rule, not the review โ€” a logging wrapper that refuses to serialize a tagged field. What does it do when it refuses: drop, redact, or raise?
  4. ยง31.8's version fails at the log call. What does that cost when it fires in production at 3am, and is that the right trade?

Exercise 31.14 โ€” Composition across releases

Difficulty: โ˜…โ˜…โ˜… ยท Time: 60 minutes

Case Study 2's four quarterly extracts shared 24,724 members, and behavioural columns became quasi-identifiers across releases.

  1. Demonstrate it. Generate two releases from one population with 60% overlap, each k-anonymous alone, and show that the intersection is not.
  2. Kestrel banded the behavioural columns. Construct a way the releases could still be composed after banding.
  3. Compare the two structural answers โ€” re-draw the population each period, or coarsen the behaviour โ€” on utility, complexity, and how they fail.
  4. Is there a k-anonymity analogue for a sharing relationship rather than a release? Sketch what it would have to measure.

Exercise 31.15 โ€” The bad find

Difficulty: โ˜…โ˜…โ˜… ยท Time: 45 minutes

ยง31.12's sequence: stop looking, escalate within the hour, preserve rather than delete, separate what is known from what is not, let legal determine notification.

  1. Write your organization's version. Who, specifically, within the hour? If you cannot name a person, that is the exercise's finding.
  2. ยง31.12 says the instinct to delete immediately is wrong. Construct the case where it is right, and say what makes it different.
  3. Write the four-line factual summary for the legacy_col_3 find, keeping known and unknown strictly apart.
  4. Role-play the pushback: a manager says "just drop the column, it's a deprecated table, why make this a thing?" Write the two sentences you say back.

Project Milestone

Exercise 31.16 โ€” Privacy for the Kestrel platform

Difficulty: โ˜…โ˜…โ˜… ยท Time: 3โ€“4 hours ยท ๐Ÿงช

Build ยง31.13's structure for your own platform.

  1. Scan every model you have built. Record findings by confidence, and open a suppressions file.
  2. Classify every high finding โ€” and record who made each call. If that person is you, note where it should not have been.
  3. Generate a manifest from your classification tags. Not a written list.
  4. Run the coverage report. Report the three group sizes and name every gap.
  5. Build the resolver for your identifier spaces.
  6. Add three assertions to Chapter 23's register: no personal data in gold, no unmasked confidential column in staging, and one of your own.
  7. Measure k on any extract your platform produces for anyone outside it. If it produces none, say what would change if it did.
  8. Write the one-page position on the gaps you will not close, in the form ยง31.5's backup answer takes.

Deliverable: the scan output, the generated manifest, the coverage report, the three assertions, and the one-page position.

The one-page position is the hardest part and the most valuable. A gap you have documented and defended is a different thing from a gap you have not noticed, and the difference is entirely in whether it is written down.