cookiebanner: capture script initiator URL on detected trackers
When third-party JS sets a cookie or writes to local/sessionStorage inside a customer page, the SDK now walks the synchronous call stack to find the first non-extension, non-Probo, non-first-party http(s) URL. That origin+path is sent as initiator_url on the report payload, persisted in a new nullable column on detected_trackers, and preserved across upserts via COALESCE. This unlocks per-vendor attribution for cookies and storage writes without needing pattern name matching, so future categorisation logic can simply look up the initiator URL in the existing tracker_resources table and inherit that vendor's category. GraphQL/MCP exposure is intentionally deferred -- the column is captured now, surfaced later. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -16,11 +16,13 @@ import { isDeletion, parseCookieName, parseMaxAgeSeconds } from "../cookie-utils
|
|||||||
import type { Detector } from "./detector";
|
import type { Detector } from "./detector";
|
||||||
import { NotFoundError } from "../errors";
|
import { NotFoundError } from "../errors";
|
||||||
import { fetchJSON } from "../http";
|
import { fetchJSON } from "../http";
|
||||||
|
import { getInitiatorURL } from "./initiator";
|
||||||
|
|
||||||
interface DetectedCookieEntry {
|
interface DetectedCookieEntry {
|
||||||
name: string;
|
name: string;
|
||||||
max_age_seconds: number | null;
|
max_age_seconds: number | null;
|
||||||
source: "script" | "pre-existing" | "http";
|
source: "script" | "pre-existing" | "http";
|
||||||
|
initiator_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEBOUNCE_MS = 2_000;
|
const DEBOUNCE_MS = 2_000;
|
||||||
@@ -34,6 +36,7 @@ function isExtensionCaller(): boolean {
|
|||||||
|
|
||||||
export class CookieDetector implements Detector {
|
export class CookieDetector implements Detector {
|
||||||
private readonly reportUrl: URL;
|
private readonly reportUrl: URL;
|
||||||
|
private readonly proboOrigin: string;
|
||||||
private readonly knownNames: Set<string>;
|
private readonly knownNames: Set<string>;
|
||||||
private readonly reported: Set<string> = new Set();
|
private readonly reported: Set<string> = new Set();
|
||||||
private readonly pending: Map<string, DetectedCookieEntry> = new Map();
|
private readonly pending: Map<string, DetectedCookieEntry> = new Map();
|
||||||
@@ -43,6 +46,7 @@ export class CookieDetector implements Detector {
|
|||||||
|
|
||||||
constructor(baseUrl: URL, bannerId: string, knownNames: Set<string>) {
|
constructor(baseUrl: URL, bannerId: string, knownNames: Set<string>) {
|
||||||
this.reportUrl = new URL(`${bannerId}/report`, baseUrl);
|
this.reportUrl = new URL(`${bannerId}/report`, baseUrl);
|
||||||
|
this.proboOrigin = baseUrl.origin;
|
||||||
this.knownNames = knownNames;
|
this.knownNames = knownNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +107,16 @@ export class CookieDetector implements Detector {
|
|||||||
if (!name || this.knownNames.has(name) || this.reported.has(name)) return;
|
if (!name || this.knownNames.has(name) || this.reported.has(name)) return;
|
||||||
|
|
||||||
const maxAgeSeconds = parseMaxAgeSeconds(raw);
|
const maxAgeSeconds = parseMaxAgeSeconds(raw);
|
||||||
|
const initiatorUrl = getInitiatorURL(this.proboOrigin);
|
||||||
|
|
||||||
this.reported.add(name);
|
this.reported.add(name);
|
||||||
this.pending.set(name, { name, max_age_seconds: maxAgeSeconds, source: "script" });
|
const entry: DetectedCookieEntry = {
|
||||||
|
name,
|
||||||
|
max_age_seconds: maxAgeSeconds,
|
||||||
|
source: "script",
|
||||||
|
};
|
||||||
|
if (initiatorUrl) entry.initiator_url = initiatorUrl;
|
||||||
|
this.pending.set(name, entry);
|
||||||
this.scheduleFlush();
|
this.scheduleFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
packages/cookie-banner/src/detectors/initiator.ts
Normal file
57
packages/cookie-banner/src/detectors/initiator.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
const EXTENSION_URL_RE = /(?:chrome|moz|safari-web)-extension:\/\//;
|
||||||
|
const STACK_URL_RE = /https?:\/\/[^\s)'"`]+/g;
|
||||||
|
const LINE_COL_SUFFIX_RE = /:\d+(?::\d+)?$/;
|
||||||
|
const MAX_INITIATOR_URL_LENGTH = 1024;
|
||||||
|
|
||||||
|
// getInitiatorURL walks the current call stack and returns the first
|
||||||
|
// third-party script URL (as origin+pathname). It deliberately skips:
|
||||||
|
// - browser extension URLs (chrome/moz/safari-web-extension://)
|
||||||
|
// - the Probo SDK's own origin (instrumentation frames)
|
||||||
|
// - the page's own origin (we want the third-party loader, not first-party code)
|
||||||
|
//
|
||||||
|
// Returns null when no third-party frame is found (anonymous/eval/inline
|
||||||
|
// scripts, or writes originating from the page itself).
|
||||||
|
export function getInitiatorURL(proboOrigin: string): string | null {
|
||||||
|
const stack = new Error().stack;
|
||||||
|
if (!stack) return null;
|
||||||
|
|
||||||
|
STACK_URL_RE.lastIndex = 0;
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
while ((m = STACK_URL_RE.exec(stack)) !== null) {
|
||||||
|
const raw = m[0];
|
||||||
|
if (EXTENSION_URL_RE.test(raw)) continue;
|
||||||
|
|
||||||
|
const cleaned = raw.replace(LINE_COL_SUFFIX_RE, "");
|
||||||
|
|
||||||
|
let parsed: URL;
|
||||||
|
try {
|
||||||
|
parsed = new URL(cleaned);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.origin === proboOrigin) continue;
|
||||||
|
if (parsed.origin === location.origin) continue;
|
||||||
|
|
||||||
|
const result = parsed.origin + parsed.pathname;
|
||||||
|
if (result.length > MAX_INITIATOR_URL_LENGTH) {
|
||||||
|
return result.slice(0, MAX_INITIATOR_URL_LENGTH);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -15,11 +15,13 @@
|
|||||||
import type { Detector } from "./detector";
|
import type { Detector } from "./detector";
|
||||||
import { NotFoundError } from "../errors";
|
import { NotFoundError } from "../errors";
|
||||||
import { fetchJSON } from "../http";
|
import { fetchJSON } from "../http";
|
||||||
|
import { getInitiatorURL } from "./initiator";
|
||||||
|
|
||||||
interface DetectedStorageEntry {
|
interface DetectedStorageEntry {
|
||||||
key: string;
|
key: string;
|
||||||
storage_type: "local_storage" | "session_storage" | "indexed_db";
|
storage_type: "local_storage" | "session_storage" | "indexed_db";
|
||||||
value_size: number | null;
|
value_size: number | null;
|
||||||
|
initiator_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEBOUNCE_MS = 2_000;
|
const DEBOUNCE_MS = 2_000;
|
||||||
@@ -34,6 +36,7 @@ function isExtensionCaller(): boolean {
|
|||||||
|
|
||||||
export class StorageDetector implements Detector {
|
export class StorageDetector implements Detector {
|
||||||
private readonly reportUrl: URL;
|
private readonly reportUrl: URL;
|
||||||
|
private readonly proboOrigin: string;
|
||||||
private readonly reported: Set<string> = new Set();
|
private readonly reported: Set<string> = new Set();
|
||||||
private readonly pending: Map<string, DetectedStorageEntry> = new Map();
|
private readonly pending: Map<string, DetectedStorageEntry> = new Map();
|
||||||
private timer: ReturnType<typeof setTimeout> | null = null;
|
private timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
@@ -42,6 +45,7 @@ export class StorageDetector implements Detector {
|
|||||||
|
|
||||||
constructor(baseUrl: URL, bannerId: string) {
|
constructor(baseUrl: URL, bannerId: string) {
|
||||||
this.reportUrl = new URL(`${bannerId}/report`, baseUrl);
|
this.reportUrl = new URL(`${bannerId}/report`, baseUrl);
|
||||||
|
this.proboOrigin = baseUrl.origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
@@ -113,12 +117,16 @@ export class StorageDetector implements Detector {
|
|||||||
const reportKey = `${storageType}:${key}`;
|
const reportKey = `${storageType}:${key}`;
|
||||||
if (this.reported.has(reportKey)) return;
|
if (this.reported.has(reportKey)) return;
|
||||||
|
|
||||||
|
const initiatorUrl = getInitiatorURL(this.proboOrigin);
|
||||||
|
|
||||||
this.reported.add(reportKey);
|
this.reported.add(reportKey);
|
||||||
this.pending.set(reportKey, {
|
const entry: DetectedStorageEntry = {
|
||||||
key,
|
key,
|
||||||
storage_type: storageType,
|
storage_type: storageType,
|
||||||
value_size: value.length * 2,
|
value_size: value.length * 2,
|
||||||
});
|
};
|
||||||
|
if (initiatorUrl) entry.initiator_url = initiatorUrl;
|
||||||
|
this.pending.set(reportKey, entry);
|
||||||
this.scheduleFlush();
|
this.scheduleFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ type (
|
|||||||
Name string
|
Name string
|
||||||
MaxAgeSeconds *int
|
MaxAgeSeconds *int
|
||||||
Source coredata.CookieSource
|
Source coredata.CookieSource
|
||||||
|
InitiatorURL *string
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportDetectedCookiesRequest struct {
|
ReportDetectedCookiesRequest struct {
|
||||||
@@ -120,9 +121,10 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
DetectedStorageItem struct {
|
DetectedStorageItem struct {
|
||||||
Key string
|
Key string
|
||||||
StorageType coredata.TrackerType
|
StorageType coredata.TrackerType
|
||||||
ValueSize *int
|
ValueSize *int
|
||||||
|
InitiatorURL *string
|
||||||
}
|
}
|
||||||
|
|
||||||
DetectedResourceItem struct {
|
DetectedResourceItem struct {
|
||||||
@@ -2035,6 +2037,7 @@ func (s *Service) ReportDetectedTrackers(
|
|||||||
Identifier: dc.Name,
|
Identifier: dc.Name,
|
||||||
MaxAgeSeconds: dc.MaxAgeSeconds,
|
MaxAgeSeconds: dc.MaxAgeSeconds,
|
||||||
Source: &dc.Source,
|
Source: &dc.Source,
|
||||||
|
InitiatorURL: dc.InitiatorURL,
|
||||||
},
|
},
|
||||||
&inserted,
|
&inserted,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -2051,9 +2054,10 @@ func (s *Service) ReportDetectedTrackers(
|
|||||||
uncategorised.ID,
|
uncategorised.ID,
|
||||||
now,
|
now,
|
||||||
detectedTrackerInfo{
|
detectedTrackerInfo{
|
||||||
TrackerType: ds.StorageType,
|
TrackerType: ds.StorageType,
|
||||||
Identifier: ds.Key,
|
Identifier: ds.Key,
|
||||||
ValueSize: ds.ValueSize,
|
ValueSize: ds.ValueSize,
|
||||||
|
InitiatorURL: ds.InitiatorURL,
|
||||||
},
|
},
|
||||||
&inserted,
|
&inserted,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -2096,6 +2100,7 @@ type detectedTrackerInfo struct {
|
|||||||
MaxAgeSeconds *int
|
MaxAgeSeconds *int
|
||||||
Source *coredata.CookieSource
|
Source *coredata.CookieSource
|
||||||
ValueSize *int
|
ValueSize *int
|
||||||
|
InitiatorURL *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) reportDetectedTracker(
|
func (s *Service) reportDetectedTracker(
|
||||||
@@ -2168,6 +2173,7 @@ func (s *Service) reportDetectedTracker(
|
|||||||
MaxAgeSeconds: info.MaxAgeSeconds,
|
MaxAgeSeconds: info.MaxAgeSeconds,
|
||||||
Source: info.Source,
|
Source: info.Source,
|
||||||
ValueSize: info.ValueSize,
|
ValueSize: info.ValueSize,
|
||||||
|
InitiatorURL: info.InitiatorURL,
|
||||||
LastDetectedAt: now,
|
LastDetectedAt: now,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type (
|
|||||||
MaxAgeSeconds *int `db:"max_age_seconds"`
|
MaxAgeSeconds *int `db:"max_age_seconds"`
|
||||||
Source *CookieSource `db:"source"`
|
Source *CookieSource `db:"source"`
|
||||||
ValueSize *int `db:"value_size"`
|
ValueSize *int `db:"value_size"`
|
||||||
|
InitiatorURL *string `db:"initiator_url"`
|
||||||
LastDetectedAt time.Time `db:"last_detected_at"`
|
LastDetectedAt time.Time `db:"last_detected_at"`
|
||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
UpdatedAt time.Time `db:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
@@ -59,6 +60,7 @@ INSERT INTO detected_trackers (
|
|||||||
max_age_seconds,
|
max_age_seconds,
|
||||||
source,
|
source,
|
||||||
value_size,
|
value_size,
|
||||||
|
initiator_url,
|
||||||
last_detected_at,
|
last_detected_at,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
@@ -72,6 +74,7 @@ INSERT INTO detected_trackers (
|
|||||||
@max_age_seconds,
|
@max_age_seconds,
|
||||||
@source,
|
@source,
|
||||||
@value_size,
|
@value_size,
|
||||||
|
@initiator_url,
|
||||||
@last_detected_at,
|
@last_detected_at,
|
||||||
@created_at,
|
@created_at,
|
||||||
@updated_at
|
@updated_at
|
||||||
@@ -83,6 +86,7 @@ ON CONFLICT (cookie_banner_id, tracker_type, identifier) DO UPDATE
|
|||||||
) THEN EXCLUDED.source
|
) THEN EXCLUDED.source
|
||||||
ELSE detected_trackers.source
|
ELSE detected_trackers.source
|
||||||
END,
|
END,
|
||||||
|
initiator_url = COALESCE(EXCLUDED.initiator_url, detected_trackers.initiator_url),
|
||||||
updated_at = EXCLUDED.updated_at
|
updated_at = EXCLUDED.updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -97,6 +101,7 @@ ON CONFLICT (cookie_banner_id, tracker_type, identifier) DO UPDATE
|
|||||||
"source": dt.Source,
|
"source": dt.Source,
|
||||||
"source_script": CookieSourceScript,
|
"source_script": CookieSourceScript,
|
||||||
"value_size": dt.ValueSize,
|
"value_size": dt.ValueSize,
|
||||||
|
"initiator_url": dt.InitiatorURL,
|
||||||
"last_detected_at": dt.LastDetectedAt,
|
"last_detected_at": dt.LastDetectedAt,
|
||||||
"created_at": dt.CreatedAt,
|
"created_at": dt.CreatedAt,
|
||||||
"updated_at": dt.UpdatedAt,
|
"updated_at": dt.UpdatedAt,
|
||||||
|
|||||||
19
pkg/coredata/migrations/20260511T063353Z.sql
Normal file
19
pkg/coredata/migrations/20260511T063353Z.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
-- Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
-- purpose with or without fee is hereby granted, provided that the above
|
||||||
|
-- copyright notice and this permission notice appear in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
-- PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
-- Capture the third-party script URL (origin+path) that triggered a
|
||||||
|
-- detected cookie or storage write, so the auto-categorisation worker
|
||||||
|
-- can attribute the artifact to a known vendor via the existing
|
||||||
|
-- tracker_resources table.
|
||||||
|
ALTER TABLE detected_trackers ADD COLUMN initiator_url TEXT;
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -221,16 +222,45 @@ func (h *Handler) handlePostConsent(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type detectedCookieEntry struct {
|
type detectedCookieEntry struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
MaxAgeSeconds *int `json:"max_age_seconds"`
|
MaxAgeSeconds *int `json:"max_age_seconds"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
|
InitiatorURL *string `json:"initiator_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type reportDetectedCookiesBody struct {
|
type reportDetectedCookiesBody struct {
|
||||||
Cookies []detectedCookieEntry `json:"cookies"`
|
Cookies []detectedCookieEntry `json:"cookies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxDetectedCookiesPerRequest = 100
|
const (
|
||||||
|
maxDetectedCookiesPerRequest = 100
|
||||||
|
maxInitiatorURLLength = 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// sanitizeInitiatorURL validates and normalises a script URL captured
|
||||||
|
// in the customer page's stack trace. It drops the value when it does
|
||||||
|
// not parse as an http(s) URL with a host, or when it exceeds the
|
||||||
|
// length cap. Returns nil for missing or invalid input.
|
||||||
|
func sanitizeInitiatorURL(raw *string) *string {
|
||||||
|
if raw == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := strings.TrimSpace(*raw)
|
||||||
|
if s == "" || len(s) > maxInitiatorURLLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if u.Host == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Request) {
|
||||||
bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID"))
|
bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID"))
|
||||||
@@ -278,6 +308,7 @@ func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Req
|
|||||||
Name: name,
|
Name: name,
|
||||||
MaxAgeSeconds: c.MaxAgeSeconds,
|
MaxAgeSeconds: c.MaxAgeSeconds,
|
||||||
Source: source,
|
Source: source,
|
||||||
|
InitiatorURL: sanitizeInitiatorURL(c.InitiatorURL),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -306,9 +337,10 @@ func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
type detectedStorageEntry struct {
|
type detectedStorageEntry struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
StorageType string `json:"storage_type"`
|
StorageType string `json:"storage_type"`
|
||||||
ValueSize *int `json:"value_size"`
|
ValueSize *int `json:"value_size"`
|
||||||
|
InitiatorURL *string `json:"initiator_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type detectedResourceEntry struct {
|
type detectedResourceEntry struct {
|
||||||
@@ -372,6 +404,7 @@ func (h *Handler) handleReportDetectedTrackers(w http.ResponseWriter, r *http.Re
|
|||||||
Name: name,
|
Name: name,
|
||||||
MaxAgeSeconds: c.MaxAgeSeconds,
|
MaxAgeSeconds: c.MaxAgeSeconds,
|
||||||
Source: source,
|
Source: source,
|
||||||
|
InitiatorURL: sanitizeInitiatorURL(c.InitiatorURL),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -397,9 +430,10 @@ func (h *Handler) handleReportDetectedTrackers(w http.ResponseWriter, r *http.Re
|
|||||||
req.Storage = append(
|
req.Storage = append(
|
||||||
req.Storage,
|
req.Storage,
|
||||||
cookiebanner.DetectedStorageItem{
|
cookiebanner.DetectedStorageItem{
|
||||||
Key: key,
|
Key: key,
|
||||||
StorageType: storageType,
|
StorageType: storageType,
|
||||||
ValueSize: s.ValueSize,
|
ValueSize: s.ValueSize,
|
||||||
|
InitiatorURL: sanitizeInitiatorURL(s.InitiatorURL),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user