{
  "id": "DPE-2026-0032",
  "name": "Account identity joined to a tracking profile",
  "slug": "account-identity-joined-to-profile",
  "name_nl": "Account gekoppeld aan het volgprofiel",
  "family": "chain",
  "applies_to": [
    "web",
    "mobile-app"
  ],
  "summary": "At sign-in, the operator's account identifier reaches a party that already holds a profile of the same browser.",
  "summary_nl": "Bij het inloggen gaat de accountaanduiding van de dienst naar een partij die al een profiel van dezelfde browser had.",
  "not_a_vulnerability": "Nothing is exploited. The identifier is passed on because a setting says so, and the objection is to the setting.",
  "mechanism": {
    "what": "Before sign-in a third party recognises the browser or the device by an identifier of its own. At the moment of authentication the operator hands that same party its own account identifier: a user number, a customer number, a hashed address, sometimes the address itself. From then on the profile that was pseudonymous has a name attached. Everything recorded before the sign-in and everything recorded after it, on any device where the person signs in, belongs to one identified person.",
    "why_it_matters": "The step is invisible and it cannot be undone. A profile built from browsing can be argued about as long as it is pseudonymous; once joined to an account it is a dossier about a named person. Clearing cookies no longer separates the two, because the next sign-in joins them again.",
    "common_causes": [
      "a user identifier configured as a custom dimension or user property in a measurement tag",
      "an advertising tag firing on the login event with the account identifier as a parameter",
      "an identity feature of the measurement product, enabled as soon as a user identifier is available",
      "the same identifier used in the application and in the tag, because it was the value at hand"
    ],
    "not_this": "Two third parties exchanging identifiers so their profiles can be matched is Identifier synchronisation between parties. Here the value comes from the operator itself and names the account. An operator writing its own user identifier into its own store is not this entry either: the value has to reach a party that also holds an identifier of its own for that browser."
  },
  "detection": {
    "indicator": "Differential over accounts. Sign in twice in the same browser profile with two accounts you control: a request to a host under a different registrable domain carries a value that changes with the account while that host's own identifier stays the same. The reverse check confirms it, in that the same account in a fresh profile yields the same account-dependent value against a new host identifier.",
    "method": "differential",
    "qod": 95,
    "capture_requirements": [
      "two accounts you control on the same service, plus a clean profile for the reverse check",
      "capture the authentication step and the first screen after it; the join is often made once per session",
      "record the consent state, because the join may be gated and the gate is part of the finding",
      "compare on values, not on parameter names: every product names its user identifier differently",
      "sign in twice with the same account, so a session token can be told apart from an account identifier"
    ],
    "attribution": [
      "har-pageref",
      "cdp-initiator"
    ]
  },
  "falsifiers": [
    {
      "condition": "The account-dependent value changes on every sign-in of the same account, so it identifies a session rather than an account.",
      "checkable": "automated",
      "if_true": "drop"
    },
    {
      "condition": "The receiving host is under the operator's own registrable domain.",
      "checkable": "automated",
      "if_true": "drop"
    },
    {
      "condition": "The receiving party holds no identifier of its own for this browser, so there is nothing to join to.",
      "checkable": "automated",
      "if_true": "weaken",
      "note": "Then the account identifier is being disclosed, which is a transfer question, without the joining that makes this entry what it is."
    },
    {
      "condition": "The recipient acts on the operator's instruction only and is barred from using the value for its own purposes.",
      "checkable": "not-from-capture",
      "if_true": "weaken"
    }
  ],
  "legal": {
    "provisions": [
      "eu-gdpr-5-1-b",
      "eu-gdpr-6-1-a",
      "eu-gdpr-13",
      "eu-gdpr-26"
    ],
    "rebuttals": [
      {
        "objection": "We only send an internal number, not a name.",
        "answer": "The number is the join. The recipient does not need the name to know that this browser and that account are one person, and the operator can resolve the number to a name whenever it likes."
      },
      {
        "objection": "It improves measurement across devices.",
        "answer": "That is the purpose stated plainly, and it is exactly the processing nobody was asked about. A benefit to the operator is not a ground."
      },
      {
        "objection": "The person is logged in, so they know we know them.",
        "answer": "They know the operator knows them. This is about a third party, and the sign-in screen says nothing about it."
      },
      {
        "objection": "The identifier is hashed before it is sent.",
        "answer": "A hash that is stable per account works as an account identifier. What matters is that the value is the same on every visit and different per person."
      }
    ]
  },
  "related": [
    "DPE-2026-0016",
    "DPE-2026-0031"
  ],
  "seen_in_the_wild": {
    "confirmed": false,
    "note": "Observed in practice by the authors but not yet backed by a published reference. Set to false until a citation exists: a claim without a source is exactly what this catalogue holds others to."
  },
  "in_practice": {
    "dpia": "Verify with two test accounts what leaves the browser at the moment of sign-in, rather than accepting that measurement is pseudonymous.",
    "procurement": "No account identifier, hashed or not, reaches a measurement or advertising party at authentication; demonstrated with two test accounts on delivery.",
    "complaint": "Two captures of the sign-in step with different accounts, the value that changes with the account marked, and the third party's own identifier shown to be constant.",
    "audit_question": "What leaves the browser at the moment someone signs in, and does anything in it change with the account?",
    "audit_question_nl": "Wat verlaat de browser op het moment dat iemand inlogt, en verandert daar iets in met het account mee?",
    "complaint_nl": "Twee opnamen van dezelfde inlogstap met verschillende accounts, met de waarde die meeverandert aangewezen.",
    "objection_nl": "Wij sturen alleen een intern nummer door, geen naam.",
    "answer_nl": "Dat nummer is juist de koppeling. De ontvanger hoeft de naam niet te kennen om te weten dat deze browser en dat account een persoon zijn."
  },
  "schema_version": "2.0",
  "status": "active",
  "credit": [
    {
      "name": "Mick Beer",
      "role": "proposed",
      "date": "2026-07-26"
    }
  ],
  "does_not_establish": [
    "harm; the catalogue standardises a finding so it can be referred to, it does not weigh it",
    "severity; there is no score here, by design. Weighing belongs to whoever applies the entry to a concrete case",
    "unlawfulness; that is for a supervisory authority or a court",
    "intent; a fault is usually a build decision, not a plan",
    "absence: not finding it in one capture is not evidence that it is not there"
  ],
  "reproduction": {
    "methods": [
      {
        "tier": "manual",
        "path": "METHOD.md",
        "expect": "no dedicated reproduction exists yet; follow the general method and the indicator above"
      }
    ]
  },
  "changes": [
    {
      "at": "2026-07-26T00:00:00Z",
      "actor": "registry",
      "entries": [
        "Entry created.",
        "Name assigned.",
        "Detection method and falsifiers defined.",
        "Legal provisions linked."
      ]
    }
  ]
}
