Search by

phil / privacy-bundle

phil

Privacy law in code for Symfony: personal fields declared by attribute, logs redacted, export and erasure, delayed account deletion, scheduled retention purges with the register of processing, cookie consent refused by default, third-party frames held until consent, requests by verified link, an inc

Package info

github.com/parler-haut-interagir-librement/privacy-bundle

Homepage

Type:symfony-bundle

pkg:composer/phil/privacy-bundle

Statistics

Installs: 0

Dependents: 2

Suggesters: 8

Stars: 0

Open Issues: 0

0.1.0-alpha 2026-11-14 22:00 UTC

This package is auto-updated.

Last update: 2026-11-15 03:40:58 UTC


README

Declare personal fields, redact logs, honour the rights of members and of people without an account, apply retention periods, keep the registers, ask consent to cookies, and anonymize database copies from code.

phil/privacy-bundle puts privacy law into a Symfony application that holds personal information. A #[PersonalData] attribute declares a personal field, and an application test can check that every one has a retention rule. A Monolog processor redacts denied keys, query strings, URL credentials, token path segments, command line options and exception messages before a line is written. Subject access and erasure answers are assembled from one contract per module, wired by autoconfiguration and run from the console. The retention schedule declares the register of processing — purpose, recipients, processors and location of each store —, which the command prints with the declared fields and fails on in CI while a gap remains; it also becomes purge orders, with a run refused when a job line has no purger. A small register keeps every confidentiality incident and names the notifications still owed. A copy of the database is anonymized column by column — emails, names, telephones in fictional ranges, addresses, IBANs, shifted dates, hashes, placeholder text — from #[Anonymize], the declared categories and the configuration, with locale packs for eight locales and a guard that refuses to touch a production database.

Out of the box, a member downloads their data, as a ZIP archive with a readable README and one JSON file per module, and deletes their account from their preferences. A deletion is a workflow: a request, a delay during which it can be cancelled, an export, then the erasure of every module, one execution per person, confirmed by Symfony's sudo mode. Once turned on, a person without an account asks for the same through a link sent to their address, behind rate limits and with the same answer whether the address is known or not. Retention periods are applied by a command or a scheduled message. Consent to cookies is refused by default, as easy to refuse as to accept, can be withdrawn at any time, respects the Global Privacy Control, and leaves a dated proof of the version of the text. Every cookie is declared in a YAML inventory from which the banner and the cookie policy are drawn, and a functional test fails on a cookie it does not declare. A video or a map of another site waits for its consent behind a preview the site serves itself. Once turned on, each member chooses on the same page whether the emails sent to them may count their opens or follow their clicks, each purpose refused until they tick it.

It exists because privacy laws such as Québec's Law 25 give a person the right to see and erase what a business holds about them, and expect the business to know what it keeps and for how long. Keeping that knowledge in the code keeps it true as the code changes, and a developer who needs real data gets a copy in which no person can be found. It is for the developers who build those applications, and for the person in charge of privacy who answers the requests and keeps the register.

The bundle does not decide your denylist, category vocabulary or retention schedule. It does not know what a module holds: each module exports and erases its own records through a port, and each store is purged by its own adapter. It does not verify the identity of someone who writes in by other means than the site, nor back the database up. For a tamper-evident record of who did what, pair it with phil/audit-bundle; to back the database up and restore a copy to anonymize, with phil/backup-bundle.

It is a Symfony bundle on Monolog, Workflow, Lock, Messenger, Yaml, the Security components, Twig and phil/email-address and phil/url, with the Console, Scheduler, Rate Limiter and Mailer components, Doctrine Persistence, DBAL and ORM as options. It is a skeleton of ports and adapters: it carries what the design decides, and ports such as PersonalDataExporter, PersonalDataEraser, RetentionPurger, Anonymizer and AnonymizationTarget for what the application decides.

Caution

A member's export and erasure are on by default on every site. Turning one off takes a legal right away from every member and needs the full sentence of Turning a right off; every build and console command then warns about it.

Status

