WordPress / WordPress/two-factor
Privacy: register a personal data exporter and eraser
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 825
- Forks
- 187
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 17
Description
Two Factor doesn't hook into the export or erasure tools
#869 added suggested privacy policy text in 0.17.0. That covers telling people what Two Factor stores.
WordPress also has two tools for acting on that: Personal Data Export and Personal Data Erasure, both added in 4.9.6. This plugin does not hook into either one. So when an admin runs an export or an erasure request, nothing from Two Factor shows up, even though it keeps authentication records for every user.
The same question is about to land on a lot of other plugins — see below.
What Two Factor stores per user
| Meta key | Holds |
|---|---|
_two_factor_enabled_providers |
Enabled methods |
_two_factor_provider |
Primary method |
_two_factor_totp_key |
TOTP secret |
_two_factor_totp_last_successful_login |
Replay-protection timestamp |
_two_factor_backup_codes |
Hashed backup codes |
_two_factor_email_token |
Hashed email login code |
_two_factor_email_token_timestamp |
When that code was sent |
_two_factor_nonce |
Login nonce |
_two_factor_last_login_failure |
Rate-limit timestamp |
_two_factor_failed_login_attempts |
Failed attempt counter |
_two_factor_password_was_reset |
Compromised-password reset flag |
What the export should say
An export file gets emailed around and sits in downloads folders. It should say what Two Factor holds without handing over anything useful to an attacker:
- Enabled methods and primary method: the values as stored
- TOTP: say it is set up, never include the secret
- Backup codes: say how many are left, never the codes or their hashes
- Email login code: say when it was sent, do not include the stored hash
- Failed attempts and last failure time: as stored
The question: erase the credentials, or keep them?
First, what the tool actually does. Erasing personal data is not deleting the account. Core registers one eraser, wp_register_comment_personal_data_eraser, and it anonymizes comment metadata. Nothing in the flow deletes the user, ends their sessions, or notifies anyone. When the request finishes, the account still exists and still logs in.
So: a request comes in, the account survives it, and 2FA is still switched on. What should Two Factor's eraser do?
Option A: erase the short-lived records, keep the credentials.
Delete the login nonce, the failed attempt counter, the rate-limit timestamp, the password-reset flag, the pending email code, and the TOTP replay timestamp. Keep the enabled methods, the TOTP secret, and the backup codes, and report them through items_retained with a message saying so.
Option B: erase everything.
A request to erase means erase. If 2FA stops working, that is the requester's call, not Two Factor's.
Why Option A is best
Two Factor's eraser gets an email address. It cannot tell which of two situations it is in:
-
The admin is also deleting the account.
-
The admin is not deleting the account, which is the default.
The decision to retain some data or delete it all only matters in the second situation, when the user account is not to be deleted. In the first, wp_delete_user() removes every usermeta row Two Factor owns, so both options end the same way. In the second, it never runs and nothing else clears those rows, so if Option B is followed (personal data erasure required = erase everything), this quietly weakens the security of an active user account. The user still may log in, now with only a password. The second factor is gone without notification to anyone.
Other plugins will face the same question
Over in the Plugin Check repo, @masteradhoc's issues WordPress/plugin-check#1251 and WordPress/plugin-check#1252 ask for checks that warn developers of any plugin storing personal data if it has not registered callbacks on the wp_privacy_personal_data_exporters and wp_privacy_personal_data_erasers filters. Both checks have implementations open in WordPress/plugin-check#1292 and WordPress/plugin-check#1293. A third, WordPress/plugin-check#1291, covers the privacy policy text this plugin already added in #869. If they land, a lot of plugin authors are going to sit down and write an eraser, many of them for the first time.
The obvious way to write one is to delete every meta key you own. For most plugins that is fine. For anything holding a credential it is not, and 2FA is the clearest case of it.
Why items_retained fits
It is the field for exactly this case: data you are keeping, disclosed plainly rather than kept quietly.
Core registers the filters, providers contribute their own data and keys
Two_Factor_Core registers both privacy filters, wp_privacy_personal_data_exporters and wp_privacy_personal_data_erasers, and handles its own meta keys. Providers add their own export data and eraser keys through two optional methods on Two_Factor_Provider, privacy_export_data() and privacy_eraser_user_meta_keys(). That follows the existing uninstall_user_meta_keys() pattern, so no provider-specific code ends up in Two_Factor_Core. Core also exposes format_privacy_timestamp() so providers render dates the same way.
Branch: https://github.com/dknauss/two-factor/tree/add/privacy-exporter-eraser
It has 15 tests covering both the exporter and the eraser. Three of them take the whole exported payload and check that the TOTP secret, the generated backup codes, and the stored email token hash appear nowhere in it.
@masteradhoc, since #869 was yours, this looks like your call.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the add/privacy-exporter-eraser branch and inspect Two_Factor_Core, Two_Factor_Provider, and the 15 privacy tests mentioned in the issue. Review the exporter and eraser callbacks, provider extension methods, and timestamp formatting, then confirm the tests cover the specified retained data and exclude secrets from exported output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, wordpress
- Domain
- authentication, backend, security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100