Data Protection Impact Assessment (Art. 35 GDPR) – MousePlayer Session Replay
Approved on 17 Aug 2026, updated through 16 Oct 2026 (status 16 October 2026). Measured against code, schema and operation of the development system
1. Why This Assessment Is Required
Art. 35(1) GDPR requires an impact assessment where a type of processing, in particular using new technologies and taking account of its nature, scope, context and purposes, is likely to result in a high risk to the rights and freedoms of natural persons. MousePlayer records the behaviour of website visitors continuously and by default: pointer movement, clicks, scrolling, taps and tab changes, together with a complete snapshot of the page the visitor saw and every later change to it, plus the text the visitor types into forms. The recorded material is stored, replayed, scored and can be shared. That is not an edge case of Art. 35(1); it is the centre of it.
One point of classification has to be stated precisely rather than repeated. Art. 35(3) lists three cases in which a DPIA is required in particular, and none of the three fits cleanly. Letter (a) covers systematic and extensive evaluation based on automated processing on which decisions producing legal or similarly significant effects are based - the evaluation exists here (see Risk R8), but no such decision against a visitor was found. Letter (b) covers large-scale processing of special categories under Art. 9 - not intended here, although special categories do reach the recording by an unintended route (Risk R1), and the measured volumes on this system are not large scale. Letter (c) covers systematic monitoring of a publicly accessible area on a large scale, which is aimed at physical observation, not at a website. Citing letter (b) as the hook for session replay would be wrong, and a wrong citation is the first thing an authority strikes out.
The correct route is Art. 35(1) itself, applied through the nine criteria of the WP29 guidelines on DPIA (WP248 rev.01, endorsed by the EDPB). Two criteria are generally taken to indicate a high risk. This processing meets six or seven.
- Evaluation or scoring - met. Every session receives engagement_score (0-255), session_quality and is_bot; at the August 2026 survey 4,043 feature rows and 3,203 machine-written session narratives existed. Further scores and derivations have been added since (see R8).
- Automated decision-making with legal or similar effect - not established for visitors. It is established against customers and IP addresses (auto-ban, is_suspicious), which is a different data subject group.
- Systematic monitoring - met in the strongest form. Recording is continuous, methodical and invisible to the person observed once the banner is gone.
- Sensitive data or data of a highly personal nature - met. Form entries, the full text of the page as rendered for that individual, and a minute-by-minute record of what they read and hesitated over.
- Large scale - not met on the measured figures of this development system (August 2026 survey: 706 sessions, 329 distinct visitor identifiers, 7 projects, 4 accounts. Status 16 Oct 2026: 8,648 human sessions with 7,363 distinct visitor identifiers since 21 Jul 2026, of which 3,515 distinct identifiers in the last 30 days, in 3 of 75 created projects; in addition 186,163 sessions marked as bots from the last 14 days, which are deleted when their period expires; 24 accounts, 19 of them recognisable test accounts). It is met by design intent, and the design contains no upper bound.
- Matching or combining datasets - met. MousePlayer.setUserId() and the automatic dataLayer 'uid' path attach the customer's own identifier to the recording; the BigQuery export ships visitor_id, custom_user_id and ip_address into the customer's own warehouse.
- Vulnerable data subjects - arguably met. The visitor cannot see what was recorded, cannot verify it, and in 92 cases at the August 2026 survey could not be connected to their own records at all (status 16 Oct 2026: no such session; see R9).
- Innovative use - met. Full-DOM replay combined with machine-generated behavioural narratives is not an established, well-understood technique.
- Preventing the exercise of a right or use of a service - met in two narrow ways: the automatic IP ban terminates further access, and the identification gap (R9) makes Art. 15 and Art. 17 technically unfulfillable for part of the stored data.
Conclusion on Art. 35(1): a DPIA is required. It is required on the merits, not as a formality, and it would be required even if the current data volumes were smaller than they are - because the volume of data per individual, not the number of individuals, is what makes this processing intrusive. A single stored session contains, on average, 90.3 kB of the page as that person saw it, with a measured maximum of 1,235,047 bytes (August 2026 survey, uncompressed). Status 16 Oct 2026: 19,988 stored page snapshots, compressed 42.0 kB on average, at most 453,821 bytes.
Art. 35(4) obliges each supervisory authority to publish a list of processing operations for which a DPIA is mandatory. The competent authority here is the Polish UODO. to be completed: which entries of the current UODO list under Art. 35(4) apply to session replay, and the date of the list version checked
2. Roles: Who Owes This Duty, and For What
MousePlayer occupies two positions at once, and the answer to almost every question in this document depends on which one is being asked about. The distinction is not a presentational choice; it is written into the code. class/Server.class.php:416 states it in the source itself: "REGEL B ... Hier sind wir AUFTRAGSVERARBEITER" - here we are the processor.
| Data | Role of MousePlayer | Controller | Consequence |
|---|---|---|---|
| Visitors on customer websites: recordings, page content, form entries, identifiers, derived scores | Processor (Art. 28) | The MousePlayer customer operating the website | The Art. 35 duty formally belongs to the customer. MousePlayer's duty is to assist under Art. 28(3)(f). |
| MousePlayer's own customers: accounts, billing, support, portal access logs, audit trail | Controller (Art. 24) | LW IT Solutions Company Lukas Wójcik | MousePlayer owes the Art. 35 assessment for this processing in its own right - although this part alone would not trigger one. |
This produces a result worth stating plainly, because it is frequently got wrong in both directions. The processing that requires a DPIA is the processing for which MousePlayer is not the controller. A processor cannot carry out the controller's assessment for them, cannot decide the necessity question on their behalf, and cannot consult the supervisory authority in their place under Art. 36. What a processor can and must do is supply the controller with everything the controller needs - the description under (a), the measured facts under (c), and an honest status list under (d). That is what this document is: MousePlayer's own assessment of its own operations, and the factual foundation the customer needs for theirs.
A customer who adopts this document unchanged as their own DPIA has not performed one. Sections (b) and (d) contain decisions only the controller can take: whether the purpose justifies the recording on their site, which pages are excluded, whether their sector permits it at all, and which of the planned measures they require before going live.
3. Data Protection Officer: The Art. 37 Test
No data protection officer is designated. Art. 37(1) binds "the controller and the processor" alike, so the question is MousePlayer's own, not one that can be passed to customers. The three statutory cases:
| Art. 37(1) | Test | Result on the measured facts |
|---|---|---|
| (a) | Processing by a public authority or body | Not applicable. LW IT Solutions Company is a private undertaking. |
| (b) | Core activities require regular and systematic monitoring of data subjects on a large scale | Core activity: yes, without argument - the monitoring is the product, not a support function. Regular and systematic: yes - continuous, methodical, by pre-arranged system. Large scale: no, on the measured figures (August 2026 survey: 706 sessions, 329 identifiers, 4 accounts; status 16 Oct 2026: 3,515 distinct human visitor identifiers in 30 days; one development instance). |
| (c) | Core activities consist of large-scale processing of Art. 9 or Art. 10 data | Not by design. Special categories nonetheless enter the stored page content by the route described in Risk R1. That is incidental rather than constitutive, so the case is not made out today - but it becomes arguable the moment a customer in the health, legal or financial sector is onboarded. |
Result, stated clearly: there is currently no legal obligation to designate a data protection officer. Two of the three elements of Art. 37(1)(b) are met permanently and by design; the only element that is not met is scale, and scale is precisely the variable this business model exists to increase. The obligation is therefore not absent - it is pending, and it will arrive without announcement.
WP243 (endorsed by the EDPB) assesses "large scale" on the number of data subjects, the volume and range of data, the duration and the geographical extent. Three of those four factors are already at their maximum here: the range is a complete reconstruction of the individual's session, the duration is continuous, and the geographic extent is whatever the customers' audiences are. Only the head count is small. So that the threshold is not noticed too late, srv/cron_status.php has counted, once a day since 19 Aug 2026, the distinct human visitor identifiers of the last 30 days (excluding 'unknown' and sessions marked as bots), compares the figure with the threshold of 100000 end users per month, and reports an overrun as well as the due annual review by e-mail. Status 16 Oct 2026: 3,515.
Two things follow, and both are recommendations rather than statements of law. First, record a written threshold assessment now, with a stated number of monitored end users at which designation happens automatically and a fixed review interval - so that the decision is documented as taken, not merely as not yet taken. Second, note that Art. 37(4) permits Member State law to require designation in further cases. to be completed: legal confirmation whether Polish law under Art. 37(4) creates a duty to designate beyond Art. 37(1). The threshold and review interval are decided: more than 100000 observed end users per month, at least annually
Art. 35(2) requires the controller to seek the advice of the data protection officer, where designated, when carrying out a DPIA. With no officer designated, that step is not applicable and the omission is not a defect. Approved by Lukas Wójcik, owner, on 17 Aug 2026. No external data protection advice was obtained. Reviewed annually and on every change to the processing; the dated versions are kept in version control under ai/cms/.
4. Method, Sources and the Limits of This Draft
Every factual statement in this document was measured against the running system - source code, database schema, row counts, process list, configuration values and log files - rather than taken from product descriptions or from what the software is supposed to do. Where a measurement contradicts an intention, the measurement is reported. Where a fact could not be established, a placeholder stands in its place; no figure was estimated and no retention period was invented.
- The system measured is the development instance (APP_ENV=dev). Several figures are therefore artefacts of test data and are labelled as such - most importantly the consent records, where 671 of 706 sessions carry consent_source='testdata' with a NULL timestamp, written by ai/devdata/001_altsitzungen_markieren.sql (August 2026 survey; status 16 Oct 2026: 566 of 8,648 human sessions).
- Some measurements were taken minutes apart and differ slightly as a result (705 vs. 706 sessions; 36,409 log rows vs. 36,542 addresses checked). Both figures are reported as measured rather than reconciled after the fact.
- Root-level access was not available. The fail2ban jail configuration, the root and www-data crontabs, and the arrival of the off-site backup at its destination could therefore not be verified. Each is marked as unverified rather than assumed. Added since: the operator has meanwhile carried out the changeover of the backup with root rights, and the arrival of the off-site backup at its destination has been evidenced since 11 August 2026, because the daily run logs and checks it itself - it is on record for 11 and 12 August 2026 (see R5). Re-checked on 16 Oct 2026: fail2ban runs nine jails, among them mp-nginx-429 for addresses that repeatedly trigger the nginx rate limit. MousePlayer's scheduled runs are listed in /etc/cron.d/mouseplayer and run through srv/cron_run.sh under the dedicated account mouseplayer-dev; the worker services also run under that account. The daily backup is still started from root's cron, and the daily deletion run still runs inside the db_worker process.
- The vendor directories (Stripe SDK, Google Cloud SDK, PHPMailer, Symfony) were not searched for outbound endpoints. The first-party code was searched exhaustively for curl_init, curl_exec, file_get_contents on http, fsockopen and stream_context_create, and for all https literals.
- One figure in this assessment - 92 sessions with visitor_id 'unknown' - was adopted as an established measurement at the August 2026 survey, because it could not be re-measured at the time. Re-measured on 16 Oct 2026: no session carries visitor_id 'unknown' any longer. The code can still write the value when a session is created (srv/db_worker.php); R9 therefore remains possible.
This document has been approved since 17 Aug 2026 but rests on the development system. Before the system carries production traffic it has to be re-run against production figures, because two of its central conclusions - the scale assessment under Art. 35 and Art. 37, and the consent-evidence quality under Art. 7(1) - depend on numbers that will change entirely. to be completed: production figures for monitored end users per month, number of customer websites, and the measured proportion of sessions carrying a genuine consent record
5. (a) Systematic Description of the Processing Operations and Purposes
5.1 How the recording works
The customer embeds a static JavaScript file (www/mp.js) on their website. Before anything is recorded, two gates have to open. A bot check runs first and terminates for automated browsers. Behind it stands the consent state machine mpEinw, which permits construction of the tracker only on a positive consent signal - from the customer's own API call, from Google Consent Mode, from a supported CMP, or from a TCF string. Every other outcome, including no signal at all, a CMP that fails to load, a script error and an unknown origin, results in no recording. The governing principle is recorded in the project's own working notes and holds throughout the code: in case of doubt, do not record.
Once permitted, the script opens an encrypted WebSocket (/mp_wss/) to the recording server. TLS encryption terminates in nginx; the WebSocket server itself (srv/server.php) listens only on the loopback address 127.0.0.1:8081 and receives the connection from there unencrypted within the same machine. The server applies the gate a second time, independently of the browser: class/Server.class.php:977-1004 admits only GRANTED or GRANTED_LATE, or an explicit regional exemption, and closes the connection with code 1008 for a withdrawal, an unknown country, and anything unclear. A path exclusion list runs before that, matching on the path only rather than the full URL. If the WebSocket cannot be established three times in a row (a proxy or firewall blocking the handshake), the script sends the same messages by HTTPS POST to /mp_wst instead; that endpoint decides nothing and hands them to the same server process, where they pass through the same gate (www/api/ws_tunnel.php, class/HttpTunnel.class.php). A page snapshot larger than 1 MB is uploaded by the script by HTTPS POST to /mp_dom (www/api/dom_upload.php); that endpoint checks a time-limited access token signed by the server, holds the snapshot in Redis for at most 120 seconds and decides nothing else - it is redeemed by the same connection that has already passed the gates. In addition, /mp_wsfail counts failed WebSocket connections per project, without a visitor identifier; the IP address is used there only as a hash for de-duplication and expires after one hour. Events queue through Redis and are written by a long-running worker (srv/db_worker.php) into MariaDB.
What is stored is not a video. It is the page itself: a serialised copy of the DOM at the moment of the page view, plus every subsequent change to it, replayed later in an iframe with the recorded pointer positions drawn on top. This distinction matters for the risk assessment, because it means the stored material contains whatever the page contained - including text the visitor never typed and never saw as "data".
5.2 Register of processing operations
| Purpose | Role | Principal tables | Erasure (status 23 Sep 2026) |
|---|---|---|---|
| Behaviour recording: movement, clicks, scroll, taps, tab changes | Processor | mouse_events (225,892 rows, 322.6 MB), session_pageviews, visitor_sessions | Daily purge by plan, monitored (see R7) |
| Page content: DOM snapshot and live mutations, to replay the page as seen | Processor | session_pageviews.payload_blob (1,848 rows, 162.9 MB), mouse_events.target_selector for mutations (66,153 rows) | Same purge |
| Form entries, character by character, masked according to the configured level | Processor | mouse_events.target_selector for input events (599 rows) | Same purge; for the 173 legacy rows see R2 |
| Recognition of the same visitor across sessions and page views | Processor | visitor_sessions.visitor_id / custom_user_id, visitor_nicknames (329 rows) | visitor_nicknames: with the visitor's last session |
| Network and coarse location determination | Processor | visitor_sessions.ip_address, country, city, asn_number, asn_org; ip_lifetime_stats (446 rows) | ip_lifetime_stats: 365 days |
| Proof of consent and of its changes (Art. 7(1)) | Processor, held for the customer's burden of proof | consent_events (120 rows), visitor_sessions.consent_* | 730 days from the withdrawal; kept until then on purpose, as it is the evidence that consent was withdrawn |
| Custom events, frustration signals, JavaScript errors | Processor | session_custom_events (823), session_custom_event_params (1,264) | Same purge |
| Derived features and machine-written session narratives | Processor | ai_session_features (4,043), ai_timelines (3,203 rows, 5.5 MB) | With the session, in the same deletion chain |
| Operation of MousePlayer's own customer accounts | Controller | users (4), user_ip_history (5), workspace_users, workspace_invites | With the account; inactive accounts after 24 months and a delivered warning |
| Access, security and change logging of the portal | Controller | mouseplayer_logs (36,409), security_access_logs (1,354), audit_logs (666), banned_ips | 60 days / 72 hours / 60 days / on expiry |
| Support requests | Controller | support_requests | 730 days |
| Collaboration on a replay: comments and share links | Mixed - controller for the author, processor for the recorded content | session_comments (12), session_shares (9) | Comments with the session; share links 90 days after they expire |
| Customer's acceptance of regional recording rules | Controller | consent_region_acceptances (0 rows) | 730 days |
5.3 Legal basis
For everything in the recording path the basis is consent, and the reasoning is documented in the migration that introduced the consent architecture (ai/migrations/018_consent.sql): the recording is behavioural observation within the meaning of Recital 24 and access to the terminal equipment under the Polish implementation of the ePrivacy rules, it requires consent, and legitimate interest does not carry it. All 7 projects are set to consent_mode='required'. The kill switch site_settings.consent_gate_active is on. That analysis is correct and unusually well documented; it is not softened anywhere in the code.
For four operations the code states no basis at all: MousePlayer's own account operation, the portal access and security logs, support requests, and replay comments. These are the controller-role operations. Legitimate interest (Art. 6(1)(f)) and contract performance (Art. 6(1)(b)) are the obvious candidates, but a candidate is not a determination, and Art. 6(1)(f) requires a documented balancing test. Answered on 17 Aug 2026: account and billing data Art. 6(1)(b), for the retention of invoices (c) (five years from the end of the calendar year in which the tax payment deadline fell). Access, security and change logs as well as reCAPTCHA and the server logs: (f), purpose defence against abuse and accountability; the balancing tests are set out in the record under B.2 and B.5. The home-page demonstration: consent, (a). Support requests: (b) where the person asking is a Customer and the request concerns the contract, otherwise (f) (record, B.3). Replay comments and share links are processed by MousePlayer on the Customer's instruction (record, A.9).
5.4 Recipients and third-country transfers
Inside the recording path, no outbound call to any third party was found. Geolocation resolves against local MaxMind database files rather than a web service - a deliberate design decision recorded in the code, so that no third party is contacted before consent exists. Fonts, flags and scripts are served locally. That is a genuinely good result and should be stated as such.
| Recipient | What is transferred | Status today |
|---|---|---|
| The customer | Everything recorded, through the dashboard and the three player endpoints | Active - this is the purpose |
| Any holder of a share link, without any login | A complete replay of one session | Active; status 16 Oct 2026: 5 share links created; every access is logged with time, IP address and browser identifier (share_views) |
| Google Cloud Storage, Warsaw (until 5 September 2026: Google Drive, USA) | A daily complete copy of every database and the entire web tree. Until 11 August 2026 unencrypted. Since 11 August 2026 encrypted on our own server before it is transferred | Active daily. Transfer in clear text ended on 11 August 2026; the unencrypted legacy holdings at Google were deleted on 12 August 2026. Since 5 September 2026 company account, region europe-central2 (Warsaw), covered by the Google Cloud Data Processing Addendum; the copies on the former Drive account were deleted on 6 September 2026 - see Risk R5 |
| Stripe (USA / Ireland) | Customer email, name, billing address, payment data - no visitor data | Test keys only; production keys empty |
| Namecheap Private Email (e-mail sending via SMTP) | Recipient address, subject and content of outgoing e-mails to customers and to the operator, such as account confirmation, password reset, invitations, sign-in notices for new devices, warning before deletion of inactive accounts, notices of changed contract documents, weekly report, support replies, notices about markings and anomalies, and system alerts | Active since 23 Sep 2026, sender office@mouseplayer.com; listed among the sub-processors |
| Telegram (outside the EU) | Until 11 August 2026: support requests in full, including the unshortened IP address and the complete message text. Since 11 August 2026: one fixed sentence only - "MousePlayer: es liegt eine neue Supportanfrage vor." - no variable, no case number, no link; no personal data leaves the system through this channel any more | Channel still active (tokens set in .env), but since 11 August 2026 not a recipient of personal data and therefore not a processor |
| Google reCAPTCHA (USA) | Login, registration, support and billing pages | Disabled on this system (APP_ENV=dev); active in production |
| Google BigQuery (customer's own project) | session_id, visitor_id, custom_user_id, ip_address, country, referrer, user agent, screen size, timestamps, device type | Inactive: disabled on all 7 projects, and the process is not running |
| Customer-defined webhook URL (destination unbounded) | Event data plus a direct player link to the session | Inactive: disabled on all 7 projects, dispatcher process running |
The webhook payload contains a direct player link of the form https://host/player?id=...&session=... Once activated, the destination of that link is chosen by the customer and is not constrained by the system - Slack and Discord formatting is built in, which tells you where such links realistically end up.
5.5 Retention, as measured rather than as intended
There is an automatic deletion routine and it runs. It lives inside the permanently running worker process (srv/db_worker.php), executing at process start and thereafter once per calendar day; the same process clears the periods for sessions marked as bots every hour. The deletion run has no cron entry of its own. Its absence, however, no longer goes unnoticed: the worker records its result, and srv/cron_status.php - a different process - evaluates it, reports a run that lies more than 48 hours back by e-mail, and records the fault as an entry in the Urgent Logs of the administration area. The other scheduled runs are listed in /etc/cron.d/mouseplayer; failures and overlong pauses of those runs are reported by the same status process as well (status 16 Oct 2026).
At the August 2026 survey the run had not yet deleted anything: all projects belonged to an owner on the enterprise tier with 365 days of retention, and the oldest session was 20 days old. As of 16 Oct 2026 it demonstrably deletes: the periods for logs and derivations take effect (the oldest entry in audit_logs sits at the period of 60 days), sessions marked as bots are deleted after 14 days and their recordings emptied after their own period (period in days: 1). The tier period for human sessions has not yet reached any session, because the two projects with genuine visitor traffic still belong to an owner on the enterprise tier with 365 days and their oldest sessions date from 21 Jul 2026.
6. (b) Assessment of the Necessity and Proportionality
Necessity and proportionality are assessed against the stated purpose: enabling a website operator to understand how visitors use their site, where they struggle, and why they abandon it. That purpose is legitimate and the general technique is capable of serving it. The question Art. 35(7)(b) asks is narrower and harder: is this processing, at this level of detail, necessary for that purpose - and is the intrusion proportionate to the benefit?
| Component | Necessary for the purpose? | Proportionate as built? |
|---|---|---|
| Pointer movement, clicks, scrolling, taps | Yes. This is the signal the purpose is about. | Yes. |
| Page structure and layout of the visited page | Yes. Coordinates are meaningless without the layout they refer to. | Yes in principle. |
| Full page text content, verbatim | No - not in general. Understanding where a visitor struggled requires the structure and the labels, not the personal content rendered into it. | No, as currently built. Masking must be requested per element; the default is to store everything. See R1. |
| Form entries | Arguable. Knowing that a field was abandoned is valuable; knowing what was typed into it is rarely necessary. | Partly. The masking floor is strong and all projects run the strictest level - but the design still permits weaker levels, and the labelling of that setting is inverted (level 0 is the strictest, the dashboard hint says the opposite). |
| Checkbox and radio state | Arguable. | No. These are never masked at any level - and a checkbox can carry a health declaration, a political affiliation or a consent to a sensitive purpose. |
| Cross-session recognition | Yes for returning-visitor analysis; not for the core purpose. | Conditionally. Pseudonymous recognition is proportionate; the customer-supplied identifier is where proportionality ends. See R6. |
| IP address | Yes for coarse location attribution (country; the city only from the already truncated address) and abuse defence. | Yes, forward-looking: the address is truncated immediately after it is read, before any consumer sees it. The legacy data in the recording path was cleared on 17 Aug 2026; see R3. |
| Scoring and session narratives | No. These are conveniences for the customer's review workflow. | Questionable, and the least examined part of the system - no legal basis is stated for it anywhere in the code. |
| Full off-site copy (to a third-country provider until 5 September 2026) | An off-site backup yes - it serves availability (Art. 32(1)(c)). Its scope goes beyond this service: it contains every database on the machine. | Since 5 September 2026 yes, with a reservation on scope. The copy is encrypted before transfer and held in a European region under an agreement under Art. 28. Until 5 September 2026 no: no established contract, and unencrypted until 11 August 2026. See R5. |
On data minimisation (Art. 5(1)(c)), the system shows two opposite faces. Deliberate minimisation is visible in several places and is well argued in the code: field names are classified rather than transmitted, because a raw field name in a shop is often descriptive enough to identify the person; the TCF string is stored only as a SHA-256 hash; the replay payload is deliberately excluded from the public API. Against that stands the central design decision to store the entire page verbatim by default, which is the opposite of minimisation and is not compensated by any of the above.
On storage limitation (Art. 5(1)(e)), the assessment was unambiguous and unfavourable until 17 Aug 2026. Thirteen tables holding personal data had no erasure period of any kind, including the two that hold the most legible material: ai_timelines, which contains each session rendered as readable prose, and visitor_nicknames. The routine that did exist had never run against real data. A retention concept that has never deleted a record is a plan, not a safeguard. Since 17 Aug 2026 all thirteen tables have a period, the purge demonstrably deletes, and its absence is reported (see R7).
Proportionality verdict: the processing is capable of being proportionate, and several components already are. It is not proportionate as currently configured, for one specific reason that is fixable: verbatim storage of page content by default. Two further reasons have fallen away. The absence of enforced retention periods for thirteen tables has been remedied since 17 Aug 2026 (see R7). And until 5 September 2026 the daily export went to a third-country provider with no established legal basis for the transfer - unencrypted until 11 August 2026 -, and since 5 September 2026 it is held encrypted in a European region under an agreement under Art. 28 (see R5).
7. (c) Assessment of the Risks to the Rights and Freedoms of Data Subjects
Risks are assessed for the data subject, not for the business. Likelihood is rated Remote, Possible, Likely, or Materialised - the last meaning that the event is not a forecast but a present, measured state of the data. Severity is rated Limited, Significant, Severe or Very severe, judged on what the affected person stands to lose: not embarrassment, but exposure of health, financial or other protected characteristics, loss of control over identification, and inability to exercise a right.
| ID | Risk | Likelihood | Severity | Level |
|---|---|---|---|---|
| R1 | Special categories of data inside the recorded page content | Likely | Very severe | Very high |
| R2 | Unmasked form entries in legacy data (173 rows) - rating from the survey; the rows were deleted on 17 Aug 2026, a re-rating by the controller is outstanding | Materialised | Significant | High |
| R3 | Unshortened IP addresses in legacy data and portal logs - rating from the survey; the legacy holdings were shortened on 17 Aug 2026 and the portal logs have carried periods since, a re-rating is outstanding | Materialised | Significant | High |
| R4 | Share links expose a complete replay without any login | Possible | Severe | High |
| R5 | Daily full off-site copy - unencrypted until 11 August 2026, encrypted on our own server before transfer since 11 August 2026; the unencrypted legacy holdings were deleted at the provider on 12 August 2026. Since 5 September 2026 the copy is held in a Google Cloud Storage bucket in region europe-central2 (Warsaw) under a company account, covered by the Google Cloud Data Processing Addendum; the copies on the former Drive account were deleted on 6 September 2026. The scope is unchanged. If both key files are lost, the backup is irrecoverable; printed copies are kept in a safe | Remote (re-rated on 17 September 2026). Before that: transfer in clear text until 11 August 2026, transfer without an established legal basis until 5 September 2026 - both materialised and ended | Very severe | Medium - re-rated on 17 September 2026; very high until 5 September 2026. The condition from the assessment (removal of the old scripts) was met on 16 October 2026; the re-rating of R5 lies with the controller |
| R6 | Cross-session recognition and merging with the customer's own records | Likely | Severe | High |
| R7 | No erasure period for thirteen tables; remedied since 17 Aug 2026, the purge runs daily and is monitored | Remote (re-rated on 23 Sep 2026). Until 17 Aug 2026: materialised | Severe | Low - re-rated on 23 Sep 2026; high until 17 Aug 2026 |
| R8 | Automated scoring and classification of every session | Likely | Significant | Medium |
| R9 | 92 sessions unreachable by any identifier; no route from person to record - rating from the survey; status 16 Oct 2026 no such session, self-service lookup since 17 Aug 2026, a re-rating is outstanding | Materialised | Significant | High |
| R10 | No encryption at rest, no TLS to the database, Redis without access protection (until 16 Aug 2026; access-restricted since then, database and Redis additionally reachable on the local network - a re-rating is outstanding) | Possible | Very severe | Medium-high |
| R11 | No record of who viewed which recording | Materialised | Significant | Medium-high |
| R12 | Consent evidence quality; untested CMP adapters | Materialised (dev data) | Severe | Medium |
| R13 | Support requests to Telegram including the unshortened IP - until 11 August 2026; since 11 August 2026 only a fixed notice text without any variable is sent. What remains is the local storage of the request with email address and IP in support_requests; since 17 Aug 2026 with a period of 730 days (see R7) | Transfer: remedied on 11 August 2026. Local storage: materialised | Significant - until 11 August 2026 for the transfer, since then for the local storage only | Remedied on 11 August 2026 as regards the transfer; the remaining local storage is tracked under R7 (rated low there since 23 Sep 2026) |
| R14 | Egress to recipients the controller chooses themselves — an erasure request does not reach them | Possible (depends on whether the controller switches a bridge on; in the current data: none switched on) | Substantial | Medium |
R1 - Special categories of data inside the recorded page content
This is the most serious risk in the system and the least visible one, because nothing about it looks like a defect. Masking of the page snapshot operates inside marked regions only (www/mp.js:3934-3970): within an element carrying data-mp-mask or the class .mp-masked, every non-whitespace character is replaced and attributes outside a keep-list are set to [FILTERED]. Everything else is stored verbatim. The masking is therefore an opt-in annotation that the customer has to apply, element by element, to a page they may not fully control.
The consequence is that the protection covers what the visitor types and not what the page says back. A confirmation page from a medical practice showing an appointment with a named specialism; a pharmacy basket; an insurance quote listing pre-existing conditions; a bank statement; a legal aid intake summary - all of these are page output, outside any input field, and all of them land in payload_blob unaltered. This is not hypothetical: 1,848 rows are populated, averaging 90.3 kB, and a sample confirms the stored value begins with the complete <html> document. The same applies to every later change to the page, serialised as HTML fragments into mouse_events.target_selector across 66,153 mutation rows.
Likelihood - Likely. The protective mechanism requires deliberate action by someone other than the person at risk, defaults to storing everything, and has no detection, no warning and no report telling a customer that unmasked personal content is being captured. Nothing in the system samples the stored payload for the patterns that would indicate it. The probability that at least one customer page renders special-category data is close to one over any meaningful period of operation.
Severity - Very severe. Art. 9(1) data attracts the highest protection, and none of the Art. 9(2) exceptions is available here. The visitor's consent to session recording is not explicit consent to the processing of health data, and it could not be - neither the visitor, nor the customer, nor MousePlayer knows at the time that such data is being captured. The material is stored verbatim, is fully replayable, is exportable, is included in the daily off-site copy (R5), and can be handed to an unidentified third party by a share link (R4).
R1 = Very high. This risk cannot be closed by better masking rules alone, because the system cannot know what a customer's page outputs. It can only be reduced structurally: by inverting the default so that text content is masked unless explicitly released, by excluding sectors contractually, and by making the capture of page text an opt-in with a written acknowledgement from the customer.
R2 - Unmasked form entries in legacy data
173 rows in mouse_events contain form input values in clear text. They were located with a pattern match on the stored structure ('"v":"' followed by a character other than the masking character), fall in the window 21 to 27 July 2026, and include entries such as a person's full name. Rows written on 10 August 2026 are masked, so the live behaviour is now correct: all 7 projects run smart_masking=0, which despite its labelling is the strictest setting and replaces every character of every field, and an unconditional floor replaces passwords and payment data at any setting.
Likelihood - Materialised. This is not a prediction. The rows exist now, and because mouse_events is only cleared by the tier purge - which deletes nothing before a record is 365 days old, and has never executed - they will remain until at least July 2027 unless removed by hand. Forward-looking likelihood of recurrence is low.
Severity - Significant. The known samples are innocuous, but the content of the remaining rows has not been classified, and the field categories the system recognises include email, phone, payment, address and name. Whether the unconditional password and payment floor was already present during that window has not been established. Completed on 17 Aug 2026: the rows were deleted without being reviewed (decision of the controller). Measured, there were 174 rows from 18 sessions of a single project, 21 Jul to 6 Aug 2026; after the run 0 remain. The gap itself had already been closed: since 7 Aug 2026 no value carries plain text. Almost a second error in the measurement: the masking character is the asterisk, not the bullet - a pattern on the bullet found 862 rows with a most recent hit from today and thus looked like an ongoing leak. A deletion on that number would have taken 688 properly masked rows with it.
R3 - Unshortened IP addresses in legacy data and portal logs
IP truncation in the recording path is implemented correctly and at the right place: class/Server.class.php:432-435 replaces the final IPv4 octet or the final IPv6 group immediately after the address is read, before any consumer of the value exists. It is verified effective for new data - every session recorded since 7 August 2026 is truncated. It is not retroactive, and no backfill routine exists; the legacy data was instead cleared once, by hand, on 17 August 2026 - see the status note below the table.
| Table | Unshortened addresses (state at assessment, before 17 Aug 2026) | Erasure period |
|---|---|---|
| visitor_sessions | 236 of 705, dated 21 July to 7 August 2026, complete public addresses | Tier purge - never executed |
| ip_lifetime_stats | 291 of 446 | None anywhere |
| mouseplayer_logs | None of 36,542 addresses checked was shortened | 60 days |
| audit_logs | All 666 rows | None |
| user_ip_history | All 5 rows | None |
| security_access_logs | 1,004 of 1,354 | 72 hours |
| support_requests | All, and forwarded onward - see R13 | None |
| Apache access log | All | 14 daily rotations |
Likelihood - Materialised. Severity - Significant. An IP address alone is a limited exposure. In visitor_sessions it is not alone: it sits in the same row as the recording, so it connects a network identity, and through it an individual, to a complete replay of that person's behaviour - including anything caught by R1. The portal logs are less acute but far larger, and they are the controller-role processing where MousePlayer answers for the choice directly.
Completed on 17 Aug 2026: the legacy addresses in audit_logs (681) and user_ip_history (7) were truncated retroactively, in the same form the recorder writes anyway; 0 remain. The future periods have been in place since the same day. (decision of the controller, 17 Aug 2026)
Status 17 Aug 2026. The legacy data in the recording path is settled: measured, 267 of 267 addresses in ip_lifetime_stats were truncated and 881 of 901 in visitor_sessions; the remaining 20 carry 0.0.0.0 and come from the form-analysis test data, so they are not visitor addresses. In the portal logs the full address remains - deliberately so, and since 17 Aug 2026 with a stated period: mouseplayer_logs 60 days, security_access_logs 72 hours, audit_logs 60 days, user_ip_history 90 days from the last time an address was seen (since 24 Sep 2026), support_requests 730 days. Why the full address is needed for support requests is set out in the Art. 30 record under B.3.
R4 - Share links expose a complete replay without any login
A rewrite rule maps /s/<40 hex characters> straight to the player. The token is the entire barrier, and the design acknowledges that honestly: the token is 20 bytes from random_bytes (160 bits, not guessable), the channel and session identifiers come exclusively from the token rather than from the request, the remaining query string is replaced rather than ignored, the role stays NULL so that deletion and re-sharing are impossible server-side, all three player endpoints are constrained to the token's session, and invented, expired and revoked tokens produce an identical response so that probing yields nothing. The default lifetime is 7 days, minimum 1, maximum 90; revocation exists. This is a carefully built mechanism.
The residual exposure is not cryptographic but human, and two implementation details widen it. First, the page that shows the recording via a share link (player.php in share mode) does carry the headers Referrer-Policy: no-referrer and Cache-Control: no-store, but no X-Robots-Tag header; noindex appears only in www/share.php, which is now only the error page, and robots.txt permits everything (checked on 16 Oct 2026). Second, since 17 Aug 2026 the system records every access to a share link - time, unshortened IP address and browser identifier in share_views (see also R11) -, but the person behind the access cannot be determined from that.
Likelihood - Possible. The token will not be brute-forced. It will be forwarded - pasted into a ticket, a group chat, an email thread that grows a recipient - and it may leak through a Referer header. Severity - Severe: one link exposes an entire session to an unlimited, unidentified audience, including anything covered by R1 and R2, and afterwards only the address and browser from which it was accessed can be established (share_views), not who saw it. A related question is recorded as open in the project's own notes and remains unverified: Measured on 17 Aug 2026: the viewer limit is not bypassed by share links - it does not apply to them at all. Live viewers are counted through the IS_LIVE_VIEWER flag on the WebSocket connection (class/Server.class.php, zaehleLiveViewers). www/player.php contains no WebSocket code at all; the player only replays. A share link leads into the player, not into the live view. What is genuinely unlimited is something else: how many people open the same link at the same time. There is no limit for that and, unlike the live view, no counter either. Since 17 Aug 2026 at least every access is logged (share_views, record B.6), so that it can afterwards be established how often and from where a link was used.
R5 - Daily full off-site copy (to Google Drive until 5 September 2026, to Google Cloud Storage in Warsaw since then; transferred encrypted since 11 August 2026; legacy holdings deleted on 12 August 2026)
This risk is not present in the technical fact base; it was established from the project's own open-items file and is documented there in detail. Until 11 August 2026 an earlier backup script ran from root's cron at 02:00 each day. It executed mysqldump across all databases and tarred the entire web tree, then uploaded the result via rclone to gdrive:backups/rpi-ls5/daily - with no encryption at any point. Locally the output is confirmed at 653 MB of database and 678 MB of files per day. Since 11 August 2026 the cron line has been switched: the daily run is a new backup script kept in the project, which captures the same scope but encrypts it on our own server before transfer (rclone crypt) and writes into a separate destination folder of its own; it is scheduled for 03:00. File and directory names are encrypted as well, so what lies at the provider is blocks without meaning. The run of 11 August 2026 was started by hand and arrived (733 MB of database, 678 MB of files); the run of 12 August 2026 completed on schedule at 03:00. Both are logged, and the check at the destination found the database dump there with the same byte count on both occasions.
What leaves the machine is therefore not a backup of session recordings. It is a complete copy of every database and every system on the host - including the recordings with their DOM snapshots, the 173 clear-text form entries, the 236 unshortened addresses, and every .env file with its production secrets. The scope is unchanged; encryption does nothing about that. What has changed is readability at the provider: until 11 August 2026 there was no encryption layer in front of the upload and the plaintext arrived as plaintext; since 11 August 2026 encryption happens on our own server before anything goes out, and the keys sit in two files with mode 600 on that same server - Google does not have them. The legacy holdings were not covered by this: the new script cleans only its own destination folder, and the previous folder kept weekly copies for up to 364 days. It was deleted on 12 August 2026; unencrypted full copies from before 11 August 2026 therefore no longer lie at the provider.
- The transfer is real and daily. Until 11 August 2026 it was unverified whether it arrived: the run used root's rclone configuration, the script logged nothing, and cron discarded its output. Since 11 August 2026 the run writes a log and checks for itself after the transfer whether the database dump stands at the destination with the expected byte count; for 11 August 2026 (by hand) and 12 August 2026 (scheduled, at 03:00) both are on record, each with a passed check. Daily operation is therefore evidenced, not merely the first attempt. The availability risk is not settled by this but moved: if both key files are lost, the entire off-site backup is irrecoverable. A measure that removes readability at the provider thereby creates a new dependency on custody of the keys; outside this machine they are, according to the controller, kept printed in a safe (section 8.3). The operator has tested decryption - that is his statement, not a measurement made by this document.
- Until 5 September 2026 no transfer instrument was established. The copy was held at Google Drive under a private account (answered on 17 Aug 2026), with no bookable European data region and no processor agreement. Since 5 September 2026 it has been held in a Google Cloud Storage bucket in region europe-central2 (Warsaw) under a company account and is covered by the Google Cloud Data Processing Addendum; the copies on the Drive account were deleted on 6 September 2026.
- The old script was a privilege escalation. Until 11 August 2026 it was owned by root, executed by root's cron, and its file mode was 777: anyone holding the pi SSH account had root at 02:00. Since 11 August 2026 the daily run no longer goes through that script; the transfer to Google Drive was removed from it on 9 September 2026. The old backup scripts were removed on 16 October 2026. The re-rating of R5 lies with the controller.
- Until 5 September 2026 the copy was outside any customer instruction. Art. 28(3)(a) requires the processor to act only on documented instructions; a daily copy of the customer's visitor recordings to a US consumer cloud service was not covered by any instruction on record. Since then section 9 of the data processing agreement and the list of sub-processors name the backup at Google Cloud Storage.
R5 = Medium (re-rated on 17 September 2026). Likelihood Remote, severity Very severe. Until 5 September 2026 R5 stood at very high: the exposure was total (every record, every day), the transfer went out in clear text until 11 August 2026, and until 5 September 2026 it had no established legal basis. Both have ended - the clear-text transfer on 11 August 2026, the unencrypted legacy holdings on 12 August 2026, the transfer without a contract on 5 September 2026. Likelihood therefore falls from materialised to remote. Google receives the copy only in encrypted form, file and directory names included, and does not hold the keys; it is kept in a European region under the Google Cloud Data Processing Addendum, and section 9 of the data processing agreement and the sub-processor list both name it. Disclosure would require the keys to be lost at the same time - from this machine or from the safe; whoever takes over this machine, however, has the holdings in clear text in front of them anyway (R10). Severity remains very severe, because the scope has not shrunk: the copy still contains every database on the machine and every .env file. Medium rather than low was the rating of 17 September 2026 because of the two old backup scripts that were still on the machine at the time. The condition from the assessment (removal of the old scripts) was met on 16 October 2026; the re-rating of R5 lies with the controller. Availability is secured: the keys are additionally kept printed in the safe, and the restore drill from the new destination passed before the Drive copies were deleted.
Superseded since 5 September 2026 (see above); kept here as history. Answered on 17 Aug 2026: private account. No European data region can therefore be booked and there is no data processing agreement - for a private Google account the data processing terms are not available. Decision of the controller: the backup stays there for now. Restore tests monthly from now on. The two key files are kept printed in a safe accessible only to the controller (statement of the controller, 17 Aug 2026); they therefore survive the loss of this machine.
Measured on 17 September 2026: the last successful backup ran at 03:30 that day and passed the check at the destination. The restore drill is planned monthly; it passed before the Drive copies were deleted on 6 September 2026, and no watchdog reminds of the next one. On 7 October 2026 a complete data restore from the cloud backup into a separate environment was additionally carried out (about 70 minutes; samples from the archive and the database without errors). Since 6 October 2026 the daily backup also contains the recording archive (/mnt/mouseplayer-dev-archive); it sits on the same disk as the system. Unless noted otherwise there, the list above describes the state before 5 September 2026.
R6 - Cross-session recognition and merging with the customer's own records
A pseudonymous identifier is created and held in the visitor's localStorage as mp_visitor_id. On its own this is ordinary and proportionate for returning-visitor analysis. Two further mechanisms change its character. MousePlayer.setUserId() lets the customer attach their own identifier - a login name, a customer number - to the recording, stored in visitor_sessions.custom_user_id with a dedicated index; and the same value is picked up automatically from a dataLayer path named 'uid', without any deliberate act by the customer. Where a tag manager already publishes a user id, the recordings become attributable to a named individual as a side effect of installation.
From that point the recording is no longer pseudonymous in the customer's hands. It can be joined to their CRM, and the BigQuery export is built to carry exactly the columns that make the join trivial: visitor_id, custom_user_id and ip_address in the same row. That export is currently disabled on all projects and its process is not running - the capability exists, the activity does not.
One design weakness should be recorded without overstating it. visitor_nicknames carries a UNIQUE constraint on visitor_id alone, with no tenant boundary - the table is structurally cross-project. In practice the identifier lives in localStorage and is therefore scoped to one origin, so this does not by itself create cross-site tracking between different customers' domains. It is a missing boundary in the schema rather than a demonstrated leak, and it should be closed before any deployment where two projects can share an origin.
Likelihood - Likely for cross-session recognition (it is default behaviour, and 329 identifiers are recorded); Possible to Likely for identification, given the automatic dataLayer path. Severity - Severe: identification removes the mitigating factor that makes behavioural recording tolerable, and it does so retrospectively - once an identifier is attached, previously pseudonymous sessions for that visitor become attributable too.
R7 - No erasure period for thirteen tables; periods set on 17 Aug 2026
The tier-aware purge deletes five tables: mouse_events, session_custom_event_params, session_custom_events, session_pageviews and visitor_sessions. Thirteen tables holding personal data are absent from that list: ai_session_features, ai_timelines, consent_events, session_comments, session_shares, visitor_nicknames, ip_lifetime_stats, audit_logs, user_ip_history, users including last_ip, support_requests, consent_region_acceptances and heatmap_page_stats.
The practical consequence is worse than the list suggests, because of the order in which things would happen. When the purge eventually does fire, the session disappears and the derivatives remain - ai_timelines would retain the session as readable prose, 3,203 rows and 5.5 MB of it, describing a person whose underlying record has been deleted. Today there are no such orphans, but only because nothing has ever been deleted.
Three further defects compound it. The routine is not scheduled by cron or systemd but runs inside the worker process, so if that process stops, erasure stops and nothing reports it. Its selection query joins sessions to projects, workspaces, owners and users with inner joins, so a session whose project was deleted can never be selected and will never be erased - one of 706 sessions is already in that state. And the retention actually in force is 365 days for every project on the system, against a data set whose oldest record is 20 days old, which means the deletion chain has never been exercised end to end.
Likelihood - Materialised. Severity - Severe, as a breach of Art. 5(1)(e) that is structural rather than accidental. Changed on 17 Aug 2026. The run itself stays in the db_worker, but its ABSENCE is now noticed: the worker records its result (mp_purge_status) and srv/cron_status.php - a DIFFERENT process - evaluates it every 60 seconds, writes sys_purge for the admin area and reports by e-mail when the last run is more than 48 hours old. At most one message per day; a watchdog that mails every minute is ignored and then reports the real failure to nobody either. The watchdog deliberately does not sit in the worker: a dead process cannot report itself. Status 23 Sep 2026: outgoing e-mail is in place; the message goes to the alarm address set in the admin area.
Status 17 Aug 2026. Resolved. Five of the thirteen tables have been part of the shared deletion chain since 12 Aug 2026, six were given a period of their own, configurable in the admin area (migration 039), and two were missing from the list altogether: ai_visitor_value and ai_insights - precisely the scoring of the person and the text generated about them. The periods live in site_settings and are read from the same row the Art. 30 record reads them from for its own text. The purge does run; the run of 17 Aug 2026 at 17:37:09 is on record in the worker log. The concern of this section - the session disappears and the derivation is left behind as an orphan - can therefore no longer occur. The run still hangs off the worker process and is scheduled by neither cron nor systemd; since 17 Aug 2026 its absence is reported by the watchdog in the status process, and since 23 Sep 2026 also by e-mail.
Re-rated on 23 Sep 2026. Measured the same day: the purge last ran 21 hours earlier, the oldest row in audit_logs is 60 days old - exactly the configured period -, and no session is without a project. The selection still uses inner joins; orphaned sessions nevertheless do not arise, because deleting a project or an account takes its sessions along by channelid. Since 24 Sep 2026 user_ip_history has a period of its own as well: 90 days from the last time an address was seen, at the latest with the account. Likelihood - Remote. Severity - Severe. Overall level - low.
R8 - Automated scoring and classification of every session
Every session is evaluated automatically. engagement_score (0-255) is computed from depth signals and irritation signals; session_quality separates sessions where little happened; is_bot with a reason code classifies the visitor as non-human, and that check runs before the consent gate. The scores are indexed and drive the ordering of the customer's review list, which means they determine which individuals get watched. At the August 2026 survey 4,043 feature rows and 3,203 generated narratives existed.
This is profiling within Art. 4(11) - automated evaluation of personal aspects concerning a natural person, specifically to analyse or predict their behaviour. It is not, on the evidence, automated decision-making under Art. 22: no credit, pricing, access or eligibility decision against a visitor was found. One mechanism sits closer to the line than the rest, and should be named: on detecting a rage click, www/mp.js pushes a behavioural verdict into third-party chat widgets running in the visitor's own browser - Intercom receives a flag, LiveChat a session variable. A judgement formed about the visitor thereby leaves the system in real time and can change how that person is treated on the site.
Scope of the profiling, status 16 Oct 2026. Since the survey further scores and derivations have been added, and all of them belong to this risk: machine-formulated sentences and reports about individual visitors (ai_insights, ai_besucher_bericht; the findings are computed, a language model on our own machines in the same local network phrases them); an exit model (logistic regression) that estimates an exit risk in the visitor's browser and, where the operator switches it on, triggers an exit offer whose text is shown only after approval by the operator; a rule-based churn indicator for returning visitors (ai_churn); behaviour profiles that group sessions by their course (k-means, mp_profile, mp_profil_sitzung), and sequence patterns; a friction score per session; surveys and a feedback button with the visitor's answers; element tracking, which assigns clicks to marked elements; an index of displayed texts for search (mp_text_block, mp_text_pv, 30 days); and console messages as well as failed or slow requests of the customer page (without body and headers). On the evidence none of these takes a decision within Art. 22 against the visitor; the exit offer, however, directly changes what the visitor is shown on the page, and of those listed it therefore sits closest to the line. The details are set out in the record under Art. 30.
Separately, two automated consequences do have real effect, but against customers and IP addresses rather than visitors: an account automatically flagged is_suspicious at registration has its data retention cut to 24 hours, and an IP exceeding the connection thresholds is banned, after which every further request is terminated - at the August 2026 survey 24 such bans were recorded. At the survey neither had a documented human-review route or objection channel; for the ban one has existed since 17 Aug 2026 (below), for the is_suspicious flag there is still none.
Likelihood - Likely (default behaviour). Severity - Significant: no legal effect for visitors, but being scored and ranked without knowing it, and having that score exported to a third-party widget, is precisely the loss of control Recital 75 describes. Assessed on 17 Aug 2026. Since that day the automatic ban expired by itself after 7 days; before that it never expired. As of 16 Oct 2026 the WebSocket server blocks by itself and the block expires after 24 h (configurable from 1 to 168 h); fail2ban blocks for one hour; banned_ips is only filled manually. An objection goes through the support form or to office@mouseplayer.com and is handled by a human; the admin area can lift the ban. The duration lives in site_settings (autoban_stunden). The measure therefore has a limited effect, human review on request and a named route to it - the features Art. 22 turns on. The classification itself remains a legal question; what is decided is the safeguard, not the interpretation. (decision of the controller, 17 Aug 2026)
R9 - 92 sessions unreachable by any identifier; no route from a person to their record
92 sessions carry visitor_id 'unknown'. For those records there is no key of any kind that leads to them: the data exists, is personal, contains a replay and possibly an address, and cannot be retrieved by any query a data subject request could trigger. Erasure under Art. 17 cannot be delivered for them, and access under Art. 15 cannot be answered. Re-measured on 16 Oct 2026: no session carries visitor_id 'unknown' any longer; the code can, however, still write the value when a session is created.
The wider problem is worse than the 92 rows and is already recorded as unresolved in the project's own notes. Even where a visitor_id exists, it lives in the visitor's localStorage, and the human being does not know it. There is no route from a real person - who can offer a name, an email address, a date and a page - to their records. An erasure button that cannot be reached by the person entitled to press it is a prop.
Art. 11 offers a partial and uncomfortable answer: where the controller can demonstrate that it is not in a position to identify the data subject, Arts. 15 to 20 do not apply - but Art. 11(2) requires the controller to inform the data subject of that, and the data subject may supply additional information enabling identification. That last clause is the point: the visitor can supply their identifier, because it is sitting in their own browser. A self-service page that displays a visitor their own mp_visitor_id would convert an impossible request into a routine one, which makes reliance on Art. 11 hard to defend while no such page exists.
Likelihood - Materialised. Severity - Significant, rising to Severe in combination with R1: the person least able to reach their record is the person who does not know a record exists. Decided on 17 Aug 2026: a self-disclosure for visitors will be set up - a page on which a visitor sees their own identifier and can trigger deletion of their recordings, linked from the privacy policy. (decision of the controller, 17 Aug 2026) Live since 17 Aug 2026 at /my-data (German /meine-daten, Polish /moje-dane), linked from the footer of every page. It shows the number of sessions, the period and the domains concerned — no recordings: the identifier is a bearer token, and anyone knowing it could otherwise view someone else's behaviour. Deletion is queued and carried out by the worker using the existing chain; the page itself does not delete.
How far this defuses R9 depends on where the visitor was recorded, and that limit is technical: browser storage is separated per origin. The identifier sits in the storage of the website the script ran on, not in ours. A page on mouseplayer.com therefore cannot see it on its own — verified against the data, where no identifier occurs on more than one domain. Three ways in follow from this:
- Visitors of mouseplayer.com itself reach their data directly, because here the browser storage is ours. For them R9 is removed.
- Visitors of a customer website reach it through a link the customer places in their own privacy notice;
MousePlayer.privacyUrl()returns it ready-made, identifier included. This is not an evasion but the allocation of roles under Art. 28: towards their own visitors the customer is the controller, and the Art. 13 notice belongs in their policy anyway. We provide the tool and are the recipient. - Anyone who already knows their identifier enters it on the page.
Where a customer does not place the link, R9 remains in full force for that customer’s visitors — the request is then not impossible, but it runs through the customer rather than through us, and for that case the Art. 11 statement stands in the privacy policy. Whoever cleared their browser storage is reachable by none of the three routes. The page therefore does not remove R9; it moves it from "technically impossible to fulfil" to "fulfillable, provided the controller signposts the way".
R10 - No encryption at rest, no TLS to the database or to Redis across the LAN
Three infrastructure gaps are documented as not implemented, and they were verified rather than assumed. InnoDB table, log and temporary-table encryption are all off and the disk is not encrypted, so the entire corpus - 322.6 MB of events and 162.9 MB of page snapshots - is at rest in plaintext. The database connection runs without TLS: the server supports it, but no certificate, key or CA is configured; the connection is local, which mitigates but does not remove the point. Until 16 Aug 2026 Redis ran without access protection while persisting to disk, and its queues hold the truncated IP, the full URL and the DOM payload in transit.
Likelihood - Possible. Remote exploitation is unlikely: at the survey Redis and the database were bound to localhost only (for today's state see below), HTTPS is enforced, and the WebSocket's TLS connection terminates with a real certificate in nginx. The realistic paths are physical - theft of the machine, disposal of the disk - and the backup path in R5, which carried the same plaintext off the machine every night until 11 August 2026 and has transferred encrypted since; the unencrypted copies from before that date were deleted at the provider on 12 August 2026. Severity - Very severe if realised, because what is exposed is everything at once.
Answered on 17 Aug 2026: the server stands in a locked room under video surveillance in Lodz; access is held solely by the controller. The USB backup disk sits in the same room as the server. Answered on 17 Aug 2026: encryption at rest will not be introduced before production - not feasible in the initial phase on the Raspberry Pi (decision of the controller). This risk therefore remains in full: the entire holding lies in the clear on the disk, and the USB backup disk sits in the same room. The remaining safeguard is physical - a locked room under video surveillance, access held only by the controller - and holds against theft or improper disposal of a disk only as far as that room holds. To be reassessed as soon as operations leave the Raspberry Pi.
Status 17 Aug 2026. One part is resolved, another has shifted. Redis has been access-restricted since 16 Aug 2026; checked against the service, it refuses requests without authentication. Encryption at rest is still absent. The mitigating assumption of this section, however, no longer holds: since 16 Aug 2026 Redis and the database no longer listen on localhost only but additionally on the LAN address (bind-address = 127.0.0.1,192.168.20.2), so that a second node can reach the shared state; the ssl- lines of the server configuration remain commented out. While a single node runs this stays theoretical. With the second, session content and database traffic travel across the network in the clear, and the routes are then no longer only physical. The rating of this risk is to be reassessed before a second node goes into service.
Status 16 Oct 2026. Connections from the internet are TLS-encrypted; encryption terminates in nginx, and the WebSocket server listens only on 127.0.0.1:8081, i.e. on the same machine. MousePlayer uses its own Redis instance on 127.0.0.1:6381 with access restriction and persistence to disk (append-only file); queue contents and the unshortened IP addresses held for the duration of the automatic ban (24 h) therefore also reach the disk. An older, shared Redis instance additionally listens, access-restricted, on the LAN address. MariaDB listens on 127.0.0.1 and on the LAN address 192.168.20.2 without TLS (no certificate configured), and there are connections from the local network. Within our own local network at the same location, database and Redis connections therefore run unencrypted; they are access-restricted and reachable only from our own local network (no port forwarding). Encryption at rest is still absent.
R11 - No record of who viewed which recording
The audit log records writing actions - share creation and revocation, comment deletion, session and visitor deletion, automatic bans, superadmin actions, 666 entries in total at the survey. It does not record reading. A call to the player page, to the event endpoint or to the theatre endpoint leaves no trace. Nobody can establish afterwards which employee of a customer watched a named individual's session, how often, or whether a MousePlayer administrator did. Since 17 Aug 2026 at least accesses to share links are logged (share_views); calls by signed-in users leave no trace (status 16 Oct 2026).
Likelihood - Materialised as a control gap. Severity - Significant, with a compounding effect that matters more than the direct one: after any incident, Art. 33 and Art. 34 require the controller to describe the categories and approximate number of data subjects and records concerned. Without read logging, that description cannot be produced from evidence, and every notification becomes an estimate.
R12 - Consent evidence quality and untested CMP adapters
Art. 7(1) places the burden of proving consent on the controller, and the code says so explicitly as the reason the consent log exists. On the measured data of the survey (August 2026), 671 of 706 sessions carry consent_source='testdata' with a NULL timestamp, written by a development fixture - these are not evidence of anything. Genuine records then numbered 35: 27 from the API, 5 from Google Consent Mode, 2 late grants, 1 withdrawal. As of 16 Oct 2026, 566 of 8,648 human sessions carry consent_source='testdata'; the others carry a record from the API (7,451), from the Consented adapter (620) or from Google Consent Mode (9). Deletion on withdrawal exists as an option and is switched off on all projects (status 16 Oct 2026), so a withdrawal stops future recording but leaves past recordings in place.
Of the six CMP adapters and the three consent detection layers, only three paths have been exercised against reality: the direct API, Google Consent Mode, and Cookiebot. TCF and five adapters - Usercentrics, OneTrust, Consentmanager, Borlabs, Complianz - are built but have never run against a live CMP. Given the governing fail-closed principle, an adapter that misreads a signal fails towards not recording, which is the safe direction; the risk is therefore mainly one of unverified evidence rather than unlawful recording.
Likelihood - Materialised for this data set. Severity - Severe in production terms, because a consent that cannot be evidenced is, in a dispute, a consent that did not exist. This rating applies to the development data and must be re-measured. to be completed: the measured proportion of production sessions carrying a genuine consent record, and the date each CMP adapter was verified against a live installation
R13 - Support requests forwarded to Telegram with the unshortened IP (transfer discontinued on 11 August 2026)
Until 11 August 2026, when a support request was submitted the entire matter was pushed to Telegram: account status, email address, subject, the complete message text, the source URL, and the IP address unshortened. The tokens were set, so the branch was live. The requester is frequently not a customer, and support text is unbounded - people describe health situations, disputes and financial circumstances in support tickets because that is what the box invites. Since 11 August 2026 cfg/lib.php sends exactly one fixed string: "MousePlayer: es liegt eine neue Supportanfrage vor." Not a single variable reaches the message, there is no parse_mode, and deliberately no case number - that would be an identifier relating to a person - and no link built from the requested host name, because that value comes from the request itself. A guard (ai/pruefstand/test_telegram_signal.php) checks not the result but the possibility: which variables can reach the message at all. Four are permitted, 15 identifiers are forbidden, and the text must be a single-quoted string with no interpolation; the guard was run against two deliberately broken versions and caught both. What has not been remedied is the local storage. The request is stored unchanged in support_requests, including the email address and the IP address - this is intended, the operator needs them for disputes - and support_requests had no retention period until 17 Aug 2026; since then the request is erased after 730 days (see R7). The point of the change is that the data does not leave, not that it disappears.
Likelihood - Materialised until 11 August 2026 on every submission; since 11 August 2026 the transfer no longer takes place. Severity - Significant: until 11 August 2026 because Telegram is outside the EU and offers no processing agreement for bot channels; since then the same rating attaches to the local storage only, because what remains is the holding of email address, IP and unbounded free text in the operator's own database - limited to 730 days since 17 Aug 2026. The third-country element is gone, the retention is bounded; it is carried under R7. The decision has been taken and dated: the channel is kept, and since 11 August 2026 the message carries no data. A legal basis and a transfer instrument for a third-country transfer are therefore no longer required, because no personal data reaches Telegram; for this operation Telegram is not a processor. Overall level - medium until 11 August 2026, low since.
R14 - Egress to recipients the controller chooses themselves
MousePlayer offers bridges through which individual items are passed to the controller's own systems: their customer relationship, helpdesk, marketing, advertising, storage or task system, or an address they name themselves. They alone make the selection, they store the credentials, and the transmission is therefore their instruction. The details are set out in the Record, section 4.1.
Where the risk lies. Not in the volume: what is transmitted are identifiers and event names, never the recorded page content, the mouse trail or form text. The risk lies in irreversibility. If an erasure request under Art. 17 reaches us, we erase our copy — the copy at the recipient we cannot reach. How long they keep it, where they take it and whom they pass it to is beyond our influence and not something we can monitor. The same applies to a transfer to a third country: the controller determines the destination, and with it the safeguard under Chapter V.
What has been done about it. The privacy policy states both plainly — what goes out, and that an erasure request has no effect there — and directs the data subject to the operator of the website, who made that decision, for the question of which recipients are switched on. The DPA makes clear that these recipients are not sub-processors of MousePlayer. Technically the volume is kept small, and name, email address, phone number and IP address are expressly not sent to advertising networks. Since 20 August 2026 every project keeps a delivery log (Record A.11) from which the controller can read what actually went out — before that it was not visible to them.
What remains. A residual risk that cannot be engineered away while the feature exists: the disclosure is intended, and its consequences lie outside our sphere. It is borne by the controller who switches the bridge on, and is named to them in the DPA and in this assessment.
8. (d) Measures Envisaged to Address the Risks
Every measure below carries a status, and the statuses are not decorative. Implemented means verified in the running system today. Partial means the mechanism exists but does not yet achieve its purpose. Planned means it does not exist. A DPIA that presents planned measures as implemented ones is the standard reproach against the instrument, and it is the reproach that destroys the document's value in the only situation where the document matters.
8.1 Implemented today - verified
| Measure | Addresses | Evidence |
|---|---|---|
| Consent gate in the browser, fail-closed: no signal, CMP failure, script error and unknown origin all resolve to "do not record" | Lawfulness | mpEinw state machine in www/mp.js, behind the bot check |
| Independent server-side enforcement: only GRANTED or GRANTED_LATE or an explicit regional exemption passes; withdrawal, unknown country and anything unclear close the connection | Lawfulness | class/Server.class.php:977-1004; 24 blocked connections recorded at the survey |
| Path exclusion list applied before the gate, matched on the path only | Minimisation | class/Server.class.php:963-975; 71 blocked connections recorded at the survey |
| IP truncation at the point of reading, before any consumer of the value | R3, forward-looking only | class/Server.class.php:432-435; all sessions since 7 Aug 2026 truncated |
| Form masking with an unconditional floor for passwords and payment data at every setting; all projects on the strictest level | R2, forward-looking only | www/mp.js:4818-4853 |
| Field names classified rather than transmitted | Minimisation | www/mp.js:4653-4674 |
| DOM masking inside marked regions, applied to live mutations as well | R1, partially | www/mp.js:3934-3970 and 4732-4768 |
| TC string stored only as a SHA-256 hash | Minimisation | visitor_sessions.tcf_hash |
| Replay payload deliberately excluded from the public API | R4 | www/api/v1_session_detail.php:25 |
| Share links: 160-bit token, 7-day default and 90-day maximum, identifiers taken only from the token, query string replaced not ignored, role NULL, all three endpoints constrained, identical response for invented, expired and revoked tokens, revocation available | R4 | cfg/lib.php mp_share_erstellen / mp_share_pruefen; www/.htaccess |
| Geolocation against local database files - no third-party call anywhere in the recording path | Third-country transfers | class/IP2CityLite.class.php:37; class/Server.class.php:724 |
| Role model per workspace; deletion restricted to owner and admin and always constrained to the project | Access control | www/player.php:267, 275-282 |
| Consent event log kept deliberately beyond session deletion, as evidence that a withdrawal occurred | Art. 7(1) | srv/db_worker.php:257-259 |
| Audit log of writing actions | Accountability | cfg/lib.php:2878-2890; 666 entries at the survey |
| Transport security: forced HTTPS; TLS with a real certificate terminates in nginx, including for the WebSocket (/mp_wss/), the HTTP tunnel (/mp_wst), the snapshot upload (/mp_dom) and the measuring point (/mp_wsfail); the WebSocket server listens only on the loopback address. Database and Redis connections within the local network are not encrypted (see R10) | Confidentiality | www/.htaccess; nginx configuration (srv/nginx/); srv/server.php bound to 127.0.0.1:8081 |
| CSRF tokens from random_bytes with constant-time comparison; httponly, SameSite=Lax, secure session cookie; password hashing; prepared statements throughout the audited write paths; login rate limiting; automatic ban and per-IP connection limits; fail2ban running | Confidentiality | cfg/lib.php:110-132; www/login.php; class/Server.class.php:449-455, 660-668 |
| Secrets outside version control | Confidentiality | .gitignore covers .env; only .env.example is tracked |
| Retention enforced for the portal logs: 60 days and 72 hours respectively, plus expiry of bans | Storage limitation | srv/db_worker.php (daily deletion run) |
| Daily deletion run by tier and by separate periods for the other tables, plus hourly the periods for sessions marked as bots; its absence is reported by a separate status process by e-mail and in the Urgent Logs | Storage limitation, R7 | srv/db_worker.php; srv/cron_status.php (sys_purge); oldest entry in audit_logs on 16 Oct 2026 at the period of 60 days |
| fail2ban with nine jails, among them mp-nginx-429, which bans addresses that repeatedly trigger the nginx rate limit; plus rate limits in nginx | Availability, abuse prevention | fail2ban-client status, checked on 16 Oct 2026 |
| Archiving before final deletion: deleting projects, workspaces and accounts as well as deletion by IP address or visitor first hides the data and stops recording; final deletion follows after 30 days or earlier on request, executed and re-counted by the worker. Deletion of inactive accounts does not archive first | Integrity, storage limitation | Migration 189; cfg/archivierung_lib.php; mp_loesch_archiv |
| Periods for sessions marked as bots: recording emptied (period in days: 1), session row deleted after 14 days | Storage limitation | site_settings retention_bot_recording_days and retention_bot_session_days; srv/db_worker.php |
8.2 Partial - exists, but does not yet achieve its purpose
| Measure | Why it is not yet effective |
|---|---|
| DOM masking | Opt-in per element. Protects what the visitor types, not what the page outputs - the substance of R1 |
| Deletion on withdrawal | Implemented as an option, switched off on all projects (status 16 Oct 2026), so a withdrawal does not remove what was already recorded |
| noindex and no-referrer headers | The page that shows the recording via a share link (player.php in share mode) carries Referrer-Policy: no-referrer and Cache-Control: no-store, but no X-Robots-Tag header; noindex appears only in www/share.php, the error page, and robots.txt permits everything (checked on 16 Oct 2026) |
8.3 Planned - not implemented
| Measure | Addresses | Priority |
|---|---|---|
| Encrypt the off-site copy before upload - done on 11 August 2026: the daily run encrypts on our own server before transfer (rclone crypt, names included) and the keys sit in two files with mode 600. Delete the unencrypted legacy holdings in the previous destination folder - done on 12 August 2026. Deposit the key files so that they survive destruction of the machine - done on 17 August 2026: they are kept printed in a safe (statement of the controller). Establish or terminate the Google Drive arrangement – done on 5 September 2026: since that day the off-site copy is held in a Google Cloud Storage bucket in region europe-central2 (Warsaw) and is covered by the Google Cloud Data Processing Addendum. The remainder in the private Drive account was deleted on 6 September 2026, after the restore drill from the new destination had passed. Remove the old backup scripts - done on 16 October 2026. | R5 | No longer applies - done |
| Invert the page-content default: store text masked unless explicitly released, and require a written customer acknowledgement before verbatim page text is captured | R1 | First |
| Contractual sector policy: which sectors are admitted at all, and under what additional conditions | R1 | First |
| One-off truncation or deletion of the 236 + 291 + 666 + 5 legacy addresses - done on 17 Aug 2026: the legacy holdings in the recording path and in audit_logs and user_ip_history are shortened (see R3) | R3 | No longer applies - done on 17 Aug 2026 |
| Review and erase the 173 clear-text input rows - done on 17 Aug 2026: deleted without review (decision of the controller, see R2) | R2 | No longer applies - done on 17 Aug 2026 |
| Extend the purge to the thirteen uncovered tables; move it to a monitored schedule; fix the inner-join gap so orphaned sessions are reachable | R7 | Not applicable - completed on 17 Aug 2026 (periods and watchdog); the inner-join selection remains, orphaned sessions do not arise (see R7) |
| Log read access to the player and both data endpoints | R11, Art. 33 | High |
| Referrer-Policy and X-Robots-Tag on the player page; shorter default share lifetime - Referrer-Policy: no-referrer is set in share mode (checked on 16 Oct 2026); X-Robots-Tag and a shorter default lifetime (still 7 days) are outstanding | R4 | Medium |
| Self-service lookup so a visitor can retrieve their own identifier, making Art. 15 and Art. 17 requests answerable - done on 17 Aug 2026: /my-data (German /meine-daten, Polish /moje-dane), see R9 | R9 | No longer applies - done on 17 Aug 2026 |
| Never mask-exempt checkbox and radio state; extend masking to it | R1, R2 | Medium |
| Encryption at rest; TLS to the database; access protection for Redis - access protection for Redis done on 16 Aug 2026; encryption at rest and TLS to the database are outstanding (see R10) | R10 | Medium |
| Remove the IP from the Telegram notification, or replace the channel - completed on 11 August 2026: the notification now consists of a fixed notice text with no variable at all (cfg/lib.php), guarded by ai/pruefstand/test_telegram_signal.php | R13 | Not applicable - completed on 11 August 2026 |
| Retention period for support_requests, audit_logs and user_ip_history | R7 | Not applicable - completed on 17 Aug 2026 for support_requests and audit_logs, on 24 Sep 2026 for user_ip_history |
| Tenant boundary on visitor_nicknames | R6 | Low |
| Documented review and objection route for the automatic ban and the is_suspicious flag - for the automatic ban done on 17 Aug 2026 (expiry then after 7 days, now after 24 h, objection via the support form, lifting in the administration area, see R8); for the is_suspicious flag it is outstanding | R8 | Low |
| Verify each CMP adapter against a live installation before it is offered | R12 | Low |
8.4 Procedures and safeguards still to be established
Art. 35(7)(d) requires safeguards, security measures and mechanisms - which includes the organisational procedures, not only the code. At the survey four were absent; their current state is given with each.
- Established on 17 Aug 2026. Detection: the controller, a Customer or a website visitor. Reporting route to us: privacy@mouseplayer.com. Decision on notifying the supervisory authority: Lukas Wójcik, owner, within 72 hours under Art. 33(1). Informing the Customer: by e-mail, without undue delay under Art. 33(2), at the latest within 48 hours (data processing agreement, section 8). Sending: outgoing e-mail has been set up since 23 Sep 2026 via SMTP at Namecheap Private Email (sender office@mouseplayer.com); the promised route to the Customer is therefore in place. No exercise has taken place so far.
- Answered on 17 Aug 2026: until then no restore test had been carried out; a monthly test was planned, with a recovery time objective of no more than 24 hours. The two key files are kept printed in a safe accessible only to the controller. Carried out since: a restore drill from the new destination in Google Cloud Storage before the Drive copies were deleted (5/6 September 2026), and on 7 October 2026 a complete data restore from the cloud backup into a separate environment - about 70 minutes, samples from the archive and the database without errors. This evidences data recovery, not the rebuild of a complete production machine; there is no watchdog that reminds of the next test.
- Answered on 17 Aug 2026: there is NO automatic alerting; checking is done by hand. Measured the same day: the code contains no monitor for failure, disk space or backup success - the only Telegram occurrences sit in a vendored library nobody calls, srv/cron_status.php only writes a status value to Redis, and system_alerts is empty. Until 17 Aug 2026 the TOM promised the opposite ("in the event of a system failure the administrator receives an automatic notification"). That was an assurance the code does not deliver; it was corrected the same day. Resolved on 19 Aug 2026: srv/backup-crypt.sh now records the outcome of every run in a status file, and srv/cron_status.php checks, alongside the existing five components, the success of the backup, free disk space on the system and backup volumes, and – as a legal rather than an operational watcher – the Art. 37 threshold. Each failing check is reported by e-mail at most once a day. The watcher asks for the last successful backup rather than the last attempt: a script that starts every night and aborts every night has a fresh timestamp and no backup, which is exactly how the GeoIP update went unnoticed for five days. One limit remains and is stated rather than implied: the watcher runs on the machine it watches and cannot report that machine's total failure; there was no external uptime probe. Since 2 October 2026 a second machine at the same location checks the application every minute (TOM, section 5); a failure of the internet connection is still not detected by it. The TOM were brought into line the same day (version 1.2), so the correction is not confined to this document.
- The views of data subjects have not been sought. Reasoning: towards the observed end users MousePlayer acts solely as a processor and has no channel of its own to them - any approach would run through the Customers' websites and would itself be a processing of their data for a new purpose. The interests of data subjects are instead taken into account indirectly: through the consent gate, which records nothing without a signal, through the masking of input, and through the erasure periods. Seeking their views remains possible as soon as a channel exists that reaches them without capturing them again for the purpose. (decision of the controller, 17 Aug 2026)
9. Residual Risk and Prior Consultation under Art. 36
Art. 36(1) requires prior consultation of the supervisory authority where a DPIA indicates that the processing would result in a high risk in the absence of measures taken to mitigate the risk. The test is therefore not whether high risks were identified - two very high ones were - but whether high risk remains once the measures under (d) are in place. That question has to be answered honestly for each of the two.
R5, the daily off-site copy, was remediable by ordinary means - and has been remedied. The encryption layer before upload has been in place since 11 August 2026, the unencrypted legacy holdings at the provider were deleted on 12 August 2026, and since 5 September 2026 the copy is held in a European region under the Google Cloud Data Processing Addendum. The question of what the daily transfer rests on is therefore answered. Re-rated on 17 September 2026, R5 stands at medium. The condition from the assessment (removal of the old scripts) was met on 16 October 2026; the re-rating of R5 lies with the controller.
R1, special categories in the page content, is not fully remediable. The system cannot know what a customer's page outputs, and no amount of masking logic changes that. What the measures can achieve is a reversal of the default, so that the failure mode becomes an unhelpful recording rather than an unlawful one, plus a contractual sector policy and a customer acknowledgement. That reduces the risk substantially. It does not eliminate it, because a customer can still release page text for a page that later renders something sensitive.
Residual risk, stated honestly. With the measures in 8.3 implemented, thirteen of the fourteen risks fall to low or medium. R1 remains at medium-high - reduced but not resolved, and permanently dependent on decisions taken by parties other than MousePlayer. Without those measures, the residual risk today is high, and R1 is very high. R5 was as well until 5 September 2026; re-rated on 17 September 2026 it stands at medium. R7 was re-rated on 23 September 2026 and stands at low.
On Art. 36 the conclusion has two parts, and they must not be collapsed into one. Formally, the duty to consult falls on the controller - the customer operating the website - not on MousePlayer, which is the processor for the visitor data that carries the high risk. MousePlayer cannot consult in a customer's place, and a customer cannot delegate the decision. What MousePlayer owes under Art. 28(3)(f) is to give the customer the facts that let them make it, which is what sections (a) and (c) of this document are for.
On the merits: prior consultation is not indicated if the measures in 8.3 are implemented before production launch, because the residual risk then falls below the threshold Art. 36 addresses. Prior consultation is indicated in one specific situation, and it is a decision rather than an accident: if the operator decides not to reverse the page-content default while continuing to admit customers in the health, legal, financial or public sectors. The residual risk then stays high by choice, and Art. 36(1) applies on its terms. A second situation that stood here until 17 September 2026 - the daily off-site copy continuing without an established transfer instrument - has no longer existed since 5 September 2026 (R5).
One further point belongs here because it is easy to lose. Art. 36 is not a penalty and consulting is not an admission. It is the mechanism the Regulation provides for exactly the situation this system is in - a genuinely useful technique whose residual risk depends on a factor the operator cannot fully control. Where a controller intends to continue with a high residual risk, consultation is the lawful route to doing so.
10. Review, Version and Sign-Off
Art. 35(11) requires the controller to review whether the processing is performed in accordance with this assessment, at least when the risk changes. Three events change the risk here materially and should each trigger a re-run rather than a reading: the first customer in a regulated sector; the first activation of the BigQuery export or a webhook; and the point at which monitored end users cross the threshold recorded under section 3.
The assessment rests at its core on measurements taken on 10 and 11 August 2026 against the development system; it was approved on 17 Aug 2026 and has been updated since, most recently on 16 Oct 2026 by a reconciliation with the technical state. Open are the placeholders in sections 1, 3 and 4 and in R12, which only the controller can fill, and the re-rating of the risks whose causes have been fully or partly removed since the survey (R2, R3, R9, R10); the residual-risk conclusion in section 9 is subject to the measures still open in 8.3.
| Item | Entry |
|---|---|
| Controller and processor | LW IT Solutions Company Lukas Wójcik, al. Tadeusza Kościuszki 80/82 lok. 301, 90-437 Lodz, Poland - NIP PL7252266190 - office@mouseplayer.com |
| Data protection officer | Not designated. See section 3 for the Art. 37 analysis and the recommendation. |
| Related documents | Records of processing activities under Art. 30(1) and Art. 30(2), the technical and organisational measures, the sub-processor list, the data processing agreement and the privacy information |
| Basis of the facts | Source code, database schema, row counts, process list, configuration values and log files of the development system, 10-11 August 2026, updated through 16 October 2026 |
| Version and date of approval | Approved by Lukas Wójcik, owner, on 17 Aug 2026. No external data protection advice was obtained. Reviewed annually and on every change to the processing; the dated versions are kept in version control under ai/cms/. Added on 17 September 2026 on the owner's instruction: R5 re-rated (sections 5.4, 6, 7, 8.3 and 9). Added on 16 October 2026: reconciliation with the technical state (header, sections 1, 3, 4, 5, 7, 8 and 10); risk levels unchanged, re-rating of R2, R3, R9 and R10 by the controller outstanding. Added on 16 October 2026 (second pass): periods and the duration of the automatic ban as settings instead of fixed numbers, legal bases for support requests and comments, custody of the keys and reachability of the old backup scripts (R5), deadline for informing the Customer; risk levels unchanged, re-rating of R5 by the controller outstanding. Added on 16 October 2026 (third pass): removal of the old backup scripts recorded (R5), statements on access protection of the database and Redis harmonised (R10); risk levels unchanged, re-rating of R5 by the controller outstanding. |
A closing observation that belongs in the document rather than in a covering note. The consent architecture in this system is better than the market norm - fail-closed throughout, enforced twice independently, with the reasoning written into the migrations. The risks in section 7 are almost all of a different kind: they concern what happens to data after a lawful consent, where the discipline applied at the front door has not yet been applied to storage, erasure, export and access. That is a solvable distribution of effort, and it is a more favourable starting position than most assessments of this kind describe.