← Dev log

security

Building Qatar's PDPPL into a product, not around it

Building Qatar's PDPPL into a product, not around it

EduQate is a school-admissions platform, so it holds children's data. Qatar's Law No. 13 of 2016 (the PDPPL) gives people the right to access their data and to have it deleted. Before this work, both meant emailing support. Here is what the product does now, and why each piece is there.

What a parent can do themselves

  • Download everything held about them. One click in Settings returns a JSON export: the account profile, children, applications, document metadata and consent history.
  • Delete their own account. The account holder requests a one-time code by email, confirms it, and the account is deleted. No support ticket.
  • A child's admission records are not deleted with the parent's account. They stay with the school under its own record-keeping duties. Only the parent's own data goes.

Deleted has to mean gone, everywhere

Deleting an account while leaving other logins alive would mean "deleted" doesn't really mean gone. On deletion, every active refresh session for that user is revoked in the same operation, so any device still holding a token is signed out on its next request.

Backups are the harder case. Databases are restored wholesale for disaster recovery. If someone deletes their account on Monday and Sunday's backup is restored on Wednesday, they reappear, which quietly undoes a right they already exercised.

So deletion also writes a small tombstone to object storage that is never restored wholesale the way the database is. A reconciliation script runs after every restore and re-deletes anyone with a tombstone. If the storage credentials are missing, writing the tombstone logs and no-ops rather than throwing, which means that configuration has to be checked in production rather than assumed.

Consent that knows when it's out of date

There are three consent types: the privacy notice, the terms, and the application disclosure. Each has a version string, and every consent a user gives is stored with the version they agreed to. If the current version differs from the latest one a user accepted, the app blocks behind a re-consent screen until they accept again. Submitting an application requires ticking an explicit disclosure, in Arabic and English, on web and mobile.

Bumping a version prompts every account, so it is never done casually.

Saying what the app decides on its own

The privacy notice says plainly where the platform makes an automated decision with personal data. There is one: if a school offers a seat and the parent doesn't confirm enrolment by the deadline, the application is withdrawn automatically, with no human involved. The notice also says what the automation never touches: acceptance, rejection and interview outcomes remain human decisions.

The rule I follow: if the app does something automatically, the notice has to say so, so the notice text is part of the change, not only the code.

Audit logs on views, and an error tracker that stays blind

Accountability is "who looked at this record", not only "who changed it". Viewing an application or a document now writes an audit entry, the same as create, update and delete.

Error trackers capture request bodies, cookies and user identity by default, which can put names, emails and tokens in a third-party dashboard. On backend, web and mobile, default PII capture is off. Request bodies, cookies and auth headers are stripped before an event leaves the app, the user object is cut down to an ID, and anything email-shaped is redacted.

What isn't done

  • Guardian identity verification: needs a product decision on what counts as sufficient.
  • Data sensitivity classification across every model.
  • A retention schedule with an automatic purge, gated on schools agreeing a period.
  • Automated tests for the work above. It was verified by hand in the running app: export, deletion with sessions ending, the consent gate, the disclosure checkbox, and a real error with its payload inspected. Until the test pass lands, a regression here would be caught by manual re-checking or by a user noticing.