Skip to main content

Personal Data Breach Response

This is the runbook for handling a confirmed or suspected personal-data breach on the Broadford Living platform. It complements gdpr.md by describing what we do once something has gone wrong.

The first priority in any breach is to identify scope and contain the exposure. Notification follows scope, not the other way around.

What counts as a breach

For this platform a personal-data breach is any of:

  • Unauthorised access to a tenant or PM Clerk account (credential theft, session hijack).
  • Unauthorised access to the User Durable Object of a property (e.g. via a misconfigured permission gate).
  • Disclosure of a sub-processor credential (SmartThings API key, Daikin client_secret, Yale OAuth token) to a party outside the operations team.
  • Any incident at an upstream provider (Cloudflare, Clerk, SmartThings, Yale, Daikin, Hive, PostHog, Supabase) that they classify as affecting our data.
  • Loss of integrity (e.g. silent data corruption that misattributes events to the wrong tenant).
  • Loss of availability that prevents tenants from controlling their property for an extended period — see § Business continuity below; this is a breach of GDPR Art. 32(1)(b).

Detection

Detection is upstream-led by design. We rely on:

  1. Provider notifications. Cloudflare, Clerk, SmartThings, Yale, Daikin, Hive, PostHog and Supabase each contact the named primary on file when they identify an incident affecting our account. Make sure the registered contact is a shared inbox monitored 24/7, not an individual.
  2. Anomaly review of access logs. The PostHog request stream (after set_user_id is set on every authenticated route) is the canonical access log. Spikes in 401/403 rates, unusual cross-property access patterns, or a single Clerk user_id touching many property IDs are the typical anomalies to chase.
  3. Internal monitoring. Cloudflare Workers Analytics + the PostHog dashboard.
  4. Tenant or PM report. A tenant noticing unauthorised activity in their app is a valid trigger.

Triage runbook

When a breach is suspected, work through the following in order. Do not rotate credentials before scoping — rotation can destroy evidence.

1. Confirm

  • Get the report in writing from the upstream provider, the reporter, or the monitoring source.
  • Open an incident document at docs/docs/incidents/{YYYY-MM-DD}-{slug}.md (private to the repo). Capture facts only — no speculation.

2. Identify scope

For each candidate user/property:

  • PostHog: filter the request stream by user_id and/or property_id for the 30 days preceding the suspected exposure window. Note unusual paths, IP addresses, and timing.
  • D1 activity_log: query for any staff-personnel actions touching the affected property/portfolio. Look in particular for it_setup.daikin_credentials_updated, it_setup.created, it_setup.deleted, and any *_fingerprint metadata.
  • bf_auth access_logs: query for token operations on the implicated supplier accounts (oauth_callback, token_retrieved, refresh_failed). The success flag and any error message are useful here.
  • User Durable Object: read the DO's request log via the getActivity RPC for the affected property. This shows guest-list and access changes.

If the scope cannot be bounded with the data above, treat the worst case as the operating assumption and note that uncertainty in the incident document.

3. Containment

Once scope is bounded, rotate / revoke as appropriate:

Compromised resourceActionCode reference
Clerk session of a specific userRevoke session via Clerk dashboardClerk admin UI
All sessions of a userForce sign-out via Clerk Backend APIClerk admin UI
SmartThings API key for a propertyRegenerate in ST Pro, then re-run POST /it-admin/properties/{id}/setup to re-encrypt and store. Old key continues to work until ST revokes it on the upstream side.crates/bf_resident/src/handlers/it_setup/handlers.rs::initialize_it_setup
Yale OAuthRevoke at Yale, force a fresh OAuth via the installer flow.Yale OAuth callback handler
Daikin OAuthRevoke at Daikin, re-OAuth via the installer flow; if client_id/secret are also suspect, regenerate at Daikin and call save_daikin_credentials to re-encrypt new values.save_daikin_credentials handler
Yale supplier admin passwordRe-derive deterministically from the seed (bf_auth credential generation). The seed itself is in Workers Secrets; rotate per key-rotation-plan.md if the seed is suspect.crates/bf_auth/src/credentials.rs
BF_ENCRYPTION_KEYFollow the rotation plan in key-rotation-plan.md. Old ciphertext becomes unreadable and must be re-encrypted with the new key.crates/bf_resident_db/src/crypto.rs
User Durable Object data integrityDO state cannot currently be rolled back independently of D1; restore from D1 PITR if cross-store consistency was preserved at the suspected timestamp.Cloudflare D1 PITR

Every containment action must be entered in the incident document with timestamp and actor.

4. Eradication

  • Identify the root cause. If it was a permission gate, fix crates/bf_user/src/permissions.rs and the relevant test cases. If it was a leaked credential, identify how it leaked and patch.
  • Add a regression test before redeploying.
  • If the bug existed in production, file an entry in security-audit.md under "Known Issues" until the fix has burned in for a release cycle.

5. Recovery

  • Confirm with the affected tenant(s) and the Controller that normal operation has resumed.
  • If physical access was implicated (Yale lock), the Controller may need to authorise lock changes per the tenancy agreement.

Notification

GDPR Art. 33 requires the Controller to notify the supervisory authority within 72 hours of becoming aware of the breach. As Processor we have a derivative obligation to notify the Controller "without undue delay". Practically:

  • Within 24 hours of confirmed scope: notify Broadford (Controller). Provide the incident document as it stands at that point.
  • Within 72 hours of confirmation: Broadford notifies the ICO. We provide whatever evidence and timeline they need to file.
  • Affected tenants: notification is the Controller's call under Art. 34. We provide the data subject list and the anomaly evidence; Broadford issues the communication, drawing on tenancy-agreement contact details.

If the breach concerns a sub-processor (e.g. Yale notified us), the same chain runs but the upstream provider's notification is the trigger, and we forward their disclosure to the Controller.

Post-incident

  • Close the incident document with: timeline, root cause, containment actions, lessons.
  • If a key was rotated, append the rotation to key-rotation-plan.md.
  • Schedule a 30-day follow-up to confirm no recurrence.
  • If the incident affected the assumptions in gdpr.md (e.g. revealed a category of data we hadn't documented), update that document.

Roles and contacts

RoleHolder
Primary incident contactPracticable Systems on-call (rotation maintained internally)
DeputyTBD
Controller liaisonBroadford-side property/data lead
Provider escalationPer each provider's incident page (Cloudflare Status, Clerk Status, etc.)

The on-call rotation, deputy assignment and Broadford-side contact are tracked outside this repo for confidentiality. Update the incident-response runbook in 1Password when they change.

Business continuity reference

A prolonged outage that prevents tenants from operating their home is itself a breach of GDPR Art. 32(1)(b) ("ability to ensure ongoing availability"). The platform's mitigations are out of scope for this document — see the operational runbook — but two notes:

  • Smart-tech retains local fail-safes (lock keypads, physical keys provided by the hardware) so a network outage never causes lockout.
  • During an outage tenants can still authenticate via Clerk magic code (which uses a separate email path), so account recovery is independent of our backend.