Initial commit
This commit is contained in:
256
docs/compliance/README.md
Normal file
256
docs/compliance/README.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# docs/compliance — reference examples for the compliance module
|
||||
|
||||
This folder contains **annotated example files** that document the schema used by the compliance generators. It is **not** the live compliance artifact directory.
|
||||
|
||||
| Location | Contents | Edit manually? |
|
||||
| -------------------------------- | ------------------------------- | ----------------------- |
|
||||
| `docs/compliance/` (this folder) | Schema examples and this README | Yes — static reference |
|
||||
| `compliance/` (repo root) | Live generated artifacts | No — run the generators |
|
||||
|
||||
---
|
||||
|
||||
## What each file in `compliance/` contains
|
||||
|
||||
### `compliance/data-map.yml`
|
||||
|
||||
A field-level PII inventory derived from every Payload collection in the workspace.
|
||||
|
||||
For each collection the generator records:
|
||||
|
||||
- **auth** — whether the collection has Payload authentication enabled
|
||||
- **piiFields** — every field carrying a `custom.pii` tag, plus Payload auth defaults (`email`, etc.) for auth collections
|
||||
|
||||
Each PII field entry captures the **category** (e.g. `contact-email`, `identification-username`), one or more **purposes** (e.g. `account-authentication`, `service-delivery`), whether the field is **exportable** (GDPR Art. 15) and **restrictable** (GDPR Art. 18), the **source** (`field-tag`, `auth-default`, or `auth-override`), and an optional per-field **retention** override.
|
||||
|
||||
See `docs/compliance/data-map.example.yml` for every field with annotations.
|
||||
|
||||
### `compliance/retention-policy.yml`
|
||||
|
||||
A collection-level retention schedule derived from `custom.retention` blocks in Payload collection configs.
|
||||
|
||||
For each collection the generator records:
|
||||
|
||||
- **purgeSchedule** — cadence for the background purge job (`daily` | `weekly` | `monthly`); **required** on every collection
|
||||
- **activeRetention** _(optional)_ — how long to keep a live record before triggering deletion
|
||||
- **coldArchive** _(optional)_ — long-term archive window for regulatory fixed-term obligations
|
||||
- **postDeletion** — what happens after a DSR erasure or account-closure request (`hard-delete` or `pseudonymize` with an ISO 8601 grace period)
|
||||
|
||||
See `docs/compliance/retention-policy.example.yml` for every field with annotations.
|
||||
|
||||
### `compliance/sub-processors.yml`
|
||||
|
||||
An inventory of every third-party processor that receives personal data from this application. Entries come from two sources and are merged at emit time:
|
||||
|
||||
1. **Library decision traces** (`docs/library-decisions/*.md`) — npm packages where `is-sub-processor: true` in the frontmatter.
|
||||
2. **Manual entries** (`compliance/sub-processors.manual.yml`) — non-npm vendors (REST APIs, SaaS integrations, infrastructure providers).
|
||||
|
||||
Each entry records the **package** name (sort key), **data-sent** description, **region**, **dpa-signed** and **sccs-required** booleans, a **contact** URL, the **decision** status, and a **source** discriminator (`library-trace` or `manual`).
|
||||
|
||||
See `docs/compliance/sub-processors.example.yml` for both entry kinds with annotations.
|
||||
|
||||
---
|
||||
|
||||
## How the files are generated
|
||||
|
||||
All three artifacts are regenerated together:
|
||||
|
||||
```bash
|
||||
pnpm compliance:emit-all
|
||||
```
|
||||
|
||||
Or individually:
|
||||
|
||||
```bash
|
||||
pnpm compliance:data-map # writes compliance/data-map.yml
|
||||
pnpm compliance:retention-policy # writes compliance/retention-policy.yml
|
||||
pnpm compliance:sub-processors # writes compliance/sub-processors.yml
|
||||
```
|
||||
|
||||
Each script also supports two diagnostic modes:
|
||||
|
||||
```bash
|
||||
pnpm compliance:data-map -- --print # write YAML to stdout (no file written)
|
||||
pnpm compliance:data-map -- --check # diff vs committed file; exit 1 on mismatch
|
||||
```
|
||||
|
||||
`--check` mode is used by the pre-commit hook and CI drift gate to detect uncommitted changes to collection configs that would cause `compliance/*.yml` to drift from the source of truth.
|
||||
|
||||
---
|
||||
|
||||
## Keeping `compliance/*.yml` up to date
|
||||
|
||||
Regenerate and commit the artifacts whenever you:
|
||||
|
||||
- Add or modify a Payload collection in any feature package
|
||||
- Change `custom.pii` tags on collection fields
|
||||
- Change `custom.retention` blocks on collection configs
|
||||
- Add a library decision trace with `is-sub-processor: true`
|
||||
- Add or update entries in `compliance/sub-processors.manual.yml`
|
||||
|
||||
The pre-commit hook and CI gate run `emit-all --check` and will reject the commit or PR if the committed YAML is stale.
|
||||
|
||||
---
|
||||
|
||||
## Annotating PII fields in a collection
|
||||
|
||||
Add `custom.pii` to any field in a Payload collection config:
|
||||
|
||||
```ts
|
||||
{
|
||||
name: "phone",
|
||||
type: "text",
|
||||
custom: {
|
||||
pii: {
|
||||
category: "contact-phone", // PiiCategory
|
||||
purpose: ["transactional-notifications"], // DataProcessingPurpose[]
|
||||
exportable: true,
|
||||
restrictable: true,
|
||||
// Optional per-field retention override:
|
||||
retention: {
|
||||
duration: "P1Y", // ISO 8601
|
||||
trigger: "from-last-access", // "from-creation" | "from-last-access" | "after-deletion"
|
||||
action: "hard-delete", // "hard-delete" | "pseudonymize"
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
For auth collections, `email` is automatically classified via `PAYLOAD_AUTH_PII_DEFAULTS`. Override per-collection via `custom.authPii`:
|
||||
|
||||
```ts
|
||||
{
|
||||
slug: "members",
|
||||
auth: true,
|
||||
custom: {
|
||||
authPii: {
|
||||
// Extend or replace the email default for this collection only:
|
||||
email: {
|
||||
category: "contact-email",
|
||||
purpose: ["account-authentication", "marketing-communications"],
|
||||
exportable: true,
|
||||
restrictable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See `packages/core-shared/src/payload/pii-types.ts` for the full list of allowed `PiiCategory` and `DataProcessingPurpose` values.
|
||||
|
||||
---
|
||||
|
||||
## Annotating retention policy in a collection
|
||||
|
||||
Add `custom.retention` to the collection config. `purgeSchedule` is **required** on every collection; the other fields are optional:
|
||||
|
||||
```ts
|
||||
{
|
||||
slug: "profiles",
|
||||
custom: {
|
||||
retention: {
|
||||
purgeSchedule: "daily", // Required: "daily" | "weekly" | "monthly"
|
||||
activeRetention: { // Optional: expire live records
|
||||
duration: "P2Y",
|
||||
trigger: "from-last-access",
|
||||
},
|
||||
coldArchive: { // Optional: regulatory fixed-term archive
|
||||
duration: "P7Y",
|
||||
trigger: "from-creation",
|
||||
},
|
||||
postDeletion: { // Optional: override the default post-deletion behaviour
|
||||
action: "pseudonymize", // "hard-delete" | "pseudonymize"
|
||||
duration: "P30D",
|
||||
trigger: "after-deletion",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Registering a sub-processor
|
||||
|
||||
### From an npm library (preferred)
|
||||
|
||||
Add sub-processor fields to the library's decision trace in `docs/library-decisions/`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
package: "@acme/notifications-sdk"
|
||||
version: "^2.3.0"
|
||||
decision: approved
|
||||
is-sub-processor: true
|
||||
data-sent: "user email address and display name for transactional notification delivery"
|
||||
region: EU
|
||||
dpa-signed: true
|
||||
sccs-required: false
|
||||
contact: https://acme-sdk.example/privacy/dpa
|
||||
---
|
||||
```
|
||||
|
||||
Run `pnpm compliance:sub-processors` to regenerate.
|
||||
|
||||
### Non-npm vendors (REST APIs, SaaS, infrastructure)
|
||||
|
||||
Create `compliance/sub-processors.manual.yml` if it doesn't already exist and add an entry:
|
||||
|
||||
```yaml
|
||||
- package: acme-email-service # slug-style identifier (no @ prefix)
|
||||
version: "REST API v3"
|
||||
data-sent: "user email address and message body for transactional email delivery"
|
||||
region: EU
|
||||
dpa-signed: true
|
||||
sccs-required: false
|
||||
contact: https://acme-email.example/legal/dpa
|
||||
decision: approved
|
||||
```
|
||||
|
||||
`compliance/sub-processors.manual.yml` is a hand-authored file committed to the repository alongside the generated `compliance/sub-processors.yml`. The generator merges manual entries at emit time and injects `source: "manual"` automatically. Do **not** add `source:` manually — it will be overwritten.
|
||||
|
||||
Run `pnpm compliance:sub-processors` after any change to regenerate `compliance/sub-processors.yml`.
|
||||
|
||||
---
|
||||
|
||||
## Policy templates
|
||||
|
||||
The `docs/compliance/templates/` directory contains fill-in-the-blank runbooks for the operational policies required before a GDPR-covered launch. These are **template originals** — do not edit them in place.
|
||||
|
||||
### Copy-to-`compliance/` workflow
|
||||
|
||||
1. Copy the desired template from `docs/compliance/templates/` to `compliance/` (repo root):
|
||||
```bash
|
||||
cp docs/compliance/templates/dsr-procedure.template.md compliance/dsr-procedure.md
|
||||
```
|
||||
2. Open the copied file and replace every `[FILL IN:]` marker with the project-specific value.
|
||||
3. Commit the filled file to the repo as a live compliance artifact (same cadence as `compliance/*.yml`).
|
||||
|
||||
### `[FILL IN:]` convention
|
||||
|
||||
Every placeholder that requires a project-specific value is marked with the literal string `[FILL IN:]` followed by a short description of what is expected. This makes placeholders machine-detectable and easy to audit.
|
||||
|
||||
To verify that all placeholders in your live artifacts have been resolved, run:
|
||||
|
||||
```bash
|
||||
grep -rn '\[FILL IN:' compliance/
|
||||
```
|
||||
|
||||
A zero-line result means all templates have been fully filled in. Any output identifies the file and line that still needs attention.
|
||||
|
||||
---
|
||||
|
||||
## `sccs-required` guidance
|
||||
|
||||
Set `sccs-required: true` when:
|
||||
|
||||
- The vendor's primary data-residency region is outside the EEA **and**
|
||||
- There is no EU adequacy decision covering that country (e.g. US before Privacy Shield replacement, India, most of Asia-Pacific)
|
||||
|
||||
Set `sccs-required: false` when:
|
||||
|
||||
- The vendor is EU/EEA-resident, **or**
|
||||
- The vendor's country has a current EU adequacy decision, **or**
|
||||
- The data transfer is covered by Binding Corporate Rules
|
||||
|
||||
When `sccs-required: true`, ensure SCCs are incorporated in the DPA before marking `dpa-signed: true`.
|
||||
79
docs/compliance/data-map.example.yml
Normal file
79
docs/compliance/data-map.example.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
# docs/compliance/data-map.example.yml — annotated reference for the data-map schema
|
||||
#
|
||||
# This file is NOT generated. It shows every possible field that can appear in
|
||||
# compliance/data-map.yml and explains what each field means.
|
||||
#
|
||||
# Generated artifact : compliance/data-map.yml
|
||||
# Generator : pnpm compliance:data-map
|
||||
# Source of truth : packages/*/src/integrations/cms/collections/*.ts
|
||||
# (custom.pii field tags + auth: true defaults)
|
||||
# PII types : packages/core-shared/src/payload/pii-types.ts
|
||||
|
||||
collections:
|
||||
# ── Non-auth collection with manually tagged PII fields ───────────────────────
|
||||
profiles:
|
||||
auth: false # true only for Payload auth-enabled collections (auth: true in collection config)
|
||||
slug: profiles # matches the Payload collection slug; used as the map key
|
||||
piiFields: # empty array ([]) when no PII fields are declared on the collection
|
||||
# ── Minimal field tag (no retention override) ─────────────────────────────
|
||||
- category:
|
||||
identification-name # PiiCategory — see packages/core-shared/src/payload/pii-types.ts
|
||||
# e.g. contact-email | identification-username | network-ip | …
|
||||
exportable: true # GDPR Art. 15 — include in data-export responses
|
||||
field: fullName # The Payload field name as declared in the collection
|
||||
purpose: # DataProcessingPurpose[] — why we collect and process this value
|
||||
- service-delivery # e.g. account-authentication | transactional-notifications |
|
||||
# marketing-communications | analytics-aggregation |
|
||||
# legal-compliance | service-delivery
|
||||
restrictable: true # User can request restriction of processing under GDPR Art. 18
|
||||
source: field-tag # Origin: "field-tag" — custom.pii tag on the collection field
|
||||
|
||||
# ── Field tag with a per-field retention override ─────────────────────────
|
||||
# Use when a field needs a stricter retention window than the collection default.
|
||||
- category: network-ip
|
||||
exportable: false
|
||||
field: lastIpAddress
|
||||
purpose:
|
||||
- legal-compliance
|
||||
restrictable: false
|
||||
retention: # Optional. Per-field retention window (overrides collection schedule).
|
||||
action: hard-delete # RetentionAction: "hard-delete" | "pseudonymize"
|
||||
duration: P6M # ISO 8601 duration (P6M = 6 months, P1Y = 1 year, P90D = 90 days)
|
||||
trigger: from-last-access # RetentionTrigger: "from-creation" | "from-last-access" | "after-deletion"
|
||||
source: field-tag
|
||||
|
||||
# ── Auth-enabled collection: defaults + overrides ─────────────────────────────
|
||||
# When a collection sets auth: true, PAYLOAD_AUTH_PII_DEFAULTS are applied
|
||||
# automatically. Each default can be overridden via custom.authPii in the
|
||||
# collection config without adding custom.pii to every field individually.
|
||||
members:
|
||||
auth: true # Activates PAYLOAD_AUTH_PII_DEFAULTS (email injected, password/salt/hash excluded)
|
||||
slug: members
|
||||
piiFields:
|
||||
# Injected automatically because auth: true — no field tag needed on the collection
|
||||
- category: contact-email
|
||||
exportable: true
|
||||
field: email
|
||||
purpose:
|
||||
- account-authentication
|
||||
- transactional-notifications
|
||||
restrictable: true
|
||||
source: auth-default # From PAYLOAD_AUTH_PII_DEFAULTS; not declared in collection fields
|
||||
|
||||
# The collection supplied custom.authPii.phone to extend or change the auth defaults
|
||||
- category: contact-phone
|
||||
exportable: true
|
||||
field: phone
|
||||
purpose:
|
||||
- transactional-notifications
|
||||
restrictable: true
|
||||
source: auth-override # Collection explicitly overrode or extended the auth default
|
||||
|
||||
# Regular custom.pii tag on a field inside an auth collection
|
||||
- category: identification-username
|
||||
exportable: true
|
||||
field: username
|
||||
purpose:
|
||||
- service-delivery
|
||||
restrictable: true
|
||||
source: field-tag
|
||||
59
docs/compliance/retention-policy.example.yml
Normal file
59
docs/compliance/retention-policy.example.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
# docs/compliance/retention-policy.example.yml — annotated reference for the retention-policy schema
|
||||
#
|
||||
# This file is NOT generated. It shows every possible field that can appear in
|
||||
# compliance/retention-policy.yml and explains what each field means.
|
||||
#
|
||||
# Generated artifact : compliance/retention-policy.yml
|
||||
# Generator : pnpm compliance:retention-policy
|
||||
# Source of truth : packages/*/src/integrations/cms/collections/*.ts
|
||||
# (custom.retention block inside each collection config)
|
||||
# PII types : packages/core-shared/src/payload/pii-types.ts
|
||||
|
||||
collections:
|
||||
# ── Collection with all optional retention fields populated ───────────────────
|
||||
# This represents a user-identity collection with the strictest schedule.
|
||||
profiles:
|
||||
slug: profiles # matches the Payload collection slug; used as the map key
|
||||
purgeSchedule:
|
||||
daily # Required. Cadence for the background purge job.
|
||||
# Allowed values: "daily" | "weekly" | "monthly"
|
||||
# Set via custom.retention.purgeSchedule in the collection config.
|
||||
|
||||
# Optional. How long to keep an active record before it enters the delete flow.
|
||||
# Omit if records should live indefinitely until a user-deletion request is received.
|
||||
activeRetention:
|
||||
duration: P2Y # ISO 8601 duration (P2Y = 2 years, P1Y = 1 year, P6M = 6 months, P90D = 90 days)
|
||||
trigger:
|
||||
from-last-access # RetentionTrigger: when the clock starts
|
||||
# "from-creation" — counted from record creation date
|
||||
# "from-last-access" — resets on every authenticated session
|
||||
|
||||
# Optional. Long-term cold-storage window before final purge.
|
||||
# Use when regulatory obligations require records to survive deletion requests for a fixed term
|
||||
# (e.g. financial records under EU accounting law, seven-year AML retention).
|
||||
coldArchive:
|
||||
duration: P7Y
|
||||
trigger: from-creation # Usually from-creation for regulatory fixed-term obligations
|
||||
|
||||
# Required. What happens after a user submits a deletion request (DSR erasure / account closure).
|
||||
postDeletion:
|
||||
action:
|
||||
pseudonymize # RetentionAction: "hard-delete" | "pseudonymize"
|
||||
# hard-delete — row is permanently removed at the end of the grace period
|
||||
# pseudonymize — PII columns are replaced with opaque tokens; row is kept
|
||||
# (use when the record must survive for aggregate analytics)
|
||||
duration: P30D # Grace period before the action executes (ISO 8601 duration)
|
||||
trigger: after-deletion # Fixed value — clock starts when the deletion request is accepted
|
||||
|
||||
# ── Collection with only the required minimum fields ─────────────────────────
|
||||
# Most content collections (articles, pages, media) use this minimal form.
|
||||
articles:
|
||||
slug: articles
|
||||
purgeSchedule: monthly # Low-sensitivity content; monthly sweep is sufficient
|
||||
|
||||
# postDeletion is required even for non-PII collections.
|
||||
# For content without PII, hard-delete with a short grace period is the default.
|
||||
postDeletion:
|
||||
action: hard-delete
|
||||
duration: P90D
|
||||
trigger: after-deletion
|
||||
68
docs/compliance/sub-processors.example.yml
Normal file
68
docs/compliance/sub-processors.example.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
# docs/compliance/sub-processors.example.yml — annotated reference for the sub-processors schema
|
||||
#
|
||||
# This file is NOT generated. It shows both kinds of entries that appear in
|
||||
# compliance/sub-processors.yml and explains what each field means.
|
||||
#
|
||||
# Generated artifact : compliance/sub-processors.yml
|
||||
# Generator : pnpm compliance:sub-processors
|
||||
# Sources : (1) docs/library-decisions/*.md — frontmatter where is-sub-processor: true
|
||||
# (2) compliance/sub-processors.manual.yml — hand-authored non-SDK vendors
|
||||
#
|
||||
# The two entry kinds are described below. Fields are rendered in a fixed order:
|
||||
# package first, then remaining fields alphabetically.
|
||||
|
||||
sub-processors:
|
||||
# ── Kind 1: library-trace entry ───────────────────────────────────────────────
|
||||
# Sourced automatically from a library-decision file in docs/library-decisions/.
|
||||
# To register a library as a sub-processor, add these fields to its frontmatter:
|
||||
#
|
||||
# is-sub-processor: true
|
||||
# data-sent: "user email and display name for transactional notifications"
|
||||
# region: EU
|
||||
# dpa-signed: true
|
||||
# sccs-required: false
|
||||
# contact: https://acme-sdk.example/privacy/dpa
|
||||
#
|
||||
# The generator reads package, version, and decision from the existing trace
|
||||
# frontmatter and merges the sub-processor fields automatically.
|
||||
- package: "@acme/notifications-sdk" # npm package name (natural sort key; must match package: in trace)
|
||||
contact: https://acme-sdk.example/privacy/dpa # DPA / privacy contact URL
|
||||
data-sent:
|
||||
"user email address and display name for transactional notification delivery"
|
||||
# Plain-language description of what data is transmitted
|
||||
decision: approved # Carry-through from library trace (approved | rejected | …)
|
||||
dpa-signed: true # Data Processing Agreement in place with this vendor
|
||||
region: EU # Primary data-residency region for this processor
|
||||
sccs-required:
|
||||
false # Standard Contractual Clauses required for data transfer?
|
||||
# Required when region is outside the EEA and no adequacy decision
|
||||
source: library-trace # Fixed value for entries sourced from a library decision file
|
||||
version: "^2.3.0" # npm version specifier from the library trace
|
||||
|
||||
# ── Kind 2: manual entry ──────────────────────────────────────────────────────
|
||||
# For sub-processors that are NOT npm packages (REST APIs, SaaS integrations,
|
||||
# infrastructure providers, etc.), create compliance/sub-processors.manual.yml
|
||||
# and add entries there. The generator merges this file at emit time and injects
|
||||
# source: "manual" automatically.
|
||||
#
|
||||
# Format for compliance/sub-processors.manual.yml (simple YAML list, no header):
|
||||
#
|
||||
# - package: acme-email-service
|
||||
# version: REST API v3
|
||||
# data-sent: "user email address and message content"
|
||||
# region: EU
|
||||
# dpa-signed: true
|
||||
# sccs-required: false
|
||||
# contact: https://acme-email.example/legal/dpa
|
||||
# decision: approved
|
||||
#
|
||||
# The "package" field is used as the sort key; use a slug-style identifier.
|
||||
- package: acme-email-service # Slug identifier for non-npm vendors (no @ prefix)
|
||||
contact: https://acme-email.example/legal/dpa
|
||||
data-sent: "user email address and message body for transactional email delivery"
|
||||
decision: approved
|
||||
dpa-signed: true
|
||||
region: EU
|
||||
sccs-required: false
|
||||
source: manual # Fixed value for entries sourced from sub-processors.manual.yml
|
||||
version: "REST API v3" # Non-npm version descriptor; use the API version or contract date
|
||||
170
docs/compliance/subject-linkage.example.md
Normal file
170
docs/compliance/subject-linkage.example.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# Subject linkage — annotated example
|
||||
|
||||
This document describes the `custom.subject` declaration pattern for Payload collections that store data belonging to **more than one data subject**. It serves as the anchor for downstream consumers adding PII-holding collections to the data map.
|
||||
|
||||
For background on the DSR cascade that consumes these declarations, see `docs/guides/dsr.md`.
|
||||
|
||||
---
|
||||
|
||||
## What is a `SubjectLink`?
|
||||
|
||||
A `SubjectLink` is a field-level declaration that maps a Payload relationship field to a data subject. The DSR cascade reads these at runtime to determine:
|
||||
|
||||
1. Which rows to include in an Art. 15 export (`dsr.export`).
|
||||
2. Which rows to delete or redact in an Art. 17 erasure (`dsr.delete`).
|
||||
|
||||
```ts
|
||||
type SubjectLink = {
|
||||
field: string; // Payload field name (must be a relationship field)
|
||||
kind: "self" | "owner" | "reference";
|
||||
target?: string; // Slug of the auth collection this field relates to
|
||||
role?: string; // Semantic label (informational, appears in the data map)
|
||||
};
|
||||
```
|
||||
|
||||
### `kind` discriminator
|
||||
|
||||
| `kind` | Meaning |
|
||||
| ------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `"self"` | The field **is** the subject row — used on the auth collection itself (e.g., Users) |
|
||||
| `"owner"` | The subject **created or owns** this row (e.g., the author of a post) |
|
||||
| `"reference"` | The subject is **referenced** in this row but does not own it (e.g., an assignee, a reviewer, a mentioned user) |
|
||||
|
||||
The difference between `"self"` and `"owner"` matters for deletion: rows marked `"self"` are the subject's account rows; rows marked `"owner"` are authored/owned content. Both are treated as owned data for Art. 15/17 purposes. `"reference"` rows are never deleted — only the linking field is NULLed.
|
||||
|
||||
---
|
||||
|
||||
## Worked example: support ticket collection
|
||||
|
||||
A support ticket has two subject relationships — the user who submitted it and the support agent assigned to it.
|
||||
|
||||
```ts
|
||||
// packages/<feature>/src/integrations/cms/support-tickets.collection.ts
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const SupportTicketsCollection: CollectionConfig = {
|
||||
slug: "support-tickets",
|
||||
custom: {
|
||||
subject: [
|
||||
{
|
||||
field: "submittedBy",
|
||||
kind: "owner", // The submitter owns this ticket
|
||||
target: "users",
|
||||
role: "submitter",
|
||||
},
|
||||
{
|
||||
field: "assignedTo",
|
||||
kind: "reference", // The assignee is merely linked, not the owner
|
||||
target: "users",
|
||||
role: "assignee",
|
||||
},
|
||||
],
|
||||
retention: {
|
||||
purgeSchedule: "daily",
|
||||
postDeletion: {
|
||||
action: "pseudonymize",
|
||||
duration: "P30D",
|
||||
trigger: "after-deletion",
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "body",
|
||||
type: "textarea",
|
||||
custom: {
|
||||
pii: {
|
||||
category: "user-generated-content",
|
||||
purpose: ["service-delivery"],
|
||||
exportable: true,
|
||||
restrictable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "submittedBy",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "assignedTo",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### What the DSR cascade does with this collection
|
||||
|
||||
**Art. 15 export** for user `alice`:
|
||||
|
||||
- `submittedBy === alice` → ticket rows appear in `data["support-tickets"].asSelf` (filtered to exportable PII fields: `body`).
|
||||
- `assignedTo === alice` → ticket row IDs + link coordinates appear in `data["support-tickets"].asReference`.
|
||||
|
||||
**Art. 17 soft delete** for user `alice`:
|
||||
|
||||
- Rows where `submittedBy === alice` → `body` field NULLed (pseudonymized, per `postDeletion.action = "pseudonymize"`).
|
||||
- Rows where `assignedTo === alice` → `assignedTo` field NULLed; row is otherwise untouched.
|
||||
|
||||
**Art. 17 cascade-hard delete** for user `alice` (admin only):
|
||||
|
||||
- Rows where `submittedBy === alice` and `postDeletion.action` resolves to `"hard-delete"` → rows deleted entirely.
|
||||
- Rows where `assignedTo === alice` → `assignedTo` field NULLed (reference rows are never hard-deleted — they belong to the submitter).
|
||||
|
||||
---
|
||||
|
||||
## Collections with a single subject
|
||||
|
||||
For collections owned by a single subject (e.g., user profile rows, blog posts), a single `SubjectLink` suffices:
|
||||
|
||||
```ts
|
||||
custom: {
|
||||
subject: [
|
||||
{ field: "author", kind: "owner", target: "users" },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Users collection (`kind: "self"`)
|
||||
|
||||
The auth collection itself uses `kind: "self"` to mark the primary subject identifier field:
|
||||
|
||||
```ts
|
||||
// This is scaffolded automatically when `auth: true` is set on the collection.
|
||||
custom: {
|
||||
subject: [
|
||||
{ field: "id", kind: "self", target: "users" },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
The DSR cascade treats `kind: "self"` rows as the subject's account record — deletion here is always gated behind the strictest policy.
|
||||
|
||||
---
|
||||
|
||||
## Regenerating the data map
|
||||
|
||||
After adding or changing `custom.subject` declarations, regenerate the compliance data map:
|
||||
|
||||
```bash
|
||||
pnpm compliance:data-map
|
||||
```
|
||||
|
||||
The output at `compliance/data-map.yml` shows every collection + its subject links + its PII fields. The pre-commit hook and CI gate run `compliance:data-map --check` to detect uncommitted drift.
|
||||
|
||||
---
|
||||
|
||||
## Cross-references
|
||||
|
||||
- DSR procedures and deletion semantics: `docs/guides/dsr.md`
|
||||
- PII field tagging (`custom.pii`): `docs/compliance/README.md`
|
||||
- Retention policy (`custom.retention`): `docs/compliance/retention-policy.example.yml`
|
||||
- Glossary: `SubjectLink`, `DeletionCertificate` in `docs/glossary.md`
|
||||
78
docs/compliance/templates/backup-policy.template.md
Normal file
78
docs/compliance/templates/backup-policy.template.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
status: template
|
||||
playbook-section: 30
|
||||
title: "Backup & Data Retention Policy"
|
||||
last-reviewed: "[FILL IN: YYYY-MM-DD]"
|
||||
---
|
||||
|
||||
# Backup & Data Retention Policy
|
||||
|
||||
> **Template status** — fill every `[FILL IN: …]` marker before use.
|
||||
|
||||
> **Not code-enforced** — this policy describes operational and organisational controls that are implemented outside the application codebase (infrastructure, vendor configuration, runbooks). The template authors intentionally do not ship backup or retention logic in the template; the consumer supplies those controls.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose & Scope
|
||||
|
||||
This policy defines how `[FILL IN: organisation name]` backs up, retains, and securely disposes of personal data and system data held in `[FILL IN: describe systems in scope — e.g., PostgreSQL database, object storage, CMS content]`.
|
||||
|
||||
**Owner:** `[FILL IN: role — e.g., Head of Engineering / DPO]`
|
||||
|
||||
---
|
||||
|
||||
## 2. Backup Schedule
|
||||
|
||||
| Data store | Backup frequency | Retention period | Storage location |
|
||||
| --------------------------------- | ------------------------- | ---------------------------- | -------------------------------------- |
|
||||
| `[FILL IN: primary database]` | `[FILL IN: e.g., daily]` | `[FILL IN: e.g., 30 days]` | `[FILL IN: e.g., encrypted S3 bucket]` |
|
||||
| `[FILL IN: CMS media / uploads]` | `[FILL IN: e.g., daily]` | `[FILL IN: e.g., 90 days]` | `[FILL IN: e.g., object storage]` |
|
||||
| `[FILL IN: log data]` | `[FILL IN: e.g., weekly]` | `[FILL IN: e.g., 12 months]` | `[FILL IN: e.g., SIEM / log archive]` |
|
||||
| `[FILL IN: any other data store]` | `[FILL IN:]` | `[FILL IN:]` | `[FILL IN:]` |
|
||||
|
||||
---
|
||||
|
||||
## 3. Encryption & Access
|
||||
|
||||
- All backups are encrypted at rest using `[FILL IN: algorithm / key service — e.g., AES-256 via AWS KMS]`.
|
||||
- Backup encryption keys are managed by `[FILL IN: key custodian / key management service]`.
|
||||
- Access to backup storage is restricted to `[FILL IN: roles — e.g., infrastructure team, on-call engineers]` and controlled via `[FILL IN: IAM policy / vault policy]`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Restore Procedure
|
||||
|
||||
1. `[FILL IN: describe how a restore request is initiated — e.g., ticket to #infra-ops]`
|
||||
2. `[FILL IN: describe authentication / authorisation required before restore begins]`
|
||||
3. `[FILL IN: describe restore steps for each data store]`
|
||||
4. Verify data integrity after restore: `[FILL IN: checksum / row-count / smoke-test procedure]`
|
||||
5. Log the restore event in `[FILL IN: audit log / incident tracker]`.
|
||||
|
||||
**Recovery Time Objective (RTO):** `[FILL IN: e.g., 4 hours]`
|
||||
|
||||
**Recovery Point Objective (RPO):** `[FILL IN: e.g., 24 hours]`
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Retention & Disposal
|
||||
|
||||
| Data category | Retention period | Disposal method |
|
||||
| ----------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------- |
|
||||
| `[FILL IN: user account data]` | `[FILL IN: e.g., account lifetime + 30 days post-erasure request]` | `[FILL IN: cryptographic erasure / secure delete]` |
|
||||
| `[FILL IN: audit log entries]` | `[FILL IN: e.g., 7 years]` | `[FILL IN: automated purge job]` |
|
||||
| `[FILL IN: application logs]` | `[FILL IN: e.g., 12 months]` | `[FILL IN: log rotation / SIEM TTL]` |
|
||||
| `[FILL IN: marketing contact data]` | `[FILL IN:]` | `[FILL IN:]` |
|
||||
|
||||
Disposal is triggered by `[FILL IN: describe the trigger — e.g., automated retention job, manual review cycle]` and verified by `[FILL IN: describe the verification step]`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing
|
||||
|
||||
Backup restores are tested `[FILL IN: frequency — e.g., quarterly]` by `[FILL IN: role]`. Results are recorded in `[FILL IN: location — e.g., the infra runbook wiki]`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Review Cycle
|
||||
|
||||
This policy is reviewed `[FILL IN: frequency — e.g., annually]` or after any material change to the backup infrastructure. The next scheduled review is `[FILL IN: YYYY-MM-DD]`.
|
||||
86
docs/compliance/templates/device-policy.template.md
Normal file
86
docs/compliance/templates/device-policy.template.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
status: template
|
||||
playbook-section: 50
|
||||
title: "Acceptable Use & Device Policy"
|
||||
last-reviewed: "[FILL IN: YYYY-MM-DD]"
|
||||
---
|
||||
|
||||
# Acceptable Use & Device Policy
|
||||
|
||||
> **Template status** — fill every `[FILL IN: …]` marker before use.
|
||||
|
||||
> **Not code-enforced** — device management, endpoint security, and acceptable-use controls are implemented outside the application codebase (MDM, EDR, organisational policy). This template documents those controls; the consumer configures and enforces them at the infrastructure and HR level.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose & Scope
|
||||
|
||||
This policy defines the acceptable use of devices and systems for all personnel — employees, contractors, and third-party service providers — who access `[FILL IN: organisation name]`'s systems, data, or networks.
|
||||
|
||||
**Owner:** `[FILL IN: role — e.g., CISO / Head of Engineering]`
|
||||
|
||||
---
|
||||
|
||||
## 2. Covered Devices
|
||||
|
||||
| Device type | Management requirement |
|
||||
| -------------------------------------- | -------------------------------------- |
|
||||
| Company-issued laptops / desktops | `[FILL IN: MDM solution]` |
|
||||
| Personal devices (BYOD) — if permitted | `[FILL IN: MDM profile / prohibition]` |
|
||||
| Mobile phones (company-issued) | `[FILL IN:]` |
|
||||
| Personal mobile devices (BYOD) | `[FILL IN:]` |
|
||||
|
||||
BYOD is `[FILL IN: permitted / not permitted]`. If permitted: `[FILL IN: describe BYOD enrolment requirements]`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Required Endpoint Controls
|
||||
|
||||
All devices accessing production systems or personal data MUST have:
|
||||
|
||||
- [ ] Full-disk encryption enabled: `[FILL IN: e.g., FileVault / BitLocker / dm-crypt]`
|
||||
- [ ] Endpoint protection (antivirus / EDR): `[FILL IN: product name]`
|
||||
- [ ] Automatic OS and software updates enabled
|
||||
- [ ] Screen lock after `[FILL IN: e.g., 5 minutes]` of inactivity
|
||||
- [ ] Strong device passcode / PIN (minimum `[FILL IN: e.g., 8 characters]`)
|
||||
- [ ] Remote-wipe capability enrolled: `[FILL IN: MDM / tool]`
|
||||
- [ ] VPN required for access to `[FILL IN: e.g., production database, staging environment]`
|
||||
|
||||
---
|
||||
|
||||
## 4. Acceptable Use
|
||||
|
||||
### 4.1 Permitted uses
|
||||
|
||||
- Business activities of `[FILL IN: organisation name]`
|
||||
- Reasonable personal use that does not interfere with professional responsibilities
|
||||
- `[FILL IN: any additional permitted uses]`
|
||||
|
||||
### 4.2 Prohibited uses
|
||||
|
||||
- Accessing, storing, or processing personal data outside approved systems
|
||||
- Installing unapproved software on managed devices: `[FILL IN: software approval process]`
|
||||
- Sharing credentials or device access with unauthorised parties
|
||||
- Using personal cloud storage for business data: `[FILL IN: exceptions, if any]`
|
||||
- `[FILL IN: any organisation-specific prohibitions]`
|
||||
|
||||
---
|
||||
|
||||
## 5. Lost or Stolen Devices
|
||||
|
||||
1. Report immediately to `[FILL IN: contact — e.g., IT helpdesk / security@org]`.
|
||||
2. Remote wipe is initiated within `[FILL IN: e.g., 2 hours]` of report.
|
||||
3. The incident is assessed for personal-data impact and escalated to the incident runbook if PII was accessible (see [`incident-runbook.template.md`](./incident-runbook.template.md)).
|
||||
4. Document in `[FILL IN: incident tracker]`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Device Return & Offboarding
|
||||
|
||||
On termination or role change, devices are returned within `[FILL IN: e.g., 1 business day]` and wiped via `[FILL IN: wipe procedure]`. See [`offboarding.template.md`](./offboarding.template.md) for the full offboarding checklist.
|
||||
|
||||
---
|
||||
|
||||
## 7. Review Cycle
|
||||
|
||||
This policy is reviewed `[FILL IN: frequency — e.g., annually]`. The next scheduled review is `[FILL IN: YYYY-MM-DD]`.
|
||||
312
docs/compliance/templates/dsr-procedure.template.md
Normal file
312
docs/compliance/templates/dsr-procedure.template.md
Normal file
@@ -0,0 +1,312 @@
|
||||
---
|
||||
status: template
|
||||
playbook-section: 10
|
||||
title: "Data Subject Rights (DSR) Fulfilment Procedure"
|
||||
gdpr-articles: [Art. 15, Art. 16, Art. 17, Art. 18, Art. 20]
|
||||
last-reviewed: "[FILL IN: YYYY-MM-DD]"
|
||||
---
|
||||
|
||||
# Data Subject Rights (DSR) Fulfilment Procedure
|
||||
|
||||
> **Template status** — fill every `[FILL IN: …]` marker before use.
|
||||
> Cross-references below point to shipped interfaces, endpoints, and ADRs; do not change them.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose & Scope
|
||||
|
||||
This procedure governs how `[FILL IN: organisation name]` receives, validates, fulfils, and records requests from data subjects exercising rights under GDPR Articles 15–20.
|
||||
|
||||
**Covered rights:**
|
||||
|
||||
| GDPR Article | Right | Shipped interface |
|
||||
| ------------ | --------------------------------- | ----------------------------------------- |
|
||||
| Art. 15 + 20 | Access + Portability | `IDataExport` (`dsr.export`) |
|
||||
| Art. 16 | Rectification | `IDataRectify` (`dsr.rectify`) |
|
||||
| Art. 17 | Erasure ("right to be forgotten") | `IDataDelete` (`dsr.delete`) |
|
||||
| Art. 18 | Restriction of processing | `IProcessingRestriction` (`dsr.restrict`) |
|
||||
|
||||
See [`docs/guides/dsr.md`](../../guides/dsr.md) for the engineering cookbook and [`docs/decisions/adr-025-eu-compliance-baseline.md`](../../decisions/adr-025-eu-compliance-baseline.md) § Epic B for the design rationale.
|
||||
|
||||
---
|
||||
|
||||
## 2. Roles & Contacts
|
||||
|
||||
| Role | Name | Contact |
|
||||
| -------------------------------- | ------------------------------ | ------------------ |
|
||||
| Data Protection Officer | [FILL IN: name / DPO provider] | [FILL IN: contact] |
|
||||
| DSR Coordinator | [FILL IN: name] | [FILL IN: contact] |
|
||||
| Engineering contact (fulfilment) | [FILL IN: name] | [FILL IN: contact] |
|
||||
|
||||
**Intake channel:** `[FILL IN: e.g. privacy@example.com / online form URL]`
|
||||
|
||||
---
|
||||
|
||||
## 3. Statutory Deadlines
|
||||
|
||||
| Stage | Deadline | Extension |
|
||||
| ------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| Acknowledge receipt | `[FILL IN: e.g. 5 business days]` | — |
|
||||
| Fulfil or refuse | **1 calendar month** from receipt (GDPR Art. 12(3)) | Up to 2 further months for complex/numerous requests; notify subject within the first month |
|
||||
| Notify subject of refusal | 1 month | — |
|
||||
|
||||
Deadline clock starts when the organisation receives the request — not when identity is verified.
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 1 — Receipt & Intake
|
||||
|
||||
### 4.1 Intake channels
|
||||
|
||||
Accept DSR requests via:
|
||||
|
||||
- `[FILL IN: primary channel, e.g. privacy form at https://example.com/privacy/request]`
|
||||
- `[FILL IN: secondary channel, e.g. email to privacy@example.com]`
|
||||
- Any written format (GDPR does not mandate a specific form)
|
||||
|
||||
### 4.2 Logging the request
|
||||
|
||||
Log each incoming request immediately in `[FILL IN: DSR register / issue tracker]` with:
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | --------------------------- |
|
||||
| Reference ID | `DSR-YYYY-NNN` (sequential) |
|
||||
| Right requested | Art. 15 / 16 / 17 / 18 / 20 |
|
||||
| Channel | email / form / letter |
|
||||
| Received at | ISO 8601 timestamp |
|
||||
| Deadline | received + 1 month |
|
||||
| Status | `pending-verification` |
|
||||
|
||||
### 4.3 Acknowledgement
|
||||
|
||||
Send acknowledgement to the requester within `[FILL IN: e.g. 5 business days]` confirming:
|
||||
|
||||
- Receipt of the request
|
||||
- Reference ID
|
||||
- Identity verification requirement (§ 5)
|
||||
- Statutory deadline
|
||||
|
||||
Template: `[FILL IN: path to acknowledgement email template]`
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 2 — Identity Validation
|
||||
|
||||
### 5.1 Verification requirement
|
||||
|
||||
GDPR Art. 12(6) permits identity verification when there is reasonable doubt. Always verify for Art. 17 (erasure) and Art. 18 (restriction). For Art. 15 (access) of clearly authenticated users, verification may be satisfied by existing session.
|
||||
|
||||
### 5.2 Verification methods
|
||||
|
||||
| Method | Acceptable for |
|
||||
| ---------------------------------------------- | ---------------------------------- |
|
||||
| Authenticated session in the app | Art. 15, Art. 16 (low-risk fields) |
|
||||
| Email confirmation to registered address | All rights |
|
||||
| `[FILL IN: government ID / identity provider]` | Art. 17 cascade-hard, Art. 18 |
|
||||
|
||||
### 5.3 Rejected or suspicious requests
|
||||
|
||||
If identity cannot be verified after `[FILL IN: e.g. 14 days]`:
|
||||
|
||||
1. Close the request with status `identity-unverified`.
|
||||
2. Notify the requester in writing of the outcome.
|
||||
3. Log closure in the DSR register.
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 3 — Fulfilment
|
||||
|
||||
### 6.1 Identify the subject record
|
||||
|
||||
Map the verified identity to the system `subjectId` (Payload `users.id` or equivalent auth collection primary key). Cross-reference `compliance/data-map.yml` (generated by `pnpm compliance:data-map`) to enumerate which collections hold personal data for this subject.
|
||||
|
||||
```bash
|
||||
# Regenerate data map to ensure it reflects current schema
|
||||
pnpm compliance:data-map
|
||||
```
|
||||
|
||||
Review `compliance/data-map.yml` fields tagged `exportable: true` (Art. 15/20) and `restrictable: true` (Art. 18).
|
||||
|
||||
### 6.2 Art. 15 + 20 — Access and Portability
|
||||
|
||||
**Interface:** `IDataExport.exportSubjectData(subjectId, format)`
|
||||
**tRPC procedure:** `dsr.export` (query — no mutation)
|
||||
**GDPR deadline:** respond within 1 month; provide data without charge for first copy.
|
||||
|
||||
Invoke via the admin tRPC client or the REST endpoint wired in `apps/web-next`:
|
||||
|
||||
```
|
||||
GET /api/gdpr/export?subjectId=<id>&format=json
|
||||
GET /api/gdpr/export?subjectId=<id>&format=json-ld # machine-readable JSON-LD
|
||||
```
|
||||
|
||||
The response is a `UserDataBundle`:
|
||||
|
||||
- `data` — keyed by Payload collection slug; each bucket contains `asSelf` (rows the subject owns) and `asReference` (rows merely referencing the subject)
|
||||
- `auditLog` — the subject's audit trail (optional; include for transparency)
|
||||
- `exportedAt` — ISO 8601 export timestamp for the certificate of fulfilment
|
||||
|
||||
**Audit entry emitted:** `EXPORT` action, `resource.type: "data-subject"`, `resource.id: subjectId`.
|
||||
|
||||
Deliver the export to the subject via `[FILL IN: secure delivery method, e.g. encrypted download link / secure email]`.
|
||||
|
||||
### 6.3 Art. 16 — Rectification
|
||||
|
||||
**Interface:** `IDataRectify.updateSubjectField(subjectId, collection, field, value)`
|
||||
**tRPC procedure:** `dsr.rectify` (mutation)
|
||||
**Scope:** only fields tagged `custom.pii` in the Payload collection config (i.e. fields indexed in `compliance/data-map.yml`).
|
||||
|
||||
```
|
||||
POST /api/gdpr/rectify
|
||||
Body: { subjectId, collection, field, value }
|
||||
```
|
||||
|
||||
Steps:
|
||||
|
||||
1. Confirm the field is `custom.pii`-tagged in `compliance/data-map.yml`.
|
||||
2. Confirm the new value passes Payload field validation.
|
||||
3. Invoke the procedure; the implementation emits a `RESTRICT` audit entry with `reason: "art-16-request"`.
|
||||
4. Notify the subject that rectification is complete and, if data was shared with third parties, notify them per Art. 19.
|
||||
|
||||
Third-party notification log: `[FILL IN: log location]`
|
||||
|
||||
### 6.4 Art. 17 — Erasure
|
||||
|
||||
**Interface:** `IDataDelete.deleteSubjectData(subjectId, mode)`
|
||||
**tRPC procedure:** `dsr.delete` (mutation — requires authenticated admin session for `cascade-hard`)
|
||||
**Returned:** `DeletionCertificate` — retain this as evidence of fulfilment.
|
||||
|
||||
**Mode selection:**
|
||||
|
||||
| Mode | Effect | Who can invoke |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- |
|
||||
| `"soft"` | Redacts PII fields in owned rows; NULLs reference fields; row structure preserved | Standard authenticated user or DSR coordinator |
|
||||
| `"cascade-hard"` | Hard-deletes owned rows where `postDeletion.action === "hard-delete"` per retention policy; NULLs reference fields | Admin role required |
|
||||
|
||||
```
|
||||
POST /api/gdpr/delete
|
||||
Body: { subjectId, mode: "soft" | "cascade-hard" }
|
||||
```
|
||||
|
||||
**Exemptions** (GDPR Art. 17(3)) — erasure must be refused if data is required for:
|
||||
|
||||
- Legal obligation compliance (document refusal in the DSR register)
|
||||
- Establishment, exercise, or defence of legal claims
|
||||
|
||||
**Audit trail pseudonymization:** after deletion, invoke `IAuditLog.eraseSubject(actorId, "pseudonymize")` via the admin tRPC to replace the subject identifier in the audit log with `erased-{hash[0:16]}` (sha256-salted pseudonym per ADR-018). This preserves the event record for regulatory purposes while removing the identifier.
|
||||
|
||||
**Retention-policy overrides:** check `compliance/retention-policy.yml` for `postDeletion` rules — some collections may have a grace period (`postDeletion.duration: P30D`) before hard deletion executes.
|
||||
|
||||
```bash
|
||||
# View current retention policy
|
||||
cat compliance/retention-policy.yml
|
||||
|
||||
# Regenerate from Payload field tags
|
||||
pnpm compliance:retention-policy
|
||||
```
|
||||
|
||||
**DeletionCertificate fields to retain:**
|
||||
|
||||
- `subjectId` (or pseudonymized form)
|
||||
- `mode`, `timestamp`, `reason: "art-17-request"`
|
||||
- `affected[]` — collections and rows modified
|
||||
- `auditEntryId` — links to the audit log
|
||||
|
||||
### 6.5 Art. 18 — Restriction of Processing
|
||||
|
||||
**Interface:** `IProcessingRestriction.setRestriction(subjectId, granted: true)`
|
||||
**tRPC procedure:** `dsr.restrict` (mutation)
|
||||
|
||||
```
|
||||
POST /api/gdpr/restrict
|
||||
Body: { subjectId, granted: true }
|
||||
```
|
||||
|
||||
The implementation sets `processingRestrictedAt` on the user record. Downstream use cases check `isRestricted(subjectId)` before processing personal data. The audit channel records a `RESTRICT` audit entry on every state change.
|
||||
|
||||
**Grounds for restriction (Art. 18(1)):**
|
||||
|
||||
- Accuracy of data is contested (pending Art. 16 rectification)
|
||||
- Processing is unlawful but erasure is opposed by the subject
|
||||
- Organisation no longer needs the data but subject requires it for legal claims
|
||||
- Subject has objected (pending verification of legitimate grounds)
|
||||
|
||||
Notify the subject when restriction is lifted: invoke `dsr.restrict` with `granted: false` and send written notice per Art. 18(3).
|
||||
|
||||
**Audit entry emitted:** `RESTRICT` (on restriction) / `UNRESTRICT` (on lift).
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase 4 — Recording & Closure
|
||||
|
||||
### 7.1 DSR register update
|
||||
|
||||
Update the DSR register entry (§ 4.2) with:
|
||||
|
||||
| Field | Value |
|
||||
| -------------- | ------------------------------------------------- |
|
||||
| Fulfilled at | ISO 8601 timestamp |
|
||||
| Outcome | `fulfilled` / `refused` / `partially-fulfilled` |
|
||||
| Evidence | DeletionCertificate ID or export reference |
|
||||
| Audit entry ID | From `AuditEntry.correlationId` or `auditEntryId` |
|
||||
| Status | `closed` |
|
||||
|
||||
### 7.2 Subject notification
|
||||
|
||||
Notify the data subject of the outcome within the Art. 12(3) deadline:
|
||||
|
||||
- Fulfilment: summary of actions taken, delivery method for exports.
|
||||
- Refusal: grounds for refusal (Art. 12(4)), right to lodge complaint with SA, right to seek judicial remedy.
|
||||
|
||||
Template: `[FILL IN: path to outcome notification template]`
|
||||
|
||||
### 7.3 Third-party notification log (Art. 19)
|
||||
|
||||
If the corrected, erased, or restricted data was previously shared with third parties (sub-processors listed in `compliance/sub-processors.yml`), notify each processor of the subject's request. Record each notification in `[FILL IN: third-party notification log]`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Consent-Related DSR Interactions
|
||||
|
||||
Consent grant and withdrawal are distinct from Art. 17 erasure but often accompany DSR requests. The consent channel ([`docs/guides/consent.md`](../../guides/consent.md)) records:
|
||||
|
||||
| Audit action | Trigger |
|
||||
| ------------------ | -------------------------------------------------------- |
|
||||
| `CONSENT_GRANT` | Subject grants consent for a category (e.g. `marketing`) |
|
||||
| `CONSENT_WITHDRAW` | Subject withdraws consent |
|
||||
| `RESTRICT` | Art. 18 restriction set |
|
||||
| `UNRESTRICT` | Art. 18 restriction lifted |
|
||||
|
||||
If a DSR request also includes consent withdrawal, invoke `IConsent.withdraw(subjectId, categories)` in addition to the relevant DSR procedure. Consent withdrawal does not automatically trigger erasure — only an explicit Art. 17 request does.
|
||||
|
||||
---
|
||||
|
||||
## 9. Appendix — Command Reference
|
||||
|
||||
```bash
|
||||
# Regenerate PII data map (enumerate affected collections and fields)
|
||||
pnpm compliance:data-map
|
||||
|
||||
# Regenerate retention policy (check postDeletion rules before Art. 17)
|
||||
pnpm compliance:retention-policy
|
||||
|
||||
# Regenerate sub-processor list (for Art. 19 third-party notification)
|
||||
pnpm compliance:sub-processors
|
||||
|
||||
# Regenerate all compliance artifacts in one pass
|
||||
pnpm compliance:emit-all
|
||||
|
||||
# Run fallow audit before closing a DSR-related PR
|
||||
pnpm fallow:audit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Document Control
|
||||
|
||||
| Field | Value |
|
||||
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Owner | [FILL IN: DPO or DSR Coordinator] |
|
||||
| Review cycle | [FILL IN: e.g. annual or on schema change] |
|
||||
| Next review date | [FILL IN: YYYY-MM-DD] |
|
||||
| Cross-references | [`docs/decisions/adr-025-eu-compliance-baseline.md`](../../decisions/adr-025-eu-compliance-baseline.md), [`docs/decisions/adr-018-audit-and-compliance.md`](../../decisions/adr-018-audit-and-compliance.md), [`docs/guides/dsr.md`](../../guides/dsr.md), [`docs/guides/audit-and-compliance.md`](../../guides/audit-and-compliance.md), [`docs/guides/consent.md`](../../guides/consent.md), [`../data-map.example.yml`](../data-map.example.yml) |
|
||||
264
docs/compliance/templates/incident-runbook.template.md
Normal file
264
docs/compliance/templates/incident-runbook.template.md
Normal file
@@ -0,0 +1,264 @@
|
||||
---
|
||||
status: template
|
||||
playbook-section: 20
|
||||
title: "Security Incident & Personal Data Breach Runbook"
|
||||
gdpr-articles: [Art. 33, Art. 34]
|
||||
last-reviewed: "[FILL IN: YYYY-MM-DD]"
|
||||
---
|
||||
|
||||
# Security Incident & Personal Data Breach Runbook
|
||||
|
||||
> **Template status** — fill every `[FILL IN: …]` marker before use.
|
||||
> Cross-references below point to shipped code and ADRs; do not change them.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose & Scope
|
||||
|
||||
This runbook governs the detection, triage, containment, notification, and post-mortem of security incidents and personal data breaches affecting `[FILL IN: product / organisation name]`.
|
||||
|
||||
**In scope:** any event that may have compromised the confidentiality, integrity, or availability of personal data processed by this system, including unauthorized access, exfiltration, accidental exposure, and ransomware.
|
||||
|
||||
**Out of scope:** non-PII service disruptions (handle via standard on-call runbook). Escalate to this runbook when PII impact cannot be ruled out.
|
||||
|
||||
---
|
||||
|
||||
## 2. Severity Classification
|
||||
|
||||
| Level | Definition | Example |
|
||||
| ----------------- | ------------------------------------------------- | ---------------------------------------------------- |
|
||||
| **P0 — Critical** | Confirmed exfiltration or mass exposure of PII | Database dump on public host |
|
||||
| **P1 — High** | Suspected breach; blast radius unknown | Anomalous `EXPORT` audit entries for non-admin actor |
|
||||
| **P2 — Medium** | Contained misconfiguration; no confirmed PII leak | Stale presigned URL exposed to wrong tenant |
|
||||
| **P3 — Low** | Near-miss; no PII accessed | Failed injection attempt blocked by rate-limit |
|
||||
|
||||
---
|
||||
|
||||
## 3. Roles & Contacts
|
||||
|
||||
| Role | Name | Contact |
|
||||
| ----------------------- | ------------------------------ | ------------------ |
|
||||
| Incident Commander | [FILL IN: name] | [FILL IN: contact] |
|
||||
| Data Protection Officer | [FILL IN: name / DPO provider] | [FILL IN: contact] |
|
||||
| Legal / Counsel | [FILL IN: name] | [FILL IN: contact] |
|
||||
| Engineering Lead | [FILL IN: name] | [FILL IN: contact] |
|
||||
| Communications Lead | [FILL IN: name] | [FILL IN: contact] |
|
||||
|
||||
**Supervisory Authority (SA):**
|
||||
|
||||
- Authority name: `[FILL IN: e.g. ICO / CNIL / BfDI]`
|
||||
- Online notification portal: `[FILL IN: URL]`
|
||||
- Emergency phone: `[FILL IN: number]`
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 1 — Detection
|
||||
|
||||
### 4.1 Automated signals
|
||||
|
||||
This template ships two automated detection surfaces. Check both when an alert fires.
|
||||
|
||||
#### Sentry error alerting (ADR-014)
|
||||
|
||||
Every unhandled exception in `apps/web-next`, `apps/cms`, and `apps/web-tanstack` is captured via `ILogger.captureException` and routed to Sentry ([`docs/decisions/adr-014-instrumentation-sentry.md`](../../decisions/adr-014-instrumentation-sentry.md)). Configure alert rules in Sentry for:
|
||||
|
||||
- Spike in `4xx` / `5xx` on auth or DSR routes
|
||||
- New issue fingerprints in production environment
|
||||
- Volume anomaly on `[FILL IN: Sentry project slug]`
|
||||
|
||||
**Verify DSNs are configured:**
|
||||
|
||||
```bash
|
||||
# Each app has its own Sentry project
|
||||
echo $WEB_NEXT_SENTRY_DSN
|
||||
echo $CMS_SENTRY_DSN
|
||||
echo $WEB_TANSTACK_SENTRY_DSN # if TanStack Start app is in use
|
||||
```
|
||||
|
||||
All three must be non-empty in production. A missing DSN silently disables capture for that app.
|
||||
|
||||
#### Audit log anomalies (ADR-018)
|
||||
|
||||
The audit channel ([`docs/decisions/adr-018-audit-and-compliance.md`](../../decisions/adr-018-audit-and-compliance.md)) records every `AuditAction` emitted by feature use cases via `AuditLogProtocol.record()`. Actions relevant to breach detection:
|
||||
|
||||
| `AuditAction` | Breach signal |
|
||||
| ------------------- | ----------------------------------------------------- |
|
||||
| `EXPORT` | Mass export by non-admin actor or from unexpected IP |
|
||||
| `VIEW` | High-frequency reads on a single `actorId` |
|
||||
| `PERMISSION_CHANGE` | Privilege escalation outside change-management window |
|
||||
| `DELETE` | Bulk delete with no corresponding DSR request |
|
||||
|
||||
Query the Payload audit logs collection or the aggregated cold archive (`[FILL IN: log aggregation URL, e.g. Vector / Fluent Bit sink]`) for anomalies. Correlate with OTel `correlationId` (trace ID auto-populated by `TraceIdEnrichingAuditLog`).
|
||||
|
||||
#### Rate-limit exhaustion (ADR-025 § Epic C)
|
||||
|
||||
The rate-limit primitive (`IRateLimit` in `core-shared/rate-limit`, see [`docs/guides/rate-limiting.md`](../../guides/rate-limiting.md)) emits `{ allowed: false, remaining: 0, resetAt }` when a budget is exhausted. Repeated exhaustion on auth or DSR endpoints may indicate credential-stuffing or automated exfiltration.
|
||||
|
||||
Check budget tables declared in feature manifests (`rateLimit: { window, budget }`) against observed traffic in `[FILL IN: metrics dashboard URL]`.
|
||||
|
||||
#### Security header violations
|
||||
|
||||
HSTS, `X-Frame-Options`, and CSP headers are emitted by the security-headers middleware ([`docs/guides/security-headers.md`](../../guides/security-headers.md)). CSP violation reports (if `report-uri` is configured) surface attempted XSS. Check `[FILL IN: CSP report endpoint / dashboard]`.
|
||||
|
||||
### 4.2 Manual discovery
|
||||
|
||||
If detection was by human observation (e.g., responsible-disclosure report, darknet alert):
|
||||
|
||||
1. Log the reporter's contact and discovery timestamp.
|
||||
2. Do not confirm or deny breach details to the reporter until DPO is engaged.
|
||||
3. Proceed to Phase 2.
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 2 — Triage
|
||||
|
||||
**Target time: within [FILL IN: e.g. 2 hours] of detection.**
|
||||
|
||||
### 5.1 Triage checklist
|
||||
|
||||
- [ ] Page Incident Commander and DPO.
|
||||
- [ ] Open incident channel: `[FILL IN: e.g. #incident-YYYYMMDD in Slack / Teams]`.
|
||||
- [ ] Document initial facts: detected at, detected by, affected system(s).
|
||||
- [ ] Classify severity (§ 2).
|
||||
- [ ] Determine if personal data is involved (see § 5.2).
|
||||
- [ ] Initiate 72-hour DPA notification clock if P0 or P1 (§ 7.1).
|
||||
|
||||
### 5.2 PII impact assessment
|
||||
|
||||
Use the generated data map (`compliance/data-map.yml`, generated by `pnpm compliance:data-map` from Payload `custom.pii` field tags) to enumerate which collections and fields may be affected. Cross-reference with the scope of the incident (endpoint, DB table, S3 bucket, etc.).
|
||||
|
||||
Key questions:
|
||||
|
||||
- Which `piiCategory` values are in the affected scope? (e.g. `contact-email`, `identification-name`)
|
||||
- Are any `restrictable: true` subjects affected (i.e. users who invoked Art. 18)?
|
||||
- Was the `auditLogs` collection itself compromised? (Audit trail integrity is required for Art. 33 notification.)
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 3 — Containment
|
||||
|
||||
**Target time: within [FILL IN: e.g. 4 hours] of confirmation for P0/P1.**
|
||||
|
||||
### 6.1 Immediate actions
|
||||
|
||||
- [ ] Revoke or rotate compromised credentials (`[FILL IN: credential management system]`).
|
||||
- [ ] Block actor at network/WAF level if attack is ongoing (`[FILL IN: WAF / CDN control panel URL]`).
|
||||
- [ ] If a specific `actorId` is confirmed malicious: suspend account in Payload admin (`apps/cms`, port 3001).
|
||||
- [ ] If a tRPC or REST route is the attack vector: deploy an emergency rate-limit tightening or disable the route (`[FILL IN: deployment method]`).
|
||||
- [ ] Preserve evidence: take read-only snapshots of affected tables, CloudWatch / log streams, and network flow logs before any cleanup.
|
||||
|
||||
### 6.2 Audit trail preservation
|
||||
|
||||
The audit log collection is append-only (`update: () => false` Payload access rule per ADR-018). Do not attempt to delete or modify audit entries. If the audit collection itself is a target, freeze DB-level access and take a snapshot:
|
||||
|
||||
```bash
|
||||
# [FILL IN: adapt to your database provider]
|
||||
pg_dump --schema-only --table=audit_logs $DATABASE_URL > audit_logs_schema_snapshot.sql
|
||||
pg_dump --data-only --table=audit_logs $DATABASE_URL > audit_logs_data_snapshot.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase 4 — Notification
|
||||
|
||||
### 7.1 Regulatory notification deadlines
|
||||
|
||||
| Obligation | Deadline | Trigger |
|
||||
| -------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------- |
|
||||
| GDPR Art. 33 — notify Supervisory Authority | **72 hours** from becoming aware | Any breach involving personal data unless unlikely to risk rights/freedoms |
|
||||
| `[FILL IN: national DPA requirement]` | **[FILL IN: e.g. 24 hours]** | `[FILL IN: condition]` |
|
||||
| GDPR Art. 34 — notify affected data subjects | Without undue delay | High risk to rights and freedoms |
|
||||
|
||||
**Clock start:** the moment the organisation "becomes aware" — typically when the Incident Commander or DPO confirms the incident in § 5.1, not when it was first detected.
|
||||
|
||||
### 7.2 Supervisory Authority notification (Art. 33)
|
||||
|
||||
Prepare the notification using the SA's online portal (`[FILL IN: URL]`). Required fields per Art. 33(3):
|
||||
|
||||
- Nature of the breach (categories and approximate number of records / data subjects)
|
||||
- Name and contact details of DPO: `[FILL IN: DPO name, email, phone]`
|
||||
- Likely consequences of the breach
|
||||
- Measures taken or proposed
|
||||
|
||||
Attach:
|
||||
|
||||
- Incident timeline (from detection to containment)
|
||||
- Affected `piiCategories` from `compliance/data-map.yml`
|
||||
- Audit log extract covering the incident window (redact unrelated subjects)
|
||||
|
||||
**If full facts are not available within 72 hours:** submit an initial notification marked "partial" and follow up without delay. GDPR Art. 33(4) explicitly permits phased notification.
|
||||
|
||||
### 7.3 Data subject notification (Art. 34)
|
||||
|
||||
Threshold: notification is required when the breach is likely to result in **high risk** to the rights and freedoms of individuals (e.g. identity theft, financial loss, discrimination).
|
||||
|
||||
- Drafting: `[FILL IN: communications lead]` drafts notice in plain language.
|
||||
- Channel: `[FILL IN: e.g. in-app banner + email via transactional provider]`
|
||||
- Content: nature of breach, DPO contact, likely consequences, mitigation steps.
|
||||
- Timing: `[FILL IN: target send window, e.g. within 48 hours of Art. 33 notification]`
|
||||
|
||||
### 7.4 Internal stakeholder notification
|
||||
|
||||
| Stakeholder | When | Channel |
|
||||
| ----------------------- | ---------------------------- | ----------- |
|
||||
| Executive team | P0/P1: immediately | `[FILL IN]` |
|
||||
| Customer success | Before customer-facing comms | `[FILL IN]` |
|
||||
| Board / audit committee | Within `[FILL IN]` hours | `[FILL IN]` |
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase 5 — Post-mortem
|
||||
|
||||
**Target: within [FILL IN: e.g. 5 business days] of containment.**
|
||||
|
||||
### 8.1 Post-mortem checklist
|
||||
|
||||
- [ ] Timeline reconstruction (detection → triage → containment → notification).
|
||||
- [ ] Root cause analysis (five-whys or equivalent).
|
||||
- [ ] Blast radius: confirmed data subjects affected, `piiCategories` exposed, duration of exposure.
|
||||
- [ ] Audit log review: were anomalous `AuditAction` entries present before detection? Were `from.ipTruncated` values suspicious?
|
||||
- [ ] Control gaps identified.
|
||||
- [ ] Remediation actions with owner and deadline.
|
||||
- [ ] Lessons learned.
|
||||
|
||||
### 8.2 Remediation tracking
|
||||
|
||||
Document each action in `[FILL IN: issue tracker, e.g. Linear / GitHub Issues]` with:
|
||||
|
||||
- Linked ADR or guide (e.g. ADR-018, ADR-014)
|
||||
- Owner
|
||||
- Target date
|
||||
- Verification step (e.g. `pnpm fallow:audit` green, new test added)
|
||||
|
||||
### 8.3 Mandatory follow-up with Supervisory Authority
|
||||
|
||||
If the Art. 33 notification was submitted as "partial", file the complete notification with the SA by `[FILL IN: agreed follow-up date]`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Appendix — Command Reference
|
||||
|
||||
```bash
|
||||
# Inspect running apps and their Sentry DSN status
|
||||
echo $WEB_NEXT_SENTRY_DSN && echo $CMS_SENTRY_DSN
|
||||
|
||||
# Run fallow audit to surface dead exports / drift before post-mortem PR
|
||||
pnpm fallow:audit
|
||||
|
||||
# Regenerate data map to confirm affected PII fields
|
||||
pnpm compliance:data-map
|
||||
|
||||
# Re-emit all compliance artifacts (data-map + retention-policy + sub-processors)
|
||||
pnpm compliance:emit-all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Document Control
|
||||
|
||||
| Field | Value |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Owner | [FILL IN: DPO or Engineering Lead] |
|
||||
| Review cycle | [FILL IN: e.g. annual or after every P0/P1] |
|
||||
| Next review date | [FILL IN: YYYY-MM-DD] |
|
||||
| Cross-references | [`docs/decisions/adr-018-audit-and-compliance.md`](../../decisions/adr-018-audit-and-compliance.md), [`docs/decisions/adr-014-instrumentation-sentry.md`](../../decisions/adr-014-instrumentation-sentry.md), [`docs/decisions/adr-025-eu-compliance-baseline.md`](../../decisions/adr-025-eu-compliance-baseline.md), [`docs/guides/audit-and-compliance.md`](../../guides/audit-and-compliance.md), [`docs/guides/rate-limiting.md`](../../guides/rate-limiting.md), [`docs/guides/security-headers.md`](../../guides/security-headers.md) |
|
||||
103
docs/compliance/templates/offboarding.template.md
Normal file
103
docs/compliance/templates/offboarding.template.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
status: template
|
||||
playbook-section: 70
|
||||
title: "Staff Offboarding Checklist (Data Access & Security)"
|
||||
last-reviewed: "[FILL IN: YYYY-MM-DD]"
|
||||
---
|
||||
|
||||
# Staff Offboarding Checklist (Data Access & Security)
|
||||
|
||||
> **Template status** — fill every `[FILL IN: …]` marker before use.
|
||||
|
||||
> **Not code-enforced** — access revocation, device return, and data-handover steps are operational controls implemented outside the application codebase. The consumer is responsible for integrating this checklist into their HR and IT offboarding workflow and ensuring it is completed before the final day.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose & Scope
|
||||
|
||||
This checklist ensures that all access, devices, and personal data are securely handled when an employee, contractor, or third-party leaves `[FILL IN: organisation name]` or changes role.
|
||||
|
||||
**Owner:** `[FILL IN: role — e.g., HR / People Ops + IT]`
|
||||
|
||||
**Trigger:** Employment or engagement termination (voluntary or involuntary), role transfer requiring access scope change, contractor end-of-engagement.
|
||||
|
||||
---
|
||||
|
||||
## 2. Before Final Day — Immediate Actions (Involuntary / High-Risk Departure)
|
||||
|
||||
> Complete this section on the same day for involuntary terminations or where data-exfiltration risk is elevated.
|
||||
|
||||
| # | Task | Owner | Done |
|
||||
| --- | -------------------------------------------------------------------------------------- | ------------------------ | ---- |
|
||||
| 1 | Suspend IdP account (`[FILL IN: provider]`) — do NOT delete yet (preserve audit trail) | `[FILL IN: IT]` | ☐ |
|
||||
| 2 | Revoke active sessions / tokens for all systems | `[FILL IN: IT]` | ☐ |
|
||||
| 3 | Rotate any shared secrets the individual had access to: `[FILL IN: list]` | `[FILL IN: engineering]` | ☐ |
|
||||
| 4 | Preserve a copy of the departing individual's work output per data-retention policy | `[FILL IN: manager]` | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 3. Final Day — Access Revocation
|
||||
|
||||
| # | System / tool | Action | Confirmed by | Done |
|
||||
| --- | ----------------------------------------------------------------------------------- | ------------------------------- | ------------ | ---- |
|
||||
| 1 | `[FILL IN: e.g., GitHub org]` | Remove from org / teams | `[FILL IN:]` | ☐ |
|
||||
| 2 | `[FILL IN: e.g., Payload CMS admin]` | Delete or deactivate user | `[FILL IN:]` | ☐ |
|
||||
| 3 | `[FILL IN: e.g., cloud console / IAM]` | Revoke all policies | `[FILL IN:]` | ☐ |
|
||||
| 4 | `[FILL IN: e.g., monitoring / Sentry]` | Remove member | `[FILL IN:]` | ☐ |
|
||||
| 5 | `[FILL IN: e.g., HR / payroll system]` | Deactivate | `[FILL IN:]` | ☐ |
|
||||
| 6 | `[FILL IN: e.g., communication tools]` | Deactivate / transfer ownership | `[FILL IN:]` | ☐ |
|
||||
| 7 | `[FILL IN: any other system]` | `[FILL IN: action]` | `[FILL IN:]` | ☐ |
|
||||
| 8 | IdP account: move to suspended → delete after `[FILL IN: e.g., 30-day]` hold period | IT | `[FILL IN:]` | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 4. Device Return
|
||||
|
||||
| # | Task | Owner | Done |
|
||||
| --- | --------------------------------------------------------------------------- | -------------------- | ---- |
|
||||
| 1 | Device returned by `[FILL IN: deadline — e.g., end of final working day]` | Departing individual | ☐ |
|
||||
| 2 | Device wiped via MDM (`[FILL IN: MDM tool]`) and wipe logged | `[FILL IN: IT]` | ☐ |
|
||||
| 3 | Device re-assigned or quarantined per `[FILL IN: asset-management process]` | `[FILL IN: IT]` | ☐ |
|
||||
|
||||
For lost/stolen devices see [`device-policy.template.md`](./device-policy.template.md) § 5.
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Handover & Retention
|
||||
|
||||
| # | Task | Done |
|
||||
| --- | --------------------------------------------------------------------------------------------- | ---- |
|
||||
| 1 | Business-critical files transferred to `[FILL IN: shared location — e.g., team drive]` | ☐ |
|
||||
| 2 | Personal data on company systems assessed; deleted or anonymised per retention policy | ☐ |
|
||||
| 3 | Any personal data held in personal tools / local storage destroyed: `[FILL IN: confirmation]` | ☐ |
|
||||
| 4 | Email forwarding / out-of-office configured for `[FILL IN: duration]` | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 6. Exit Interview & Acknowledgement
|
||||
|
||||
| # | Task | Done |
|
||||
| --- | ------------------------------------------------------------------------------ | ---- |
|
||||
| 1 | Departing individual reminded of ongoing confidentiality obligations | ☐ |
|
||||
| 2 | Signed offboarding acknowledgement obtained: `[FILL IN: form name / location]` | ☐ |
|
||||
| 3 | Final payslip / equipment receipt issued | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 7. Post-Departure Review (30 days)
|
||||
|
||||
- Confirm no residual access exists: re-run access audit for `[FILL IN: critical systems]`.
|
||||
- Review audit log for anomalous activity by the account in the 30 days before departure: `[FILL IN: query / command]`.
|
||||
- If anomalies found, escalate to the incident runbook (see [`incident-runbook.template.md`](./incident-runbook.template.md)).
|
||||
|
||||
---
|
||||
|
||||
## 8. Record-Keeping
|
||||
|
||||
Completed offboarding checklists are stored in `[FILL IN: location — e.g., HR system / personnel file]` and retained for `[FILL IN: e.g., 7 years]` per the backup and retention policy (see [`backup-policy.template.md`](./backup-policy.template.md)).
|
||||
|
||||
---
|
||||
|
||||
## 9. Review Cycle
|
||||
|
||||
This checklist is reviewed `[FILL IN: frequency — e.g., annually or when systems change]`. The next scheduled review is `[FILL IN: YYYY-MM-DD]`.
|
||||
79
docs/compliance/templates/onboarding.template.md
Normal file
79
docs/compliance/templates/onboarding.template.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
status: template
|
||||
playbook-section: 60
|
||||
title: "Staff Onboarding Checklist (Data Access & Security)"
|
||||
last-reviewed: "[FILL IN: YYYY-MM-DD]"
|
||||
---
|
||||
|
||||
# Staff Onboarding Checklist (Data Access & Security)
|
||||
|
||||
> **Template status** — fill every `[FILL IN: …]` marker before use.
|
||||
|
||||
> **Not code-enforced** — this checklist documents HR and operational controls. Access provisioning, policy acknowledgement, and training completion are tracked outside the application codebase by `[FILL IN: HR system / identity provider / ticketing tool]`. The consumer is responsible for integrating this checklist into their onboarding workflow.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose & Scope
|
||||
|
||||
This checklist ensures that every new employee, contractor, or third-party with access to `[FILL IN: organisation name]`'s systems completes the required security, privacy, and data-access steps before handling personal data.
|
||||
|
||||
**Owner:** `[FILL IN: role — e.g., HR / People Ops + Engineering Lead]`
|
||||
|
||||
---
|
||||
|
||||
## 2. Before First Day
|
||||
|
||||
| # | Task | Owner | Done |
|
||||
| --- | ---------------------------------------------------------------------------------------------------- | --------------------- | ---- |
|
||||
| 1 | Role-based access list agreed with hiring manager | `[FILL IN: e.g., HR]` | ☐ |
|
||||
| 2 | Identity-provider account created (IdP: `[FILL IN: provider name]`) | `[FILL IN: e.g., IT]` | ☐ |
|
||||
| 3 | Device provisioned and MDM-enrolled (see [`device-policy.template.md`](./device-policy.template.md)) | `[FILL IN:]` | ☐ |
|
||||
| 4 | NDA / data-processing agreement signed | `[FILL IN: e.g., HR]` | ☐ |
|
||||
| 5 | Emergency contact and DPO contact shared with new hire | `[FILL IN: e.g., HR]` | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 3. Day 1 — Security & Privacy Orientation
|
||||
|
||||
| # | Task | Owner | Done |
|
||||
| --- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ---- |
|
||||
| 1 | Complete data-protection / GDPR awareness training: `[FILL IN: course name / platform]` | New hire | ☐ |
|
||||
| 2 | Read and acknowledge: Acceptable Use & Device Policy (see [`device-policy.template.md`](./device-policy.template.md)) | New hire | ☐ |
|
||||
| 3 | Read and acknowledge: Password & Authentication Policy (see [`password-policy.template.md`](./password-policy.template.md)) | New hire | ☐ |
|
||||
| 4 | Set up MFA on IdP account: `[FILL IN: MFA method + instructions URL]` | New hire + IT | ☐ |
|
||||
| 5 | Access production systems: `[FILL IN: systems list]` granted at minimum-privilege level | `[FILL IN: e.g., IT / Lead]` | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 4. First Week — System Access Provisioning
|
||||
|
||||
| # | System / tool | Access level | Approver | Done |
|
||||
| --- | -------------------------------------- | ------------------------------------- | ----------------------------- | ---- |
|
||||
| 1 | `[FILL IN: e.g., GitHub org]` | `[FILL IN: e.g., member / write]` | `[FILL IN: engineering lead]` | ☐ |
|
||||
| 2 | `[FILL IN: e.g., Payload CMS admin]` | `[FILL IN: e.g., editor / admin]` | `[FILL IN:]` | ☐ |
|
||||
| 3 | `[FILL IN: e.g., cloud console]` | `[FILL IN: e.g., read-only / scoped]` | `[FILL IN:]` | ☐ |
|
||||
| 4 | `[FILL IN: e.g., monitoring / Sentry]` | `[FILL IN: e.g., member]` | `[FILL IN:]` | ☐ |
|
||||
| 5 | `[FILL IN: e.g., HR / payroll system]` | `[FILL IN:]` | `[FILL IN:]` | ☐ |
|
||||
| 6 | `[FILL IN: any other system]` | `[FILL IN:]` | `[FILL IN:]` | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 5. First 30 Days — Compliance Acknowledgement
|
||||
|
||||
| # | Task | Done |
|
||||
| --- | ----------------------------------------------------------------------------------------------- | ---- |
|
||||
| 1 | Confirm receipt of this organisation's privacy notice (staff version) | ☐ |
|
||||
| 2 | Complete any role-specific data-handling training: `[FILL IN: e.g., PCI / HIPAA if applicable]` | ☐ |
|
||||
| 3 | 30-day check-in with manager on access requirements (reduce if not needed) | ☐ |
|
||||
|
||||
---
|
||||
|
||||
## 6. Record-Keeping
|
||||
|
||||
Completed checklists are stored in `[FILL IN: location — e.g., HR system / personnel file]` and retained for `[FILL IN: e.g., the duration of employment + 2 years]`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Review Cycle
|
||||
|
||||
This checklist is reviewed `[FILL IN: frequency — e.g., annually or when systems change]`. The next scheduled review is `[FILL IN: YYYY-MM-DD]`.
|
||||
82
docs/compliance/templates/password-policy.template.md
Normal file
82
docs/compliance/templates/password-policy.template.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
status: template
|
||||
playbook-section: 40
|
||||
title: "Password & Authentication Policy"
|
||||
last-reviewed: "[FILL IN: YYYY-MM-DD]"
|
||||
---
|
||||
|
||||
# Password & Authentication Policy
|
||||
|
||||
> **Template status** — fill every `[FILL IN: …]` marker before use.
|
||||
|
||||
> **Not code-enforced** — MFA enforcement, minimum password complexity, and account lockout are **explicitly deferred** in [ADR-025 § Deferred items](../../decisions/adr-025-eu-compliance-baseline.md) because they require identity-infrastructure choices (TOTP/WebAuthn/passkeys), a threat-model-specific policy, and an OTP delivery vendor that are consumer-supplied. This template documents the policy; the consumer implements the technical controls once those choices are made.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose & Scope
|
||||
|
||||
This policy defines the minimum authentication standards for all accounts — human and service — that access systems operated by `[FILL IN: organisation name]`, including `[FILL IN: describe systems — e.g., the application, Payload CMS, cloud infrastructure consoles, CI/CD pipelines]`.
|
||||
|
||||
**Owner:** `[FILL IN: role — e.g., Head of Engineering / CISO]`
|
||||
|
||||
---
|
||||
|
||||
## 2. Password Requirements
|
||||
|
||||
### 2.1 User accounts
|
||||
|
||||
| Requirement | Minimum standard | Organisation value |
|
||||
| ----------------------- | ------------------------------------------- | ------------------------------ |
|
||||
| Minimum length | 12 characters | `[FILL IN:]` |
|
||||
| Character complexity | No single-class requirement; length > rules | `[FILL IN: any additions]` |
|
||||
| Breach / pwned-password | Must be checked on creation / change | `[FILL IN: tool / service]` |
|
||||
| Maximum age | Not enforced unless breach detected | `[FILL IN: or "not enforced"]` |
|
||||
| Reuse restriction | `[FILL IN: e.g., last 10 passwords]` | `[FILL IN:]` |
|
||||
|
||||
> Reference: NIST SP 800-63B §5.1.1 — length beats complexity rules; mandatory rotation is discouraged.
|
||||
|
||||
### 2.2 Service accounts & API secrets
|
||||
|
||||
- All service secrets are stored in `[FILL IN: secret manager — e.g., AWS Secrets Manager / HashiCorp Vault]`.
|
||||
- Secrets are rotated `[FILL IN: frequency — e.g., every 90 days or on suspected compromise]`.
|
||||
- Long-lived credentials are prohibited for `[FILL IN: list systems — e.g., production database, CI/CD pipelines]`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Multi-Factor Authentication (MFA)
|
||||
|
||||
> **Deferred per ADR-025.** The specific MFA method (TOTP, WebAuthn, SMS) and the enforcement perimeter are consumer decisions. Until those choices are made, document the target state here.
|
||||
|
||||
| Account type | Required MFA method | Enforcement date / trigger |
|
||||
| ------------------------------ | ---------------------------- | -------------------------- |
|
||||
| Admin / privileged users | `[FILL IN: e.g., WebAuthn]` | `[FILL IN: YYYY-MM-DD]` |
|
||||
| All users (application) | `[FILL IN: e.g., TOTP]` | `[FILL IN:]` |
|
||||
| Service accounts | N/A — use short-lived tokens | — |
|
||||
| Infrastructure / cloud console | `[FILL IN:]` | `[FILL IN:]` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Account Lockout & Brute-Force Protection
|
||||
|
||||
> **Deferred per ADR-025.** Lockout thresholds are deferred until the authentication threat model is established. The application ships rate-limit budgets (see `rateLimit` in each feature manifest and ADR-025 § Epic C) but does not hard-lock accounts.
|
||||
|
||||
| Parameter | Target value | Status |
|
||||
| ------------------------------ | ----------------------------------------------- | ---------------------------- |
|
||||
| Failed attempts before lockout | `[FILL IN: e.g., 10]` | `[FILL IN: deferred / live]` |
|
||||
| Lockout duration | `[FILL IN: e.g., 15 minutes]` | `[FILL IN:]` |
|
||||
| Admin unlock procedure | `[FILL IN: e.g., helpdesk ticket + MFA reset]` | `[FILL IN:]` |
|
||||
| Notification on lockout | `[FILL IN: e.g., email to user + Sentry alert]` | `[FILL IN:]` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Privileged Access
|
||||
|
||||
- Privileged (admin) access requires `[FILL IN: approval workflow — e.g., pair-approval in #infra-access]`.
|
||||
- Privileged sessions are limited to `[FILL IN: duration — e.g., 4-hour session tokens]`.
|
||||
- All privileged actions are captured by the audit channel (see [`docs/guides/audit-and-compliance.md`](../../guides/audit-and-compliance.md)).
|
||||
|
||||
---
|
||||
|
||||
## 6. Review Cycle
|
||||
|
||||
This policy is reviewed `[FILL IN: frequency — e.g., annually or after any authentication-related incident]`. The next scheduled review is `[FILL IN: YYYY-MM-DD]`.
|
||||
Reference in New Issue
Block a user