diff --git a/EVENT.md b/EVENT.md index d67a96d103..f49668987c 100644 --- a/EVENT.md +++ b/EVENT.md @@ -1114,9 +1114,27 @@ trying to measure user visits on, the browser can limit the number `destination` sites represented by unexpired sources for a source-site. The browser can place a limit on the number of a source site's unexpired source's -unique `destination` sites. When an attribution source is registered for a site -that is not already in the unexpired sources and a source site is at its limit, -the browser will drop the new source. +unique `destination` sites. Source registrations will accept an optional field +`destination_limit_priority` to allow developers to prioritize the destinations +registered with this source with respect to other destinations for the purpose +of source deactivation. + +```jsonc +{ + ..., // existing fields + "destination_limit_priority": "[64-bit signed integer]" // defaults to 0 if not present +} +``` + +When an attribution source is registered for a site that is not already in the +unexpired sources and a source site is at its limit, the browser will sort the +`destination` sites registered by unexpired sources, including the new source, +by `destination_limit_priority` in descending order and by the registration +time in descending order. The browser will then select the first few +`destination` sites within this limit, and delete pending sources and +aggregatable reports associated with the unselected `destination` sites. Any +event-level reports are not deleted, as the leak of user's browsing history is +mitigated by fake reports within differential privacy. The lower this value, the harder it is for a reporting origin to use the API to try and measure user browsing activity not associated with ads being shown. @@ -1129,6 +1147,9 @@ the [denial of service](#denial-of-service) for more details. To prevent this attack, the browser should maintain these limits per reporting site. This effectively limits the number of unique sites covered per {source site, reporting site} applied to all unexpired sources regardless of type at source time. +The browser can also limit the number of `destination` sites per {source site, reporting site, 1 day} +to mitigate the history reconstruction attack. + #### Limiting the number of unique destinations per source site To further reduce the possibility of a history reconstruction attack, the browser can also limit the number of `destination` sites registered per {source-site, 1 minute}. diff --git a/index.bs b/index.bs index 4a008aecc7..9d3d3676fc 100644 --- a/index.bs +++ b/index.bs @@ -843,6 +843,8 @@ An attribution source is a [=struct=] with the following items: :: Number of [=aggregatable debug reports=] created for this [=attribution source=]. : aggregatable debug reporting config :: An [=aggregatable debug reporting config=]. +: destination limit priority +:: A 64-bit integer. @@ -1064,6 +1066,8 @@ An aggregatable attribution report is an [=aggregatable report=] with :: Null or a [=string=]. : attribution debug info :: An [=attribution debug info=]. +: source identifier +:: A [=string=]. @@ -1094,8 +1098,14 @@ An attribution rate-limit record is a [=struct=] with the following i :: A [=moment=]. : expiry time :: Null or a [=moment=]. -: event-level report ID -:: Null or an [=event-level report=]'s [=event-level report/report ID=]. +: entity ID +:: Null for [=obtain a fake report|fake reports=] or an [=event-level report=]'s [=event-level report/report ID=] or an + [=aggregatable attribution report=]'s [=aggregatable attribution report/report ID=] or an + [=attribution source=]'s [=attribution source/source identifier=]. +: deactivated for unexpired destination limit (default false) +:: A [=boolean=]. +: destination limit priority (default null) +:: Null or a 64-bit integer. @@ -1127,6 +1137,8 @@ Possible values are:
  • "source-channel-capacity-limit"
  • "source-destination-global-rate-limit"
  • "source-destination-limit" +
  • "source-destination-limit-replaced" +
  • "source-destination-per-day-rate-limit"
  • "source-destination-rate-limit"
  • "source-noised"
  • "source-reporting-origin-limit" @@ -1434,6 +1446,11 @@ The second controls the maximum number of distinct [=sites=] across all [=attrib for [=attribution sources=] with a given ([=attribution source/source site=], [=attribution source/reporting origin=] [=site=]) per [=destination rate-limit window=]. +Max destinations per source reporting site per day is an integer that controls +the maximum number of distinct [=sites=] across all [=attribution source/attribution destinations=] +for [=attribution sources=] with a given ([=attribution source/source site=], [=attribution source/reporting origin=] [=site=]) +per day. + Max source reporting origins per rate-limit window is a positive integer that controls the maximum number of distinct [=attribution source/reporting origin|reporting origins=] for a @@ -2361,6 +2378,7 @@ A source-registration JSON key is one of the following:
  • "debug_key"
  • "debug_reporting"
  • "destination" +
  • "destination_limit_priority"
  • "end_times"
  • "event_level_epsilon"
  • "event_report_window" @@ -2666,6 +2684,10 @@ To parse source-registration JSON given a [=byte sequence=] [=parse an optional 64-bit signed integer=] with |value|, "[=source-registration JSON key/priority=]", and 0. 1. If |priority| is an error, return null. +1. Let |destinationLimitPriority| be the result of running + [=parse an optional 64-bit signed integer=] with |value|, + "[=source-registration JSON key/destination_limit_priority=]", and 0. +1. If |destinationLimitPriority| is an error, return null. 1. Let |filterData| be a new [=filter map=]. 1. If |value|["[=source-registration JSON key/filter_data=]"] [=map/exists=]: 1. Set |filterData| to the result of running [=parse filter data=] with @@ -2772,6 +2794,8 @@ To parse source-registration JSON given a [=byte sequence=] :: |aggregatableDebugBudget| : [=attribution source/aggregatable debug reporting config=] :: |aggregatableDebugReportingConfig| + : [=attribution source/destination limit priority=] + :: |destinationLimitPriority| 1. Return |source|. Issue: Determine proper charset-handling for the JSON header value. @@ -2812,19 +2836,123 @@ To check if an [=attribution source=] exceeds the time-based destination li Note: When both limits are hit, we interpret it as "[=destination rate-limit result/hit reporting limit=]" for debug reporting. -To check if an [=attribution source=] exceeds the unexpired destination limit given an -[=attribution source=] |source|, run the following steps: +To check if an [=attribution source=] exceeds the per day destination limits +given an [=attribution source=] |source|, run the following steps: -1. Let |unexpiredSources| be all [=attribution rate-limit records=] |record| in the [=attribution rate-limit cache=] where all of the following are true: +1. Let |matchingSources| be all [=attribution rate-limit records=] |record| in the [=attribution rate-limit cache=] where all of the following are true: * |record|'s [=attribution rate-limit record/scope=] is "[=rate-limit scope/source=]" * |record|'s [=attribution rate-limit record/source site=] and |source|'s [=attribution source/source site=] are equal - * |record|'s [=attribution rate-limit record/reporting origin=] and |source|'s [=attribution source/reporting origin=] are [=same site=] + * |record|'s [=attribution rate-limit record/reporting origin=] is [=same site=] with + |source|'s [=attribution source/reporting origin=] * |record|'s [=attribution rate-limit record/expiry time=] is greater than |source|'s [=attribution source/source time=] -1. Let |unexpiredDestinations| be a new [=set=]. -1. For each [=attribution rate-limit record=] |unexpiredRecord| of |unexpiredSources|: - 1. [=set/Append=] |unexpiredRecord|'s [=attribution rate-limit record/attribution destination=] to |unexpiredDestinations|. -1. Let |newDestinations| be the result of taking the [=set/union=] of |unexpiredDestinations| and |source|'s [=attribution source/attribution destinations=]. -1. Return whether |newDestinations|'s [=set/size=] is greater than the user agent's [=max destinations covered by unexpired sources=]. + * The [=duration from=] |record|'s [=attribution rate-limit record/time=] and |source|'s [=attribution source/source time=] + is less than 1 day +1. Let |destinations| be the [=set=] of all [=attribution rate-limit record/attribution destination=] in |matchingSources|, + [=set/union|unioned=] with |source|'s [=attribution source/attribution destinations=]. +1. Return whether |destinations|'s [=set/size=] is greater than [=max destinations per source reporting site per day=]. + +To delete sources for unexpired destination limit given a [=set=] of +[=attribution source/source identifiers=] |sourcesToDelete| and a [=moment=] |now|: + +1. If |sourcesToDelete| [=set/is empty=], return. +1. [=set/iterate|For each=] [=attribution source=] |source| of the [=attribution source cache=]: + 1. [=set/Remove=] |source| from the [=attribution source cache=] if |sourcesToDelete| + [=set/contains=] |source|'s [=attribution source/source identifier=]. +1. Let |deletedEventLevelReports| be a new [=set=]. +1. [=set/iterate|For each=] [=event-level report=] |report| of the [=event-level report cache=]: + 1. If |sourcesToDelete| [=set/contains=] |report|'s [=event-level report/source identifier=] + and |report|'s [=event-level report/trigger time=] is greater than or equal to |now|: + 1. [=set/Append=] |report|'s [=event-level report/report ID=] to |deletedEventLevelReports|. + 1. [=set/Remove=] |report| from the [=event-level report cache=]. +1. Let |deletedAggregatableReports| be a new [=set=]. +1. [=set/iterate|For each=] [=aggregatable attribution report=] |report| of the [=aggregatable attribution report cache=]: + 1. If |sourcesToDelete| [=set/contains=] |report|'s [=aggregatable attribution report/source identifier=]: + 1. [=set/Append=] |report|'s [=aggregatable attribution report/report ID=] to |deletedAggregatableReports|. + 1. [=set/Remove=] |report| from the [=aggregatable attribution report cache=]. +1. [=set/iterate|For each=] [=attribution rate-limit record=] |record| of the [=attribution rate-limit cache=]: + 1. If |record|'s [=attribution rate-limit record/scope=] is: +
    + : "[=rate-limit scope/source=]" + :: Set |record|'s [=attribution rate-limit record/deactivated for unexpired destination limit=] to + true if |sourcesToDelete| [=set/contains=] |record|'s [=attribution rate-limit record/entity ID=]. + : "[=rate-limit scope/event-attribution=]" + :: [=set/Remove=] |record| from the [=attribution rate-limit cache=] if + |deletedEventLevelReports| [=set/contains=] |record|'s [=attribution rate-limit record/entity ID=]. + : "[=rate-limit scope/aggregatable-attribution=]" + :: [=set/Remove=] |record| from the [=attribution rate-limit cache=] if + |deletedAggregatableReports| [=set/contains=] |record|'s [=attribution rate-limit record/entity ID=]. + +
    + +A destination limit record is a [=struct=] with the following items: + +
    +: attribution destination +:: A [=site=]. +: priority +:: A 64-bit integer. +: time +:: A [=moment=] +: source identifier +:: A [=string=]. + +
    + +To get sources to delete for the unexpired destination limit given an +[=attribution source=] |source|, run the following steps: +1. Let |destinationRecords| be a new [=list=]. +1. [=set/iterate|For each=] [=attribution rate-limit record=] |record| of the [=attribution rate-limit cache=]: + 1. If |record|'s [=attribution rate-limit record/scope=] is not "[=rate-limit scope/source=]", [=iteration/continue=]. + 1. If |record|'s [=attribution rate-limit record/deactivated for unexpired destination limit=] is true, [=iteration/continue=]. + 1. If |record|'s [=attribution rate-limit record/source site=] and |source|'s [=attribution source/source site=] are not equal, [=iteration/continue=]. + 1. If |record|'s [=attribution rate-limit record/reporting origin=] and |source|'s [=attribution source/reporting origin=] are not [=same site=], [=iteration/continue=]. + 1. If |record|'s [=attribution rate-limit record/expiry time=] is less than or equal to |source|'s [=attribution source/source time=], [=iteration/continue=]. + 1. [=Assert=]: |record|'s [=attribution rate-limit record/destination limit priority=] is not null. + 1. [=Assert=]: |record|'s [=attribution rate-limit record/entity ID=] is not null. + 1. Let |destinationRecord| be a new [=destination limit record=] struct whose items are: + + : [=destination limit record/attribution destination=] + :: |record|'s [=attribution rate-limit record/attribution destination=] + : [=destination limit record/priority=] + :: |record|'s [=attribution rate-limit record/destination limit priority=] + : [=destination limit record/time=] + :: |record|'s [=attribution rate-limit record/time=] + : [=destination limit record/source identifier=] + :: |record|'s [=attribution rate-limit record/entity ID=] + + 1. [=list/Append=] |destinationRecord| to |destinationRecords|. +1. [=set/iterate|For each=] [=site=] |destination| of |source|'s [=attribution source/attribution destinations=]: + 1. Let |destinationRecord| be a new [=destination limit record=] struct whose items are: + + : [=destination limit record/attribution destination=] + :: |destination| + : [=destination limit record/priority=] + :: |source|'s [=attribution source/destination limit priority=] + : [=destination limit record/time=] + :: |source|'s [=attribution source/source time=] + : [=destination limit record/source identifier=] + :: |record|'s [=attribution source/source identifier=] + + 1. [=list/Append=] |destinationRecord| to |destinationRecords|. +1. [=list/sort in descending order|Sort=] |destinationRecords| in descending order, with |a| less than |b| if the following steps return true: + 1. If |a|'s [=destination limit record/priority=] is less than |b|'s [=destination limit record/priority=], return true. + 1. If |a|'s [=destination limit record/priority=] is greater than |b|'s [=destination limit record/priority=], return false. + 1. If |a|'s [=destination limit record/time=] is less than |b|'s [=destination limit record/time=], return true. + 1. If |a|'s [=destination limit record/time=] is greater than |b|'s [=destination limit record/time=], return false. + 1. If |a|'s serialized + [=destination limit record/attribution destination=] + is less than |b|'s serialized + [=destination limit record/attribution destination=], return true. + 1. Return false. +1. Let |sourcesToDelete| be a new [=set=]. +1. Let |newDestinations| be a new [=set=]. +1. [=set/iterate|For each=] [=destination limit record=] |record| of |destinationRecords|: + 1. Let |destination| be |record|'s [=destination limit record/attribution destination=]. + 1. If |newDestinations|'s [=set/size=] is less than the user agent's [=max destinations covered by unexpired sources=], + [=set/append=] |destination| to |newDestinations|. + 1. Otherwise, if |newDestinations| does not [=set/contain=] |destination|: + 1. [=set/Append=] |record|'s [=destination limit record/source identifier=] to |sourcesToDelete|. +1. Return |sourcesToDelete|. To check if an [=attribution source=] should be blocked by reporting-origin per site limit given an [=attribution source=] |source|: @@ -2842,8 +2970,7 @@ a [=trigger state=] |triggerState|: 1. Let |specEntry| be the [=map/entry=] for |source|'s [=attribution source/trigger specs=][|triggerState|'s [=trigger state/trigger data=]]. -1. Let |triggerTime| be the greatest [=moment=] that is strictly less than - |triggerState|'s [=trigger state/report window=]'s [=report window/end=]. +1. Let |triggerTime| be |triggerState|'s [=trigger state/report window=]'s [=report window/start=]. 1. Let |priority| be 0. 1. Let |fakeReport| be the result of running [=obtain an event-level report=] with |source|, |triggerTime|, [=obtain an event-level report/triggerDebugKey=] set to null, @@ -2852,9 +2979,22 @@ a [=trigger state=] |triggerState|: |triggerState|'s [=trigger state/report window=]'s [=report window/end=]. 1. Return |fakeReport|. +To check if a source debug data type is a verbose debug data type +given a [=source debug data type=] |dataType|: + +1. If |dataType| is: +
    + : "[=source debug data type/source-destination-global-rate-limit=]" + : "[=source debug data type/source-destination-limit-replaced=]" + : "[=source debug data type/source-reporting-origin-limit=]" + :: Return false. + +
    +1. Return true. + To obtain and deliver a verbose debug report on source registration given a -[=source debug data type=] |dataType|, an [=attribution source=] |source|, and -a [=boolean=] |isNoised|: +[=set=] of [=source debug data types=] |dataTypes|, an [=attribution source=] |source|, +and a [=boolean=] |isNoised|: 1. If |source|'s [=attribution source/debug reporting enabled=] is false, return. 1. If |source|'s [=attribution source/debug cookie set=] is false, return. @@ -2867,39 +3007,58 @@ a [=boolean=] |isNoised|: :: |source|'s [=attribution source/source site=], serialized. 1. If |source|'s [=attribution source/debug key=] is not null, [=map/set=] |body|["`source_debug_key`"] to |source|'s [=attribution source/debug key=], [=serialize an integer|serialized=]. -1. Let |dataTypeToReport| be |dataType|. -1. If |dataType| is: -
    - : "[=source debug data type/source-destination-global-rate-limit=]" - : "[=source debug data type/source-reporting-origin-limit=]" - :: Set |dataTypeToReport| to "[=source debug data type/source-success=]". +1. Let |dataTypeToReport| be null. +1. [=set/iterate|For each=] |dataType| of |dataTypes|: + 1. Let |effectiveDataType| be |dataType|. + 1. If |dataType| is: +
    + : "[=source debug data type/source-destination-global-rate-limit=]" + : "[=source debug data type/source-reporting-origin-limit=]" + :: Set |effectiveDataType| to "[=source debug data type/source-success=]". -
    -1. If |dataTypeToReport| is "[=source debug data type/source-success=]" - and |isNoised| is true, set |dataTypeToReport| to "[=source debug data type/source-noised=]". -1. If |dataTypeToReport| is: -
    - : "[=source debug data type/source-destination-limit=]" - :: [=map/Set=] |body|["`limit`"] to the user agent's [=max destinations covered by unexpired sources=], - [=serialize an integer|serialized=]. - : "[=source debug data type/source-destination-rate-limit=]" - :: [=map/Set=] |body|["`limit`"] to the user agent's [=max destinations per rate-limit window=][1], - [=serialize an integer|serialized=]. - : "[=source debug data type/source-storage-limit=]" - :: [=map/Set=] |body|["`limit`"] to the user agent's [=max pending sources per source origin=], - [=serialize an integer|serialized=]. - : "[=source debug data type/source-channel-capacity-limit=]" - :: - 1. Let |sourceType| be |source|'s [=attribution source/source type=]. - 1. [=map/Set=] |body|["`limit`"] to the user agent's [=max event-level channel capacity per source=][|sourceType|]. - : "[=source debug data type/source-trigger-state-cardinality-limit=]" - :: [=map/Set=] |body|["`limit`"] to the user agent's [=max trigger-state cardinality=], - [=serialize an integer|serialized=]. - : "[=source debug data type/source-reporting-origin-per-site-limit=]" - :: [=map/Set=] |body|["`limit`"] to the user agent's [=max source reporting origins per source reporting site=], - [=serialize an integer|serialized=]. +
    + 1. If |effectiveDataType| is "[=source debug data type/source-success=]" + and |isNoised| is true, set |effectiveDataType| to "[=source debug data type/source-noised=]". + 1. If the result of [=checking if a source debug data type is a verbose debug data type=] + is true: + 1. [=Assert=]: |dataTypeToReport| is null. + 1. Set |dataTypeToReport| to |effectiveDataType|. + 1. If |effectiveDataType| is: +
    + : "[=source debug data type/source-destination-limit=]" + :: [=map/Set=] |body|["`limit`"] to the user agent's [=max destinations covered by unexpired sources=], + [=serialize an integer|serialized=]. + : "[=source debug data type/source-destination-limit-replaced=]" + :: [=map/Set=] |body|["`source_destination_limit`"] to the user agent's + [=max destinations covered by unexpired sources=], [=serialize an integer|serialized=]. + + Note: The "`source_destination_limit`" field may be included to indicate that + [=max destinations covered by unexpired sources=] was hit, which is not + reported as "[=source debug data type/source-destination-limit=]" to prevent side-channel + leakage of cross-origin data. + + : "[=source debug data type/source-destination-rate-limit=]" + :: [=map/Set=] |body|["`limit`"] to the user agent's [=max destinations per rate-limit window=][1], + [=serialize an integer|serialized=]. + : "[=source debug data type/source-destination-per-day-rate-limit=]" + :: [=map/Set=] |body|["`limit`"] to the user agent's [=max destinations per source reporting site per day=], + [=serialize an integer|serialized=]. + : "[=source debug data type/source-storage-limit=]" + :: [=map/Set=] |body|["`limit`"] to the user agent's [=max pending sources per source origin=], + [=serialize an integer|serialized=]. + : "[=source debug data type/source-channel-capacity-limit=]" + :: + 1. Let |sourceType| be |source|'s [=attribution source/source type=]. + 1. [=map/Set=] |body|["`limit`"] to the user agent's [=max event-level channel capacity per source=][|sourceType|]. + : "[=source debug data type/source-trigger-state-cardinality-limit=]" + :: [=map/Set=] |body|["`limit`"] to the user agent's [=max trigger-state cardinality=], + [=serialize an integer|serialized=]. + : "[=source debug data type/source-reporting-origin-per-site-limit=]" + :: [=map/Set=] |body|["`limit`"] to the user agent's [=max source reporting origins per source reporting site=], + [=serialize an integer|serialized=]. -
    +
    +1. [=Assert=]: |dataTypeToReport| is not null. 1. Let |data| be a new [=verbose debug data=] with the items: : [=verbose debug data/data type=] :: |dataTypeToReport| @@ -2909,7 +3068,7 @@ a [=boolean=] |isNoised|: and |source|'s [=attribution source/fenced=]. To obtain and deliver an aggregatable debug report on source registration -given a [=source debug data type=] |dataType|, an [=attribution source=] |source|, +given a [=set=] of [=source debug data types=] |dataTypes|, an [=attribution source=] |source|, and a [=boolean=] |isNoised|: 1. If |source|'s [=attribution source/fenced=] is true, return. @@ -2917,17 +3076,18 @@ and a [=boolean=] |isNoised|: 1. Let |debugDataMap| be |config|'s [=aggregatable debug reporting config/debug data=]. 1. If |debugDataMap| [=map/is empty=], return. 1. Let |contributions| be a new [=list=]. -1. Let |dataTypeToReport| be |dataType|. -1. If |dataTypeToReport| is "[=source debug data type/source-success=]" - and |isNoised| is true, set |dataTypeToReport| to "[=source debug data type/source-noised=]". -1. If |debugDataMap|[|dataTypeToReport|] [=map/exists=]: - 1. Let |contribution| be a new [=aggregatable contribution=] with items: - : [=aggregatable contribution/key=] - :: |debugDataMap|[|dataTypeToReport|]'s [=aggregatable contribution/key=] bitwise-OR - |config|'s [=aggregatable debug reporting config/key piece=] - : [=aggregatable contribution/value=] - :: |debugDataMap|[|dataTypeToReport|]'s [=aggregatable contribution/value=] - 1. [=list/Append=] |contribution| to |contributions|. +1. [=set/iterate|For each=] |dataType| of |dataTypes|: + 1. Let |dataTypeToReport| be |dataType|. + 1. If |dataType| is "[=source debug data type/source-success=]" + and |isNoised| is true, set |dataTypeToReport| to "[=source debug data type/source-noised=]". + 1. If |debugDataMap|[|dataTypeToReport|] [=map/exists=]: + 1. Let |contribution| be a new [=aggregatable contribution=] with items: + : [=aggregatable contribution/key=] + :: |debugDataMap|[|dataTypeToReport|]'s [=aggregatable contribution/key=] bitwise-OR + |config|'s [=aggregatable debug reporting config/key piece=] + : [=aggregatable contribution/value=] + :: |debugDataMap|[|dataTypeToReport|]'s [=aggregatable contribution/value=] + 1. [=list/Append=] |contribution| to |contributions|. 1. Run [=obtain and deliver an aggregatable debug report on registration=] with |contributions|, |source|'s [=attribution source/source site=], |source|'s [=attribution source/reporting origin=], |source|, |source|'s [=attribution source/attribution destinations=][0], @@ -2935,13 +3095,13 @@ and a [=boolean=] |isNoised|: and |source|'s [=attribution source/source time=]. To obtain and deliver debug reports on source registration -given a [=source debug data type=] |dataType|, an [=attribution source=] |source|, +given a [=set=] of [=source debug data types=] |dataTypes|, an [=attribution source=] |source|, and an optional [=boolean=] |isNoised| (default false): 1. Run [=obtain and deliver a verbose debug report on source registration=] - with |dataType|, |source|, and |isNoised|. + with |dataTypes|, |source|, and |isNoised|. 1. Run [=obtain and deliver an aggregatable debug report on source registration=] - with |dataType|, |source|, and |isNoised|. + with |dataTypes|, |source|, and |isNoised|. To process an attribution source given an [=attribution source=] |source|: @@ -2957,11 +3117,13 @@ To process an attribution source given an [=attribution source=] |sou 1. Let |epsilon| be |source|'s [=attribution source/event-level epsilon=]. 1. Let |channelCapacity| be the result of [=computing the channel capacity of a source=] with |randomizedResponseConfig| and |epsilon|. 1. If |channelCapacity| is an error: - 1. Run [=obtain and deliver debug reports on source registration=] with "[=source debug data type/source-trigger-state-cardinality-limit=]" and |source|. + 1. Run [=obtain and deliver debug reports on source registration=] with + « "[=source debug data type/source-trigger-state-cardinality-limit=]" » and |source|. 1. Return. 1. Let |sourceType| be |source|'s [=attribution source/source type=]. 1. If |channelCapacity| is greater than [=max event-level channel capacity per source=][|sourceType|]: - 1. Run [=obtain and deliver debug reports on source registration=] with "[=source debug data type/source-channel-capacity-limit=]" and |source|. + 1. Run [=obtain and deliver debug reports on source registration=] with + « "[=source debug data type/source-channel-capacity-limit=]" » and |source|. 1. Return. 1. Set |source|'s [=attribution source/randomized response=] to the result of [=obtaining a randomized source response=] with |randomizedResponseConfig| and |epsilon|. @@ -2976,24 +3138,37 @@ To process an attribution source given an [=attribution source=] |sou [=attribution source/source origin=] are [=same origin=]. 1. If |pendingSourcesForSourceOrigin|'s [=list/size=] is greater than or equal to the user agent's [=max pending sources per source origin=]: - 1. Run [=obtain and deliver debug reports on source registration=] with "[=source debug data type/source-storage-limit=]" and |source|. - 1. Return. -1. If the result of running [=check if an attribution source exceeds the unexpired destination limit=] - with |source| is true: - 1. Run [=obtain and deliver debug reports on source registration=] with "[=source debug data type/source-destination-limit=]" and |source|. - 1. Return. -1. If the result of running [=check if an attribution source should be blocked by reporting-origin per site limit=] - with |source| is blocked: - 1. Run [=obtain and deliver debug reports on source registration=] with "[=source debug data type/source-reporting-origin-per-site-limit=]" and |source|. + 1. Run [=obtain and deliver debug reports on source registration=] with + « "[=source debug data type/source-storage-limit=]" » and |source|. 1. Return. 1. Let |destinationRateLimitResult| be the result of running [=check if an attribution source exceeds the time-based destination limit=] with |source|. 1. If |destinationRateLimitResult| is "[=destination rate-limit result/hit reporting limit=]": - 1. Run [=obtain and deliver debug reports on source registration=] with "[=source debug data type/source-destination-rate-limit=]" and |source|. + 1. Run [=obtain and deliver debug reports on source registration=] with + « "[=source debug data type/source-destination-rate-limit=]" » and |source|. 1. Return. +1. If the result of running [=check if an attribution source exceeds the per day destination limit=] + with |source| is true: + 1. Run [=obtain and deliver debug reports on source registration=] with + « "[=source debug data type/source-destination-per-day-rate-limit=]" » and |source|. + 1. Return. +1. Let |sourcesToDeleteForDestinationLimit| be the result of running [=get sources to delete for the unexpired destination limit=] + with |source|. +1. If |sourcesToDeleteForDestinationLimit| [=set/contains=] |source|'s [=attribution source/source identifier=]: + 1. Run [=obtain and deliver debug reports on source registration=] with + "[=source debug data type/source-destination-limit=]" and |source|. + 1. Return. +1. Let |debugDataTypes| be a new [=set=]. +1. If |sourcesToDeleteForDestinationLimit| is not [=set/is empty|empty=], + [=set/append=] "[=source debug data type/source-destination-limit-replaced=]" + to |debugDataTypes|. +1. Run [=delete sources for unexpired destination limit=] with |sourcesToDeleteForDestinationLimit| and |source|'s [=attribution source/source time=]. 1. Let |isNoised| be true if |source|'s [=attribution source/randomized response=] is not null, otherwise false. 1. If |destinationRateLimitResult| is "[=destination rate-limit result/hit global limit=]": - 1. Run [=obtain and deliver debug reports on source registration=] with "[=source debug data type/source-destination-global-rate-limit=]", |source|, and |isNoised|. + 1. [=set/Append=] "[=source debug data type/source-destination-global-rate-limit=]" + to |debugDataTypes|. + 1. Run [=obtain and deliver debug reports on source registration=] + with |debugDataTypes|, |source|, and |isNoised|. 1. Return. 1. Let |newRateLimitRecords| be a new [=set=]. 1. [=set/iterate|For each=] |destination| in |source|'s [=attribution source/attribution destinations=]: @@ -3010,13 +3185,16 @@ To process an attribution source given an [=attribution source=] |sou :: |source|'s [=attribution source/source time=] : [=attribution rate-limit record/expiry time=] :: |source|'s [=attribution source/expiry time=] - : [=attribution rate-limit record/event-level report ID=] - :: null + : [=attribution rate-limit record/entity ID=] + :: |source|'s [=attribution source/source identifier=] + : [=attribution rate-limit record/destination limit priority=] + :: |source|'s [=attribution source/destination limit priority=] 1. If the result of running [=should processing be blocked by reporting-origin limit=] with |rateLimitRecord| is blocked: - 1. Run [=obtain and deliver debug reports on source registration=] with - "[=source debug data type/source-reporting-origin-limit=]", |source|, - and |isNoised|. + 1. [=set/Append=] "[=source debug data type/source-reporting-origin-limit=]" + to |debugDataTypes|. + 1. Run [=obtain and deliver debug reports on source registration=] + with |debugDataTypes|, |source|, and |isNoised|. 1. Return. 1. [=set/Append=] |rateLimitRecord| to |newRateLimitRecords|. 1. [=set/iterate|For each=] |record| of |newRateLimitRecords|, [=set/append=] |record| to @@ -3045,12 +3223,13 @@ To process an attribution source given an [=attribution source=] |sou :: |source|'s [=attribution source/source time=] : [=attribution rate-limit record/expiry time=] :: null - : [=attribution rate-limit record/event-level report ID=] + : [=attribution rate-limit record/entity ID=] :: null 1. [=set/Append=] |rateLimitRecord| to the [=attribution rate-limit cache=]. +1. [=set/Append=] "[=source debug data type/source-success=]" to + |debugDataTypes|. 1. Run [=obtain and deliver debug reports on source registration=] with - "[=source debug data type/source-success=]", |source|, - and |isNoised|. + |debugDataTypes|, |source|, and |isNoised|. 1. [=set/Append=] |source| to |cache|. Note: Because a fake report does not have a "real" effective destination, we need to subtract from the @@ -3603,7 +3782,7 @@ To maybe replace event-level report given an [=attribution source=] 1. [=set/Remove=] |lowestPriorityReport| from the [=event-level report cache=]. 1. Decrement |sourceToAttribute|'s [=attribution source/number of event-level reports=] value by 1. 1. Let |rateLimitRecord| be the element from [=attribution rate-limit cache=] whose - [=attribution rate-limit record/event-level report ID=] is equal to |lowestPriorityReport|'s [=event-level report/report ID=] + [=attribution rate-limit record/entity ID=] is equal to |lowestPriorityReport|'s [=event-level report/report ID=] and [=attribution rate-limit record/scope=] is equal to "[=rate-limit scope/event-attribution=]". 1. [=Assert=]: |rateLimitRecord| is not null. @@ -3678,7 +3857,7 @@ To trigger event-level attribution given an [=attribution trigger=] | :: |sourceToAttribute|'s [=attribution source/source time=] : [=attribution rate-limit record/expiry time=] :: null - : [=attribution rate-limit record/event-level report ID=] + : [=attribution rate-limit record/entity ID=] :: |report|'s [=event-level report/report ID=] 1. If the result of running [=check if attribution should be blocked by rate limits=] with |trigger|, |sourceToAttribute|, and |rateLimitRecord| is not null, return it. @@ -3777,7 +3956,7 @@ To trigger aggregatable attribution given an [=attribution trigger=] :: |sourceToAttribute|'s [=attribution source/source time=] : [=attribution rate-limit record/expiry time=] :: null - : [=attribution rate-limit record/event-level report ID=] + : [=attribution rate-limit record/entity ID=] :: null 1. If the result of running [=check if attribution should be blocked by rate limits=] with |trigger|, |sourceToAttribute|, and |rateLimitRecord| is not null, @@ -4032,6 +4211,8 @@ an [=attribution trigger=] |trigger|: :: |trigger|'s [=attribution trigger/aggregatable source registration time configuration=]. : [=aggregatable attribution report/trigger context ID=] :: |trigger|'s [=attribution trigger/trigger context ID=] + : [=aggregatable attribution report/source identifier=] + :: |source|'s [=attribution source/source identifier=]. 1. Return |report|.

    Generating randomized null attribution reports

    diff --git a/params/chromium-params.md b/params/chromium-params.md index 3480cf1d13..a42d1b877c 100644 --- a/params/chromium-params.md +++ b/params/chromium-params.md @@ -17,6 +17,7 @@ Chromium's implementation assigns the following values: | [Max destinations covered by unexpired sources][] | [100][max destinations covered by unexpired sources value] | | [Destination rate-limit window][] | [1 minute][destination rate-limit window value] | [Max destinations per rate-limit window][] | [50][max destinations per rate-limit window per reporting site] per reporting site, [200][max destinations per rate-limit window total] total +| [Max destinations per reporting site per day][] | [100] | [Max source reporting origins per rate-limit window][] | [100][max source reporting origins per rate-limit window value] | | [Max source reporting origins per source reporting site][] | [1][max source reporting origins per source reporting site value] | [Origin rate-limit window][] | [1 day][origin rate-limit window value] @@ -48,6 +49,7 @@ Chromium's implementation assigns the following values: [Max destinations per rate-limit window]: https://wicg.github.io/attribution-reporting-api/#max-destinations-per-rate-limit-window [Max destinations per rate-limit window per reporting site]: https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:content/browser/attribution_reporting/destination_throttler.h;l=29;drc=1890f3f74c8100eb1a3e945d34d6fd576d2a9061 [Max destinations per rate-limit window total]: https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:content/browser/attribution_reporting/destination_throttler.h;l=28;drc=1890f3f74c8100eb1a3e945d34d6fd576d2a9061 +[Max destinations per reporting site per day]: https://wicg.github.io/attribution-reporting-api/#max-destinations-per-reporting-site-per-day [Max source reporting origins per rate-limit window]: https://wicg.github.io/attribution-reporting-api/#max-source-reporting-origins-per-rate-limit-window [max source reporting origins per rate-limit window value]: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/attribution_reporting/attribution_config.h;l=28;drc=3733a639d724a4353463a872605119d11a1e4d37 [Max source reporting origins per source reporting site]: https://wicg.github.io/attribution-reporting-api/#max-source-reporting-origins-per-source-reporting-site diff --git a/ts/src/header-validator/source.test.ts b/ts/src/header-validator/source.test.ts index 5ec1efd9bc..8553151dcf 100644 --- a/ts/src/header-validator/source.test.ts +++ b/ts/src/header-validator/source.test.ts @@ -34,6 +34,7 @@ const testCases: TestCase[] = [ "debug_key": "1", "debug_reporting": true, "destination": "https://a.test", + "destination_limit_priority": "1", "event_report_window": "3601", "expiry": "86400", "filter_data": {"b": ["c"]}, @@ -57,6 +58,7 @@ const testCases: TestCase[] = [ debugKey: 1n, debugReporting: true, destination: new Set(['https://a.test']), + destinationLimitPriority: 1n, eventLevelEpsilon: 14, expiry: 86400, filterData: new Map([['b', new Set(['c'])]]), @@ -1264,6 +1266,32 @@ const testCases: TestCase[] = [ }, ], }, + { + name: 'destination-limit-priority-wrong-type', + json: `{ + "destination": "https://a.test", + "destination_limit_priority": 1 + }`, + expectedErrors: [ + { + path: ['destination_limit_priority'], + msg: 'must be a string', + }, + ], + }, + { + name: 'destination-limit-priority-wrong-format', + json: `{ + "destination": "https://a.test", + "destination_limit_priority": "x" + }`, + expectedErrors: [ + { + path: ['destination_limit_priority'], + msg: 'string must represent an integer (must match /^-?[0-9]+$/)', + }, + ], + }, { name: 'channel-capacity-default-event', diff --git a/ts/src/header-validator/to-json.ts b/ts/src/header-validator/to-json.ts index 1d5c01ea74..ceb6b237aa 100644 --- a/ts/src/header-validator/to-json.ts +++ b/ts/src/header-validator/to-json.ts @@ -174,6 +174,7 @@ export type Source = CommonDebug & aggregation_keys: { [key: string]: string } aggregatable_report_window: number destination: string[] + destination_limit_priority: string event_level_epsilon: number expiry: number filter_data: { [key: string]: string[] } @@ -205,6 +206,7 @@ export function serializeSource(s: parsed.Source, fullFlex: boolean): Source { aggregatable_report_window: s.aggregatableReportWindow, destination: Array.from(s.destination), + destination_limit_priority: s.destinationLimitPriority.toString(), event_level_epsilon: s.eventLevelEpsilon, expiry: s.expiry, max_event_level_reports: s.maxEventLevelReports, diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 75669cc910..2767fd7185 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -1047,6 +1047,7 @@ export type Source = CommonDebug & eventLevelEpsilon: number aggregatableDebugReporting: SourceAggregatableDebugReportingConfig | null + destinationLimitPriority: bigint } function source(j: Json, ctx: SourceContext): Maybe { @@ -1126,6 +1127,11 @@ function source(j: Json, ctx: SourceContext): Maybe { triggerDataMatching, TriggerDataMatching.modulus ), + destinationLimitPriority: field( + 'destination_limit_priority', + int64, + 0n + ), ...commonDebugFields, ...priorityField, diff --git a/verbose_debugging_reports.md b/verbose_debugging_reports.md index 5d98fed787..e556e77f26 100644 --- a/verbose_debugging_reports.md +++ b/verbose_debugging_reports.md @@ -29,6 +29,9 @@ is rejected due to the following limits to mitigate security concerns: #### `source-destination-rate-limit` A source is rejected due to the [destinations per source and reporting site rate limit](https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#limiting-the-number-of-unique-destinations-per-source-site). +#### `source-destination-per-day-rate-limit` +A source is rejected due to the [destinations per source and reporting site per day rate limit](https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#limiting-the-number-of-unique-destinations-covered-by-unexpired-sources). + #### `source-unknown-error` System error. @@ -134,34 +137,39 @@ otherwise the dictionary may include the following fields: * `source_site`: The site on which source was registered, e.g. `"https://source.example"`. * `trigger_debug_key`: The debug key in the trigger registration, omitted if not set. +If `type` is [`source-success`](#source-success) or [`source-noised`](#source-noised), the `body` +dictionary may include a `source_destination_limit` field if the [destination limit](https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#limiting-the-number-of-unique-destinations-covered-by-unexpired-sources) +was exceeded. + This table defines the fields in the `body` dictionary. -| `type` | `attribution_destination`| `limit` | `source_debug_key` | `source_event_id` | `source_site` | `trigger_debug_key` | -| --- | --- | --- | --- | --- | --- | --- | -| [`source-channel-capacity-limit`](#source-channel-capacity-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | -| [`source-destination-limit`](#source-destination-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | -| [`source-destination-rate-limit`](#source-destination-rate-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | -| [`source-noised`](#source-noised) | ✓ | ❌ | ✓ | ✓ | ✓ | ❌ | -| [`source-reporting-origin-per-site-limit`](#source-reporting-origin-per-site-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | -| [`source-storage-limit`](#source-storage-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | -| [`source-success`](#source-success) | ✓ | ❌ | ✓ | ✓ | ✓ | ❌ | -| [`source-trigger-state-cardinality-limit`](#source-trigger-state-cardinality-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | -| [`source-unknown-error`](#source-unknown-error) | ✓ | ❌ | ✓ | ✓ | ✓ | ❌ | -| [`trigger-no-matching-source`](#trigger-no-matching-source) | ✓ | ❌ | ❌ | ❌ | ❌ | ✓ | -| [`trigger-no-matching-filter-data`](#trigger-no-matching-filter-data) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-event-attributions-per-source-destination-limit`](#trigger-event-attributions-per-source-destination-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-aggregate-attributions-per-source-destination-limit`](#trigger-aggregate-attributions-per-source-destination-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-reporting-origin-limit`](#trigger-reporting-origin-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-event-deduplicated`](#trigger-event-deduplicated) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-event-no-matching-configurations`](#trigger-event-no-matching-configurations) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-event-noise`](#trigger-event-noise) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-event-storage-limit`](#trigger-event-storage-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-event-report-window-not-started`](#trigger-event-report-window-not-started) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-event-report-window-passed`](#trigger-event-report-window-passed) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-aggregate-deduplicated`](#trigger-aggregate-deduplicated) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-aggregate-excessive-reports`](#trigger-aggregate-excessive-reports) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-aggregate-no-contributions`](#trigger-aggregate-no-contributions) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-aggregate-insufficient-budget`](#trigger-aggregate-insufficient-budget) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-aggregate-storage-limit`](#trigger-aggregate-storage-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-aggregate-report-window-passed`](#trigger-aggregate-report-window-passed) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | -| [`trigger-unknown-error`](#trigger-unknown-error) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | +| `type` | `attribution_destination`| `limit` | `source_debug_key` | `source_event_id` | `source_site` | `trigger_debug_key` | `source_destination_limit` | +| --- | --- | --- | --- | --- | --- | --- | --- | +| [`source-channel-capacity-limit`](#source-channel-capacity-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`source-destination-limit`](#source-destination-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`source-destination-per-day-rate-limit`](#source-destination-per-day-rate-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`source-destination-rate-limit`](#source-destination-rate-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`source-noised`](#source-noised) | ✓ | ❌ | ✓ | ✓ | ✓ | ❌ | ✓ | +| [`source-reporting-origin-per-site-limit`](#source-reporting-origin-per-site-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`source-storage-limit`](#source-storage-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`source-success`](#source-success) | ✓ | ❌ | ✓ | ✓ | ✓ | ❌ | ✓ | +| [`source-trigger-state-cardinality-limit`](#source-trigger-state-cardinality-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`source-unknown-error`](#source-unknown-error) | ✓ | ❌ | ✓ | ✓ | ✓ | ❌ | ❌ | +| [`trigger-no-matching-source`](#trigger-no-matching-source) | ✓ | ❌ | ❌ | ❌ | ❌ | ✓ | ❌ | +| [`trigger-no-matching-filter-data`](#trigger-no-matching-filter-data) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-event-attributions-per-source-destination-limit`](#trigger-event-attributions-per-source-destination-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-aggregate-attributions-per-source-destination-limit`](#trigger-aggregate-attributions-per-source-destination-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-reporting-origin-limit`](#trigger-reporting-origin-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-event-deduplicated`](#trigger-event-deduplicated) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-event-no-matching-configurations`](#trigger-event-no-matching-configurations) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-event-noise`](#trigger-event-noise) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-event-storage-limit`](#trigger-event-storage-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-event-report-window-not-started`](#trigger-event-report-window-not-started) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-event-report-window-passed`](#trigger-event-report-window-passed) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-aggregate-deduplicated`](#trigger-aggregate-deduplicated) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-aggregate-excessive-reports`](#trigger-aggregate-excessive-reports) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-aggregate-no-contributions`](#trigger-aggregate-no-contributions) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-aggregate-insufficient-budget`](#trigger-aggregate-insufficient-budget) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-aggregate-storage-limit`](#trigger-aggregate-storage-limit) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-aggregate-report-window-passed`](#trigger-aggregate-report-window-passed) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ | +| [`trigger-unknown-error`](#trigger-unknown-error) | ✓ | ❌ | ✓ | ✓ | ✓ | ✓ | ❌ |