Area State
#[PersonalData], DeclaredPersonalFields, PersonalField Implemented and tested
DataSubject, DataCategory, ModuleName, ErasureReport Implemented and tested
Denylist, UrlRedactor, CommandLineRedactor, ContextRedactor Implemented and tested
RetentionPeriod, RetentionRule, RetentionSchedule, PurgeOrder and the enums Implemented and tested
RedactingProcessor Implemented, tested, wired from configuration and tagged monolog.processor
SubjectExport, SubjectErasure Implemented, tested, wired over every tagged module
phil:privacy:export, phil:privacy:erase Implemented and tested
ProcessingRegister, ProcessingActivity, phil:privacy:register Implemented and tested: the register of processing from the schedule and the declarations, Markdown or JSON, --strict
RetentionRun, RetentionJob, phil:privacy:retention, the schedule phil_privacy Implemented, tested and wired from a schedule
AccountDeletions, the workflow, phil:privacy:deletions Implemented, tested and wired
The consent to cookies: banner, page, Twig functions, Stimulus controller, proofs Implemented, tested and wired
The cookie inventory, the cookie policy, AssertsDeclaredCookies Implemented, tested and wired
privacy_embed(): third-party frames held until consent Implemented, tested and wired
A member's export and erasure from their preferences, as a ZIP archive Implemented, tested and on by default
The requests of people without an account, by a verified link Implemented, tested and wired; off until enabled
The register of confidentiality incidents, phil:privacy:incident, phil:privacy:incidents Implemented, tested and wired
Each member's consent to tracked emails, EmailTrackingConsents Implemented, tested and wired; off until enabled
The Stimulus controllers through AssetMapper and StimulusBundle Implemented and tested
PersonalDataExporter, PersonalDataEraser, RetentionScheduleSource, RetentionPurger Ports
#[Anonymize], #[AsAnonymizer], the thirteen anonymizers, the eight locale packs Implemented and tested
AnonymizationPlanReader, AnonymizationRun, ProductionGuard, ConnectionIdentity Implemented, tested and wired
phil:privacy:anonymize Implemented and tested on SQLite; MySQL, MariaDB and PostgreSQL through Doctrine DBAL
PhilPrivacyBundle Everything above, with its configuration

Documentation

  • Installation — requirements, installing with Composer, registering the bundle alongside MonologBundle.
  • Configuration — every key of phil_privacy, its default and effect, the services it wires, and each build-time error.
  • Declaring personal data — the #[PersonalData] attribute, reading declarations through parents, and the inventory test that fails on uncovered fields.
  • Encrypting personal data at rest — which columns to encrypt with #[Encrypted] of Symfony's KeyManagement component, blind indexes with #[BlindIndexed] for searching, and rotation.
  • Log redaction — what the processor redacts and why, the denylist comparison and patterns, URL and command line rules, throwables, and processor order in Monolog.
  • Subject export and erasure — DataSubject, the per-module contracts, SubjectExport, SubjectErasure with failure isolation, how the bundle wires them, the register of processing, and the ZIP archive of an access answer.
  • Retention schedule and purges — periods and UTC cutoffs with month clamping, schedule lines and their refused contradictions, RetentionRun, and running the job.
  • Deleting an account — the workflow, the delay and the cancellation, what guarantees one execution per person, running the sweep, and where the export goes.
  • Consent to cookies — declaring categories, the Twig functions, the rules, the cookie and the proof, working without JavaScript, and overriding the templates.
  • The cookie inventory — the YAML file, the banner, the page and the policy drawn from it, and the test that fails on an undeclared cookie.
  • Third-party frames — privacy_embed(), the preview served by the site, the players that write fewer trackers, and the Stimulus controller.
  • Requests without an account — the verified link, the steps, the guards against abuse, and who a verified address is.
  • The register of incidents — recording, completing and printing it, the notifications a serious risk asks for, and how long it is kept.
  • Tracked emails — each member's consent to the counting of opens and the following of clicks, what a sender asks, the version of the text, and its export and erasure.
  • Legal framework — ePrivacy and the CNIL, Québec's Law 25, the GDPR and the proposed Digital Omnibus, and what the default configuration does about each.
  • Export and erasure from the preferences — the routes, the current member, the sudo mode, and what it takes to turn a right off.
  • Anonymization — the plan from attributes, categories and configuration, every anonymizer and its options, the locale packs, the command, the production guard, and a copy to share.
  • Extension points — the ports method by method, custom anonymizers and locale packs, the invariants an adapter keeps, example adapters, and the service ids you can rely on.
  • Testing — how the bundle is tested, running its tests in a monorepo or alone, and how to test your adapters and configuration.
  • API reference — every public type by layer, with its role and state.

Quick start

composer require phil/privacy-bundle:^0.1@dev
# config/packages/phil_privacy.yaml
phil_privacy:
    log_redaction:
        denylist: ['*password*', '*token*', '*secret*', '*authorization*', '*cookie*', query_string]
        token_path_prefixes: ['/p/']
        short_options: { p: password }
final class Member
{
    #[PersonalData('email', retention: 'R-002')]
    private string $email;
}

DeclaredPersonalFields::uncoveredIn(Member::class, $schedule); // [] once every field has a rule
#[PersonalData('email', retention: 'R-002')]
#[Anonymize('email', options: ['domain' => 'example.org'])]
private string $email;
bin/console phil:privacy:anonymize --dry-run           # the plan, and what it leaves in clear
bin/console phil:privacy:anonymize --confirm           # never in production

A malformed configuration entry fails the container build, not the first log line.

Requirements

PHP 8.5 or later with ext-sodium and ext-intl, monolog/monolog 3, Symfony 8.2 and Twig 3. The requests without an account need symfony/rate-limiter, and symfony/mailer for the bundle's sender. MonologBundle is needed for the processor to reach your loggers through its tag, a firewall for a member to reach their preferences, and a database for the account deletions unless a store is named. See Installation.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.