RODO i inżynieria prywatności
Techniczne wdrożenie RODO dla inżynierów: minimalizacja danych, podstawy prawne, privacy by design, usuwanie danych, retencja, DPIA i transfery międzynarodowe.
- Minimalizacja danych (art. 5) jako wymóg projektowy, nie sugestia dla analityków
- Sześć podstaw prawnych art. 6 i dodatkowy reżim danych szczególnych kategorii z art. 9
- Privacy by design i by default (art. 25): ustawienia domyślne muszą być najbardziej restrykcyjne
- Prawo do usunięcia (art. 17) w systemach z backupami, cache'em i logami: crypto-shredding, kaskada do procesorów
- Kiedy DPIA jest obowiązkowa (art. 35, kryteria WP248) i jak ją przeprowadzić
- Pseudonimizacja to wciąż dane osobowe; transfery międzynarodowe po Schrems II (SCC, adekwatność, DPF/Schrems III)
1 · Zasady RODO i minimalizacja danych jako ograniczenie projektowe
★ egzaminRODO (Rozporządzenie 2016/679) nie jest listą życzeń prawników, tylko zbiorem twardych ograniczeń, które muszą znaleźć odzwierciedlenie w schemacie bazy, kontrakcie API i architekturze systemu. Sześć zasad art. 5 ust. 1 to fundament, na którym stoi cała reszta rozporządzenia.
| Zasada | Wymóg | Typowe naruszenie w kodzie |
|---|---|---|
| Zgodność z prawem i przejrzystość | Podstawa prawna + jasna informacja dla użytkownika | Brak polityki prywatności linkowanej z formularza |
| Ograniczenie celu | Dane używane tylko do zadeklarowanego celu | Wykorzystanie adresu email z rejestracji do cold mailingu bez zgody |
| Minimalizacja danych | Zbierasz tylko to, co niezbędne | Formularz zbierający datę urodzenia "na wszelki wypadek" |
| Prawidłowość | Dane aktualne i poprawne, z mechanizmem korekty | Brak endpointu do edycji własnych danych |
| Ograniczenie przechowywania | Retencja ograniczona czasowo | Tabela logów bez TTL, rosnąca od lat bez czyszczenia |
| Integralność i poufność | Bezpieczeństwo techniczne i organizacyjne | Hasła w plaintext, brak szyfrowania w tranzycie |
from pydantic import BaseModel, EmailStr
class SignupRequest(BaseModel):
"""Only fields strictly necessary to create an account.
Marketing consent is a separate, explicit opt-in field, never inferred."""
email: EmailStr
password: str
marketing_consent: bool = False # must default to False (privacy by default)
class UserProfile(BaseModel):
"""Internal model can hold more, but API responses use a narrower view."""
id: str
email: EmailStr
created_at: str
class UserProfilePublic(BaseModel):
"""Shape returned to other users - excludes email entirely."""
id: str
display_name: str - DTO/schema na granicy API ujawnia tylko pola niezbędne odbiorcy, nawet jeśli model wewnętrzny ma więcej danych.
- Endpoint zwracający profil publiczny nigdy nie serializuje pola email czy numeru telefonu.
- Formularz frontendowy nie ma pola, którego backend nie potrzebuje, redukcja na wejściu jest tańsza niż redakcja na wyjściu.
- Code review pyta wprost: jaki jest cel tego pola i kto go realnie używa za 3 miesiące?
- 1 Zdefiniuj cel przed projektowaniem schematu Każde pole w modelu danych ma udokumentowany cel biznesowy i podstawę prawną.
- 2 Zredukuj zakres na granicy API DTO wejściowe/wyjściowe ograniczają pola do potrzebnych danemu operacji.
- 3 Przypisz właściciela polu wrażliwemu Kto odpowiada za to pole, kto ma dostęp, kiedy jest usuwane.
- 4 Zweryfikuj w code review Nowe pole to pytanie o cel, podstawę prawną i okres retencji, nie tylko o typ danych.
2 · Podstawy prawne przetwarzania danych (art. 6 i 9)
★ egzaminKażde przetwarzanie danych osobowych musi opierać się na jednej z sześciu podstaw prawnych z art. 6 ust. 1. Brak wybranej podstawy nie oznacza "przetwarzania na zasadach ogólnych", oznacza przetwarzanie niezgodne z prawem od pierwszej linijki kodu.
| Podstawa (art. 6 ust. 1) | Kiedy stosować | Przykład |
|---|---|---|
| lit. a) zgoda | Brak innej podstawy, np. marketing email | Newsletter po dobrowolnym opt-in |
| lit. b) wykonanie umowy | Dane niezbędne do realizacji umowy z osobą | Adres dostawy przy zamówieniu |
| lit. c) obowiązek prawny | Przepis nakazuje przetwarzanie | Dane do faktury VAT, procedury KYC/AML |
| lit. d) żywotne interesy | Ochrona życia/zdrowia, gdy zgoda niemożliwa | Udostępnienie danych medycznych w nagłym wypadku |
| lit. e) zadanie publiczne | Wykonywanie władzy publicznej | Rejestry administracji publicznej |
| lit. f) prawnie uzasadniony interes | Interes administratora ważący więcej niż prawa osoby, po LIA | Zapobieganie oszustwom, podstawowa analityka bezpieczeństwa |
from dataclasses import dataclass
from datetime import datetime, timezone
@dataclass(frozen=True)
class ConsentRecord:
"""Immutable proof of consent, kept for accountability (Art. 5(2), Art. 7(1))."""
user_id: str
purpose: str # e.g. "marketing_email", never a bundled "all purposes"
policy_version: str # ties consent to the exact text the user saw
given_at: datetime
withdrawn_at: datetime | None = None
def is_active(self) -> bool:
return self.withdrawn_at is None
def record_consent(user_id: str, purpose: str, policy_version: str) -> ConsentRecord:
return ConsentRecord(
user_id=user_id,
purpose=purpose,
policy_version=policy_version,
given_at=datetime.now(timezone.utc),
) Zgoda (consent)
- Wymaga aktywnego opt-in użytkownika
- Musi być odwoływalna w każdej chwili
- Dobra podstawa dla marketingu i funkcji nieistotnych dla usługi
- Słaba podstawa gdy istnieje nierównowaga sił (np. pracodawca-pracownik)
Prawnie uzasadniony interes
- Nie wymaga zgody użytkownika, ale wymaga LIA
- Osoba ma prawo wnieść sprzeciw (art. 21)
- Dobra podstawa dla bezpieczeństwa, zapobiegania nadużyciom, podstawowej analityki
- Nie działa dla danych szczególnych kategorii (art. 9)
- 1 Sprawdź, czy istnieje przepis nakazujący przetwarzanie (obowiązek prawny) zanim sięgniesz po zgodę.
- 2 Sprawdź, czy dane są niezbędne do wykonania umowy z tą konkretną osobą.
- 3 Jeśli żadna z twardych podstaw nie pasuje, rozważ prawnie uzasadniony interes i udokumentuj LIA.
- 4 Zgodę traktuj jako podstawę ostatniego wyboru dla marketingu, ponieważ jest najłatwiejsza do wycofania i najtrudniejsza do utrzymania w audycie.
3 · Privacy by design i privacy by default w praktyce inżynierskiej (art. 25)
★ egzaminArt. 25 przenosi RODO z warstwy polityki do warstwy architektury: uwzględnianie ochrony danych w fazie projektowania i domyślna ochrona danych to wymogi wobec systemu, nie deklaracje w dokumencie.
# Migration: new visibility field defaults to the most restrictive setting.
# Privacy by default (Art. 25(2)): the user must opt IN to public visibility,
# never opt out of it.
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("profiles", "0012_alter_profile")]
operations = [
migrations.AddField(
model_name="profile",
name="is_public",
field=models.BooleanField(default=False), # private until user chooses otherwise
),
] - Modelowanie danych: pole wrażliwe ma domyślną wartość najbardziej restrykcyjną, nie "null" interpretowane jako publiczne.
- Warstwa API: endpoint bez jawnie podanych parametrów zwraca minimalny zestaw danych, rozszerzenie wymaga explicit query param.
- Integracje trzecie: nowy webhook czy SDK analityczny przechodzi przegląd danych, zanim trafi na produkcję, nie po.
- Kontrola dostępu: RBAC/ABAC domyślnie odmawia dostępu (deny by default), uprawnienia nadawane wprost, nigdy przez brak reguły.
- 1 Data flow diagram Zmapuj przepływ danych nowej funkcji: skąd, dokąd, przez kogo, w tym procesorów trzecich.
- 2 Przegląd minimalizacji Dla każdego pola: czy jest niezbędne do konkretnego celu tej funkcji?
- 3 Audyt ustawień domyślnych Każdy nowy przełącznik/widoczność: wartość domyślna to najbardziej restrykcyjna.
- 4 Screening pod DPIA Sprawdź kryteria WP248 (sekcja 7), zdecyduj czy potrzebna pełna DPIA.
4 · Klasyfikacja i obsługa danych osobowych w kodzie
Zanim padnie pytanie "czy to jest RODO", trzeba wiedzieć, czym w ogóle jest dana osobowa w kodzie, który piszesz, i jak ją oznaczyć tak, by dalsze warstwy systemu (logi, cache, eksport do BI) wiedziały, że mają do czynienia z czymś wrażliwym.
| Klasa danych | Przykład | Wymagana kontrola |
|---|---|---|
| Publiczne | Nazwa firmy, cennik | Brak specjalnych wymagań |
| Wewnętrzne | Statystyki agregowane, brak PII | Standardowa kontrola dostępu |
| Dane osobowe | Email, IP, historia zamówień | Szyfrowanie w spoczynku, RBAC, logowanie dostępu, retencja |
| Dane szczególnych kategorii (art. 9) | Dane zdrowotne, biometryczne | Dodatkowa podstawa prawna, szyfrowanie na poziomie pola, ścisła kontrola dostępu, DPIA |
import logging
import re
PII_PATTERNS = {
"email": re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+"),
"ip": re.compile(r"\b\d{1,3}(?:\.\d{1,3}){3}\b"),
}
class RedactPIIFilter(logging.Filter):
"""Strips common PII shapes before a log record leaves the process,
so third-party log aggregators (Sentry, Datadog) never receive raw PII."""
def filter(self, record: logging.LogRecord) -> bool:
msg = record.getMessage()
for label, pattern in PII_PATTERNS.items():
msg = pattern.sub(f"[REDACTED_{label.upper()}]", msg)
record.msg, record.args = msg, ()
return True
logger = logging.getLogger("app")
logger.addFilter(RedactPIIFilter()) - Szyfrowanie w spoczynku (at rest) dla całej bazy plus szyfrowanie na poziomie pola dla danych szczególnych kategorii.
- RBAC/ABAC z zasadą najmniejszych uprawnień, dostęp do tabeli z PII nie jest domyślny dla każdego mikroserwisu.
- Tokenizacja numerów kart/dokumentów tam, gdzie pełna wartość nie jest potrzebna w większości przepływów.
- Maskowanie w interfejsach administracyjnych (ostatnie 4 cyfry, częściowy email), pełna wartość tylko po dodatkowej autoryzacji.
- 1 Zinwentaryzuj przepływy danych Skąd dana wchodzi do systemu, przez jakie usługi przechodzi, gdzie ląduje: baza, cache, log, BI, backup.
- 2 Oznacz pola w kodzie Metadane lub konwencja nazewnicza wskazująca klasę danych, czytelna dla lintera i serializera.
- 3 Zsynchronizuj z rejestrem czynności (art. 30) Inwentarz z kroku 1 to surowiec dla ROPA, nie osobny dokument tworzony ręcznie od zera.
5 · Prawo do usunięcia: dlaczego jest trudne technicznie (art. 17)
★ egzamin"Usuń moje dane" brzmi jak jedno zapytanie DELETE. W realnym systemie to rozgałęziony problem: baza produkcyjna, indeks wyszukiwania, cache, kolejka zdarzeń, hurtownia danych, logi, backupy i procesorzy trzecy, każdy z innym cyklem życia.
from cryptography.fernet import Fernet
class SubjectKeyStore:
"""One encryption key per data subject. "Erasing" a user means deleting
their key here, not rewriting every backup that contains their ciphertext."""
def __init__(self) -> None:
self._keys: dict[str, bytes] = {}
def key_for(self, user_id: str) -> bytes:
if user_id not in self._keys:
self._keys[user_id] = Fernet.generate_key()
return self._keys[user_id]
def erase(self, user_id: str) -> None:
# Deleting the key here makes every ciphertext copy that exists only as
# bytes elsewhere (a backup, a log, another process) unreadable forever,
# since nothing can retrieve this key again. Note: key_for() called again
# for this user_id after erase() would silently mint a brand-new, unrelated
# key, that is a separate footgun, not an "undo" of the erasure.
self._keys.pop(user_id, None)
store = SubjectKeyStore()
cipher = Fernet(store.key_for("user-123"))
encrypted = cipher.encrypt(b"sensitive profile data")
store.erase("user-123")
# cipher already captured its own copy of the key bytes at construction time,
# so cipher.decrypt(encrypted) still succeeds here, erase() does not reach into
# live objects. The real guarantee covers ciphertext sitting as bytes elsewhere
# (backups, logs, other processes) that never had a live Fernet built from this key. - Baza produkcyjna: DELETE lub anonimizacja rekordu.
- Indeks wyszukiwania (Elasticsearch/OpenSearch): osobne zadanie reindeksujące lub usuwające dokument.
- Cache (Redis/Memcached): TTL rozwiązuje część przypadków, ale klucze bez TTL wymagają jawnego czyszczenia.
- Kolejki zdarzeń (Kafka): retencja tematu ograniczona czasowo, event z PII nie powinien żyć w logu zdarzeń w nieskończoność.
- Hurtownia danych/BI: pipeline ETL musi honorować to samo żądanie usunięcia, nie tylko system źródłowy.
- Modele ML: dane treningowe zawierające PII konkretnej osoby to osobny, trudniejszy problem, nie zawsze da się wymazać wpływ na wagi modelu.
- Procesorzy trzecy: administrator musi powiadomić każdego odbiorcę, któremu dane ujawniono, chyba że jest to niemożliwe lub wymaga niewspółmiernie dużego wysiłku (art. 19).
- 1 Weryfikacja tożsamości żądającego Nie usuwaj danych na podstawie samego emaila bez potwierdzenia, że żąda właściwa osoba.
- 2 Lokalizacja danych przez ROPA Rejestr czynności przetwarzania (art. 30) wskazuje, gdzie dana kategoria danych w ogóle żyje.
- 3 Kaskada do procesorów Powiadomienie procesorów zgodnie z umową powierzenia, art. 28 ust. 3 lit. g: obowiązek usunięcia/zwrotu danych po zakończeniu usługi.
- 4 Log samego usunięcia Fakt i czas wykonania żądania trzeba udokumentować, to dowód rozliczalności, nawet po zniknięciu danych źródłowych.
- 5 Odpowiedź w terminie Co do zasady miesiąc od żądania (art. 12 ust. 3), przedłużalne o kolejne dwa miesiące przy złożonych lub licznych żądaniach, z uzasadnieniem.
6 · Retencja danych: polityki wymuszane w schemacie i automatyzacji
Zasada ograniczenia przechowywania (art. 5 ust. 1 lit. e) mówi, że dane nie mogą żyć dłużej niż to niezbędne do celu, dla którego są przetwarzane. Wdrożenie tej zasady w kodzie oznacza: retencja to pole w schemacie i zaplanowane zadanie, nie ręczna procedura wykonywana przy okazji.
| Kategoria danych | Typowy okres retencji | Podstawa okresu |
|---|---|---|
| Logi uwierzytelniania | 6-12 miesięcy | Bezpieczeństwo, wykrywanie nadużyć (art. 6 ust. 1 lit. f) |
| Zgoda marketingowa (dowód) | Czas trwania zgody plus okres przedawnienia roszczeń | Rozliczalność (art. 5 ust. 2) |
| Faktury/dokumenty księgowe | 5-10 lat, zależnie od jurysdykcji | Obowiązek prawny, prawo podatkowe |
| Zgłoszenia supportowe | 2-3 lata od zamknięcia zgłoszenia | Uzasadniony interes, jakość obsługi |
| Dane sesji/koszyka niezalogowanego użytkownika | Dni do tygodni | Minimalizacja, brak dalszego celu po sesji |
-- Column encodes the legal/business retention deadline directly in the schema,
-- so "when can this be deleted" is a query, not a spreadsheet. Computed from
-- closed_at (not created_at), matching the "retention from ticket closure" policy;
-- stays NULL while the ticket is open, so open tickets are never purged below.
ALTER TABLE support_tickets
ADD COLUMN retention_until date
GENERATED ALWAYS AS ((closed_at + INTERVAL '3 years')::date) STORED;
-- Scheduled job (e.g. pg_cron or an external cron calling this daily)
-- purges rows past their retention deadline, including soft-deleted ones.
DELETE FROM support_tickets
WHERE retention_until < CURRENT_DATE; - Kolumna/pole retention_until lub TTL natywny silnika bazy: Redis EXPIRE, DynamoDB TTL, S3 Lifecycle Policy.
- Zaplanowane zadanie (cron, pg_cron, Celery beat, Cloud Scheduler) uruchamiane cyklicznie, nie gdy ktoś się przypomni.
- Partycjonowanie tabel po dacie umożliwia tanie usuwanie całej partycji zamiast miliona pojedynczych DELETE.
- Audyt: log samego procesu czyszczenia (co, kiedy, ile rekordów), do wykazania rozliczalności.
- 1 Skategoryzuj dane Każda tabela/kolekcja z PII ma przypisaną kategorię z harmonogramu retencji.
- 2 Przypisz okres i podstawę Okres retencji wynika z realnego celu lub przepisu prawa, nie z domysłu.
- 3 Zakoduj w schemacie Pole z terminem lub TTL silnika bazy, nie komentarz w dokumentacji, który nikt nie czyta.
- 4 Zautomatyzuj usuwanie Zadanie cykliczne, monitorowane jak każdy inny krytyczny cron w systemie.
7 · DPIA: ocena skutków dla ochrony danych
★ egzaminDPIA (Data Protection Impact Assessment, ocena skutków dla ochrony danych) to formalny proces wymagany art. 35 RODO, gdy planowane przetwarzanie może powodować wysokie ryzyko dla praw i wolności osób. Dla inżyniera to bramka w procesie projektowania nowej funkcji, nie dokument tworzony po fakcie.
- 1 Ocena/scoring cech osobowych, np. profil kredytowy, ocena wydajności pracownika.
- 2 Zautomatyzowane podejmowanie decyzji ze skutkiem prawnym (art. 22).
- 3 Systematyczne monitorowanie, np. monitoring pracowników, śledzenie lokalizacji.
- 4 Dane szczególnych kategorii lub dane o wyrokach skazujących na dużą skalę.
- 5 Przetwarzanie na dużą skalę: liczba osób, zakres geograficzny, czas trwania.
- 6 Łączenie lub porównywanie zbiorów danych z różnych źródeł.
- 7 Dane osób w sytuacji szczególnej podatności: dzieci, pracownicy, pacjenci.
- 8 Innowacyjne użycie technologii, np. nowy model biometryczny.
- 9 Przetwarzanie uniemożliwiające osobie wykonanie prawa lub skorzystanie z usługi/umowy.
"""Simple heuristic CI check: flags new fields that look like special-category
data (Art. 9) so they get a linked DPIA before merging, not after launch."""
import subprocess
SENSITIVE_MARKERS = ("health_", "biometric_", "genetic_", "religion_", "orientation_")
def new_fields_in_diff(base_ref: str = "origin/main") -> list[str]:
diff = subprocess.run(
["git", "diff", base_ref, "--unified=0", "--", "*/models.py"],
capture_output=True, text=True, check=True,
).stdout
return [line for line in diff.splitlines() if line.startswith("+") and
any(marker in line for marker in SENSITIVE_MARKERS)]
if new_fields_in_diff():
raise SystemExit(
"New special-category field detected: link a DPIA ticket before merging (Art. 35)."
) - 1 Opisz przetwarzanie Cel, zakres danych, kategorie osób, odbiorcy, okres przechowywania.
- 2 Oceń niezbędność i proporcjonalność Czy cel da się osiągnąć mniejszym zakresem danych lub mniej inwazyjną metodą?
- 3 Oceń ryzyko dla osób Prawdopodobieństwo i dotkliwość skutków dla praw i wolności, nie tylko ryzyko biznesowe/reputacyjne administratora.
- 4 Zaplanuj środki zaradcze Techniczne: szyfrowanie, pseudonimizacja, kontrola dostępu; organizacyjne: szkolenia, procedury.
- 5 Skonsultuj z IOD/DPO Inspektor Ochrony Danych opiniuje DPIA przed wdrożeniem (art. 35 ust. 2).
- 6 Konsultacja z organem nadzorczym Jeśli po środkach zaradczych ryzyko rezydualne pozostaje wysokie, wymagana uprzednia konsultacja z organem nadzorczym (art. 36).
8 · Anonimizacja, pseudonimizacja i transfery międzynarodowe po Schrems II
★ egzaminDwa różne mechanizmy często mylone w praktyce inżynierskiej: anonimizacja wyprowadza dane spod RODO, pseudonimizacja nie. Ta sama pomyłka pojawia się przy transferach danych poza EOG: "zaszyfrowaliśmy więc jest bezpiecznie" nie jest odpowiedzią na pytanie prawne o podstawę transferu.
import hashlib
import hmac
import os
def naive_pseudonymize(email: str) -> str:
# WRONG: a plain hash of a low-entropy value (email) is reversible via
# a rainbow table or simple dictionary attack. Still personal data,
# and easily re-identifiable by anyone, not just the controller.
return hashlib.sha256(email.encode()).hexdigest()
SECRET_KEY = os.environ["PSEUDONYMIZATION_KEY"].encode()
def proper_pseudonymize(email: str) -> str:
# Correct: HMAC with a server-side secret kept separate from the output.
# Re-identification requires the secret key (Art. 4(5) "additional information
# kept separately"), which is what makes this pseudonymization, not encryption
# or anonymization, and why the result is STILL personal data under GDPR.
return hmac.new(SECRET_KEY, email.encode(), hashlib.sha256).hexdigest() | Mechanizm transferu (rozdz. V RODO) | Podstawa | Uwaga praktyczna |
|---|---|---|
| Decyzja o adekwatności (art. 45) | Komisja Europejska uznaje kraj/organizację za zapewniające adekwatny poziom ochrony | Lista obejmuje m.in. Wielką Brytanię (odnowiona grudzień 2025), Japonię, Koreę Płd., Brazylię (od stycznia 2026), USA wyłącznie dla podmiotów certyfikowanych w Data Privacy Framework |
| Standardowe klauzule umowne, SCC (art. 46) | Wzorzec klauzul z 2021 r., moduły kontroler-kontroler, kontroler-procesor itd. | Wymaga transfer impact assessment po Schrems II, czasem środków dodatkowych: szyfrowanie end-to-end, minimalizacja zakresu transferu |
| Wiążące reguły korporacyjne, BCR (art. 47) | Wewnątrzgrupowa polityka zatwierdzona przez organ nadzorczy | Kosztowne i czasochłonne wdrożenie, sensowne dla dużych grup kapitałowych z regularnymi transferami wewnętrznymi |
- 1 Zmapuj przepływy danych poza EOG Który procesor/subprocesor fizycznie przetwarza dane poza Europejskim Obszarem Gospodarczym?
- 2 Zweryfikuj mechanizm transferu Adekwatność, SCC czy BCR, i czy dokumentacja jest aktualna.
- 3 Przeprowadź transfer impact assessment Ocena prawa i praktyki kraju odbiorcy pod kątem dostępu władz publicznych do danych.
- 4 Wdróż środki dodatkowe, jeśli potrzebne Szyfrowanie z kluczem niedostępnym dla odbiorcy, pseudonimizacja przed transferem, minimalizacja zakresu.
Sprawdź się - testowanie to nauka
24 pytań w losowej kolejności. Twoje wyniki zapisują się lokalnie.