@solvenza/cookies-compliance 1.2.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +960 -36
- package/bin/audit-cli.js +134 -13
- package/dist/audit.cjs +811 -0
- package/dist/audit.cjs.map +1 -0
- package/dist/audit.d.cts +60 -0
- package/dist/audit.d.ts +60 -0
- package/dist/audit.js +380 -0
- package/dist/audit.js.map +1 -0
- package/dist/chunk-C5QPXHTH.js +3557 -0
- package/dist/chunk-C5QPXHTH.js.map +1 -0
- package/dist/chunk-OHOHIAK6.js +442 -0
- package/dist/chunk-OHOHIAK6.js.map +1 -0
- package/dist/chunk-Q3FZFXQE.js +89 -0
- package/dist/chunk-UQU54ANZ.js +225 -0
- package/dist/chunk-UQU54ANZ.js.map +1 -0
- package/dist/chunk-W5MG56VI.js +269 -0
- package/dist/chunk-W5MG56VI.js.map +1 -0
- package/dist/consent.esm.cjs +2195 -84
- package/dist/consent.esm.cjs.map +1 -1
- package/dist/consent.esm.d.cts +173 -6
- package/dist/consent.esm.d.ts +173 -6
- package/dist/consent.esm.js +57 -23
- package/dist/consent.esm.js.map +1 -1
- package/dist/consent.min.js +437 -55
- package/dist/consent.min.js.map +1 -1
- package/dist/presets.cjs +495 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +51 -0
- package/dist/presets.d.ts +51 -0
- package/dist/presets.js +16 -0
- package/dist/presets.js.map +1 -0
- package/dist/types--kyI7OOR.d.cts +314 -0
- package/dist/types--kyI7OOR.d.ts +314 -0
- package/dist/wrappers/angular.cjs +2341 -65
- package/dist/wrappers/angular.cjs.map +1 -1
- package/dist/wrappers/angular.d.cts +85 -5
- package/dist/wrappers/angular.d.ts +85 -5
- package/dist/wrappers/angular.js +217 -3
- package/dist/wrappers/angular.js.map +1 -1
- package/dist/wrappers/next.cjs +2353 -103
- package/dist/wrappers/next.cjs.map +1 -1
- package/dist/wrappers/next.d.cts +25 -1
- package/dist/wrappers/next.d.ts +25 -1
- package/dist/wrappers/next.js +30 -5
- package/dist/wrappers/next.js.map +1 -1
- package/dist/wrappers/nuxt.cjs +4171 -0
- package/dist/wrappers/nuxt.cjs.map +1 -0
- package/dist/wrappers/nuxt.d.cts +33 -0
- package/dist/wrappers/nuxt.d.ts +33 -0
- package/dist/wrappers/nuxt.js +51 -0
- package/dist/wrappers/nuxt.js.map +1 -0
- package/dist/wrappers/react.cjs +2336 -141
- package/dist/wrappers/react.cjs.map +1 -1
- package/dist/wrappers/react.d.cts +87 -1
- package/dist/wrappers/react.d.ts +87 -1
- package/dist/wrappers/react.js +21 -51
- package/dist/wrappers/react.js.map +1 -1
- package/dist/wrappers/vue.cjs +4163 -0
- package/dist/wrappers/vue.cjs.map +1 -0
- package/dist/wrappers/vue.d.cts +144 -0
- package/dist/wrappers/vue.d.ts +144 -0
- package/dist/wrappers/vue.js +28 -0
- package/dist/wrappers/vue.js.map +1 -0
- package/dist/wrappers/wordpress.js +1 -1
- package/package.json +26 -5
- package/schema.json +143 -4
- package/dist/chunk-DDAAVRWG.js +0 -25
- package/dist/chunk-FBRP4MSA.js +0 -1907
- package/dist/chunk-FBRP4MSA.js.map +0 -1
- package/dist/consent-engine-DOMyEKfk.d.cts +0 -193
- package/dist/consent-engine-DOMyEKfk.d.ts +0 -193
- /package/dist/{chunk-DDAAVRWG.js.map → chunk-Q3FZFXQE.js.map} +0 -0
package/dist/audit.cjs
ADDED
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __export = (target, all) => {
|
|
26
|
+
for (var name in all)
|
|
27
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
28
|
+
};
|
|
29
|
+
var __copyProps = (to, from, except, desc) => {
|
|
30
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
31
|
+
for (let key of __getOwnPropNames(from))
|
|
32
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
33
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
34
|
+
}
|
|
35
|
+
return to;
|
|
36
|
+
};
|
|
37
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
38
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
39
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
40
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
41
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
42
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
43
|
+
mod
|
|
44
|
+
));
|
|
45
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
46
|
+
|
|
47
|
+
// src/audit/index.ts
|
|
48
|
+
var audit_exports = {};
|
|
49
|
+
__export(audit_exports, {
|
|
50
|
+
LssiAuditor: () => LssiAuditor
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(audit_exports);
|
|
53
|
+
|
|
54
|
+
// src/audit/auditor.ts
|
|
55
|
+
var fs = __toESM(require("fs"), 1);
|
|
56
|
+
var path = __toESM(require("path"), 1);
|
|
57
|
+
|
|
58
|
+
// src/presets/presets-catalog.ts
|
|
59
|
+
var SERVICE_PRESETS = {
|
|
60
|
+
// --- ANALYTICS ---
|
|
61
|
+
ga4: {
|
|
62
|
+
id: "ga4",
|
|
63
|
+
category: "analytics",
|
|
64
|
+
label: "Google Analytics 4",
|
|
65
|
+
provider: "Google LLC",
|
|
66
|
+
description: "Medici\xF3n de visitas, p\xE1ginas vistas, eventos y comportamiento de navegaci\xF3n del usuario.",
|
|
67
|
+
policyUrl: "https://policies.google.com/privacy",
|
|
68
|
+
cookies: [
|
|
69
|
+
{ name: "_ga", duration: "2 a\xF1os", purpose: "Distinguir a los usuarios \xFAnicos en el sitio web." },
|
|
70
|
+
{ name: "_ga_*", duration: "2 a\xF1os", purpose: "Mantener el estado de la sesi\xF3n de Google Analytics 4." },
|
|
71
|
+
{ name: "_gid", duration: "24 horas", purpose: "Distinguir a los usuarios durante un \xFAnico d\xEDa." },
|
|
72
|
+
{ name: "_gat*", duration: "1 minuto", purpose: "Limitar el porcentaje de solicitudes a Google Analytics." }
|
|
73
|
+
],
|
|
74
|
+
storageKeys: ["_ga_*", "google_analytics_*"],
|
|
75
|
+
localStorage: ["_ga_*", "google_analytics_*"]
|
|
76
|
+
},
|
|
77
|
+
google_analytics: {
|
|
78
|
+
id: "google_analytics",
|
|
79
|
+
category: "analytics",
|
|
80
|
+
label: "Google Analytics (Universal / GA4)",
|
|
81
|
+
provider: "Google LLC",
|
|
82
|
+
description: "Medici\xF3n de visitas y estad\xEDsticas an\xF3nimas de tr\xE1fico web.",
|
|
83
|
+
policyUrl: "https://policies.google.com/privacy",
|
|
84
|
+
cookies: [
|
|
85
|
+
{ name: "_ga", duration: "2 a\xF1os", purpose: "Distinguir usuarios." },
|
|
86
|
+
{ name: "_ga_*", duration: "2 a\xF1os", purpose: "Estado de la sesi\xF3n GA4." },
|
|
87
|
+
{ name: "_gid", duration: "24 horas", purpose: "Identificador diario." }
|
|
88
|
+
],
|
|
89
|
+
storageKeys: ["_ga_*"]
|
|
90
|
+
},
|
|
91
|
+
posthog: {
|
|
92
|
+
id: "posthog",
|
|
93
|
+
category: "analytics",
|
|
94
|
+
label: "PostHog Analytics",
|
|
95
|
+
provider: "PostHog, Inc.",
|
|
96
|
+
description: "Anal\xEDtica de producto, registro de sesiones, mapas de ruta y embudos de interacci\xF3n.",
|
|
97
|
+
policyUrl: "https://posthog.com/privacy",
|
|
98
|
+
cookies: [
|
|
99
|
+
{ name: "ph_*_posthog", duration: "1 a\xF1o", purpose: "Persistencia de usuario e identificaci\xF3n de sesiones en PostHog." }
|
|
100
|
+
],
|
|
101
|
+
storageKeys: ["ph_*_posthog"],
|
|
102
|
+
localStorage: ["ph_*_posthog"],
|
|
103
|
+
sessionStorage: ["ph_*_posthog"]
|
|
104
|
+
},
|
|
105
|
+
hotjar: {
|
|
106
|
+
id: "hotjar",
|
|
107
|
+
category: "analytics",
|
|
108
|
+
label: "Hotjar Heatmaps & Feedback",
|
|
109
|
+
provider: "Hotjar Ltd.",
|
|
110
|
+
description: "Mapas de calor de clics, desplazamientos, grabaciones de comportamiento y encuestas.",
|
|
111
|
+
policyUrl: "https://www.hotjar.com/legal/policies/privacy/",
|
|
112
|
+
cookies: [
|
|
113
|
+
{ name: "_hjSession*", duration: "30 minutos", purpose: "Mantener los datos de la sesi\xF3n actual del usuario en Hotjar." },
|
|
114
|
+
{ name: "_hjSessionUser*", duration: "1 a\xF1o", purpose: "Mantener el ID de usuario an\xF3nimo en el navegador." },
|
|
115
|
+
{ name: "_hjIncludedIn*", duration: "Sesi\xF3n", purpose: "Determinar si el usuario est\xE1 incluido en el muestreo de datos." },
|
|
116
|
+
{ name: "_hjAbsoluteCanvasUrl", duration: "Sesi\xF3n", purpose: "Almacenar la URL can\xF3nica de visualizaci\xF3n." },
|
|
117
|
+
{ name: "_hjTLDTest", duration: "Sesi\xF3n", purpose: "Verificar el nivel de dominio para cookies." }
|
|
118
|
+
],
|
|
119
|
+
storageKeys: ["_hj*", "hj*"],
|
|
120
|
+
localStorage: ["_hj*"],
|
|
121
|
+
sessionStorage: ["_hj*"]
|
|
122
|
+
},
|
|
123
|
+
clarity: {
|
|
124
|
+
id: "clarity",
|
|
125
|
+
category: "analytics",
|
|
126
|
+
label: "Microsoft Clarity",
|
|
127
|
+
provider: "Microsoft Corporation",
|
|
128
|
+
description: "Grabaci\xF3n de navegaci\xF3n y mapas t\xE9rmicos de interacci\xF3n sin datos personales.",
|
|
129
|
+
policyUrl: "https://privacy.microsoft.com/privacystatement",
|
|
130
|
+
cookies: [
|
|
131
|
+
{ name: "_clck", duration: "1 a\xF1o", purpose: "Persistir el ID de usuario Clarity y sus preferencias de p\xE1gina." },
|
|
132
|
+
{ name: "_clsk", duration: "24 horas", purpose: "Conectar m\xFAltiples visitas de p\xE1gina en una \xFAnica sesi\xF3n." },
|
|
133
|
+
{ name: "CLID", duration: "1 a\xF1o", purpose: "Identificador de navegador para Clarity." },
|
|
134
|
+
{ name: "MUID", duration: "1 a\xF1o", purpose: "Identificador de usuario \xFAnico de Microsoft." }
|
|
135
|
+
],
|
|
136
|
+
storageKeys: ["_cl*"],
|
|
137
|
+
localStorage: ["_cl*"]
|
|
138
|
+
},
|
|
139
|
+
matomo: {
|
|
140
|
+
id: "matomo",
|
|
141
|
+
category: "analytics",
|
|
142
|
+
label: "Matomo Analytics",
|
|
143
|
+
provider: "InnoCraft Ltd.",
|
|
144
|
+
description: "Plataforma de anal\xEDtica web local-first y centrada en la privacidad.",
|
|
145
|
+
policyUrl: "https://matomo.org/privacy-policy/",
|
|
146
|
+
cookies: [
|
|
147
|
+
{ name: "_pk_id*", duration: "13 meses", purpose: "Almacenar datos de usuario \xFAnicos como el ID de visitante." },
|
|
148
|
+
{ name: "_pk_ses*", duration: "30 minutos", purpose: "Almacenar datos temporales de la sesi\xF3n de navegaci\xF3n." },
|
|
149
|
+
{ name: "_pk_ref*", duration: "6 meses", purpose: "Almacenar la informaci\xF3n de atribuci\xF3n de procedencia." }
|
|
150
|
+
],
|
|
151
|
+
storageKeys: ["_pk_*"],
|
|
152
|
+
localStorage: ["_pk_*"]
|
|
153
|
+
},
|
|
154
|
+
plausible: {
|
|
155
|
+
id: "plausible",
|
|
156
|
+
category: "analytics",
|
|
157
|
+
label: "Plausible Analytics",
|
|
158
|
+
provider: "Plausible Insights O\xDC",
|
|
159
|
+
description: "Anal\xEDtica web ligera y respetuosa con la privacidad, 100% libre de cookies.",
|
|
160
|
+
policyUrl: "https://plausible.io/data-policy",
|
|
161
|
+
cookies: [],
|
|
162
|
+
storageKeys: []
|
|
163
|
+
},
|
|
164
|
+
// --- MARKETING & ADVERTISING ---
|
|
165
|
+
meta_pixel: {
|
|
166
|
+
id: "meta_pixel",
|
|
167
|
+
category: "marketing",
|
|
168
|
+
label: "Meta Pixel (Facebook)",
|
|
169
|
+
provider: "Meta Platforms Ireland Ltd.",
|
|
170
|
+
description: "Medici\xF3n de conversiones, personalizaci\xF3n y optimizaci\xF3n de campa\xF1as publicitarias en Facebook e Instagram.",
|
|
171
|
+
policyUrl: "https://www.facebook.com/privacy/policy",
|
|
172
|
+
cookies: [
|
|
173
|
+
{ name: "_fbp", duration: "90 d\xEDas", purpose: "Almacenar y rastrear visitas en los sitios web para segmentaci\xF3n publicitaria." },
|
|
174
|
+
{ name: "_fbc", duration: "90 d\xEDas", purpose: "Guardar el \xFAltimo clic en un anuncio de Facebook (par\xE1metro fbclid)." },
|
|
175
|
+
{ name: "fr", duration: "90 d\xEDas", purpose: "Cookie de publicidad comportamental de Facebook." },
|
|
176
|
+
{ name: "tr", duration: "Sesi\xF3n", purpose: "P\xEDxel de seguimiento de eventos en tiempo real." },
|
|
177
|
+
{ name: "datr", duration: "2 a\xF1os", purpose: "Identificaci\xF3n del navegador web para seguridad y anal\xEDtica publicitaria." }
|
|
178
|
+
],
|
|
179
|
+
storageKeys: ["_fbp*"],
|
|
180
|
+
localStorage: ["_fbp*"]
|
|
181
|
+
},
|
|
182
|
+
facebook_pixel: {
|
|
183
|
+
id: "facebook_pixel",
|
|
184
|
+
category: "marketing",
|
|
185
|
+
label: "Meta Pixel (Facebook)",
|
|
186
|
+
provider: "Meta Platforms Ireland Ltd.",
|
|
187
|
+
description: "Medici\xF3n de conversiones y retargeting en Meta/Facebook.",
|
|
188
|
+
policyUrl: "https://www.facebook.com/privacy/policy",
|
|
189
|
+
cookies: [
|
|
190
|
+
{ name: "_fbp", duration: "90 d\xEDas", purpose: "Rastreo de conversiones y atribuci\xF3n de campa\xF1as." },
|
|
191
|
+
{ name: "_fbc", duration: "90 d\xEDas", purpose: "Identificador de clic de anuncio." }
|
|
192
|
+
],
|
|
193
|
+
storageKeys: ["_fbp*"]
|
|
194
|
+
},
|
|
195
|
+
google_ads: {
|
|
196
|
+
id: "google_ads",
|
|
197
|
+
category: "marketing",
|
|
198
|
+
label: "Google Ads & Remarketing",
|
|
199
|
+
provider: "Google LLC",
|
|
200
|
+
description: "Medici\xF3n de conversiones publicitarias y campa\xF1as de retargeting de Google Ads.",
|
|
201
|
+
policyUrl: "https://policies.google.com/technologies/ads",
|
|
202
|
+
cookies: [
|
|
203
|
+
{ name: "_gcl_au", duration: "90 d\xEDas", purpose: "Medici\xF3n de conversiones publicitarias de Google AdSense y Ads." },
|
|
204
|
+
{ name: "_gcl_aw", duration: "90 d\xEDas", purpose: "Conversiones procedentes de clics en anuncios de Google Ads." },
|
|
205
|
+
{ name: "IDE", duration: "1 a\xF1o", purpose: "Publicidad dirigida y medici\xF3n de rendimiento de DoubleClick." },
|
|
206
|
+
{ name: "DSID", duration: "2 semanas", purpose: "Identificar usuario conectado en sitios web ajenos a Google." },
|
|
207
|
+
{ name: "RUL", duration: "1 a\xF1o", purpose: "Audiencias de remarketing de Google Ads." }
|
|
208
|
+
],
|
|
209
|
+
storageKeys: ["_gcl_*"]
|
|
210
|
+
},
|
|
211
|
+
tiktok_pixel: {
|
|
212
|
+
id: "tiktok_pixel",
|
|
213
|
+
category: "marketing",
|
|
214
|
+
label: "TikTok Pixel",
|
|
215
|
+
provider: "TikTok Technology Limited",
|
|
216
|
+
description: "Medici\xF3n de rendimiento y segmentaci\xF3n de campa\xF1as publicitarias en TikTok.",
|
|
217
|
+
policyUrl: "https://www.tiktok.com/legal/privacy-policy-eea",
|
|
218
|
+
cookies: [
|
|
219
|
+
{ name: "_tt_enable_cookie", duration: "13 meses", purpose: "Habilitar la medici\xF3n de conversiones del p\xEDxel de TikTok." },
|
|
220
|
+
{ name: "_ttp", duration: "13 meses", purpose: "Medir y mejorar el rendimiento de las campa\xF1as publicitarias en TikTok." }
|
|
221
|
+
],
|
|
222
|
+
storageKeys: ["tt_*", "_ttp*"]
|
|
223
|
+
},
|
|
224
|
+
linkedin_insight: {
|
|
225
|
+
id: "linkedin_insight",
|
|
226
|
+
category: "marketing",
|
|
227
|
+
label: "LinkedIn Insight Tag",
|
|
228
|
+
provider: "LinkedIn Ireland Unlimited Company",
|
|
229
|
+
description: "Informes de conversiones y atribuci\xF3n de campa\xF1as profesionales B2B en LinkedIn.",
|
|
230
|
+
policyUrl: "https://www.linkedin.com/legal/privacy-policy",
|
|
231
|
+
cookies: [
|
|
232
|
+
{ name: "li_sugr", duration: "90 d\xEDas", purpose: "Identificador probabil\xEDstico de navegador en LinkedIn." },
|
|
233
|
+
{ name: "bcookie", duration: "1 a\xF1o", purpose: "Identificador de sesi\xF3n de navegador para servicios de LinkedIn." },
|
|
234
|
+
{ name: "lidc", duration: "24 horas", purpose: "Enrutamiento de centros de datos para LinkedIn." },
|
|
235
|
+
{ name: "UserMatchHistory", duration: "30 d\xEDas", purpose: "Sincronizaci\xF3n de identificadores de publicidad de LinkedIn." }
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
twitter_pixel: {
|
|
239
|
+
id: "twitter_pixel",
|
|
240
|
+
category: "marketing",
|
|
241
|
+
label: "X / Twitter Ads Pixel",
|
|
242
|
+
provider: "X Corp.",
|
|
243
|
+
description: "Medici\xF3n de conversiones e interacci\xF3n con campa\xF1as publicitarias en X (Twitter).",
|
|
244
|
+
policyUrl: "https://twitter.com/privacy",
|
|
245
|
+
cookies: [
|
|
246
|
+
{ name: "personalization_id", duration: "2 a\xF1os", purpose: "Personalizaci\xF3n de publicidad y medici\xF3n de eventos en X." },
|
|
247
|
+
{ name: "muc_ads", duration: "2 a\xF1os", purpose: "Seguimiento de conversiones publicitarias en X." }
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
hubspot: {
|
|
251
|
+
id: "hubspot",
|
|
252
|
+
category: "marketing",
|
|
253
|
+
label: "HubSpot CRM & Tracking",
|
|
254
|
+
provider: "HubSpot, Inc.",
|
|
255
|
+
description: "Anal\xEDtica de leads, formularios inteligentes y seguimiento de clientes potenciales.",
|
|
256
|
+
policyUrl: "https://legal.hubspot.com/privacy-policy",
|
|
257
|
+
cookies: [
|
|
258
|
+
{ name: "__hstc", duration: "6 meses", purpose: "Rastreo de visitantes \xFAnicos, sesiones y marcas de tiempo." },
|
|
259
|
+
{ name: "hubspotutk", duration: "6 meses", purpose: "Rastreo de identidad de visitante pasado a formularios." },
|
|
260
|
+
{ name: "__hssc", duration: "30 minutos", purpose: "Rastreo de sesiones activas en HubSpot." },
|
|
261
|
+
{ name: "__hssrc", duration: "Sesi\xF3n", purpose: "Determinar si el usuario ha reiniciado su navegador." }
|
|
262
|
+
],
|
|
263
|
+
storageKeys: ["__hs*", "hubspot*"]
|
|
264
|
+
},
|
|
265
|
+
// --- EMBEDDED MEDIA ---
|
|
266
|
+
youtube: {
|
|
267
|
+
id: "youtube",
|
|
268
|
+
category: "marketing",
|
|
269
|
+
label: "YouTube Video Player",
|
|
270
|
+
provider: "Google LLC",
|
|
271
|
+
description: "Reproducci\xF3n de contenidos de v\xEDdeo integrados y almacenamiento de preferencias del reproductor.",
|
|
272
|
+
policyUrl: "https://policies.google.com/privacy",
|
|
273
|
+
cookies: [
|
|
274
|
+
{ name: "VISITOR_INFO1_LIVE", duration: "6 meses", purpose: "Estimar el ancho de banda del usuario en p\xE1ginas con v\xEDdeos de YouTube." },
|
|
275
|
+
{ name: "YSC", duration: "Sesi\xF3n", purpose: "Registrar estad\xEDsticas de visualizaciones de v\xEDdeo de YouTube." },
|
|
276
|
+
{ name: "PREF", duration: "8 meses", purpose: "Almacenar preferencias de configuraci\xF3n del reproductor." },
|
|
277
|
+
{ name: "GPS", duration: "30 minutos", purpose: "Rastrear ubicaci\xF3n en dispositivos m\xF3viles." }
|
|
278
|
+
],
|
|
279
|
+
storageKeys: ["yt-*", "yt-remote-*"],
|
|
280
|
+
localStorage: ["yt-*", "yt-remote-*"]
|
|
281
|
+
},
|
|
282
|
+
vimeo: {
|
|
283
|
+
id: "vimeo",
|
|
284
|
+
category: "marketing",
|
|
285
|
+
label: "Vimeo Video Player",
|
|
286
|
+
provider: "Vimeo, Inc.",
|
|
287
|
+
description: "Reproducci\xF3n de v\xEDdeos interactivos alojados en Vimeo y estad\xEDsticas de visualizaci\xF3n.",
|
|
288
|
+
policyUrl: "https://vimeo.com/privacy",
|
|
289
|
+
cookies: [
|
|
290
|
+
{ name: "vuid", duration: "2 a\xF1os", purpose: "Almacenar el historial de reproducciones de v\xEDdeo del usuario en Vimeo." },
|
|
291
|
+
{ name: "player", duration: "1 a\xF1o", purpose: "Guardar las preferencias de volumen y resoluci\xF3n del reproductor." }
|
|
292
|
+
],
|
|
293
|
+
storageKeys: ["vimeo*"]
|
|
294
|
+
},
|
|
295
|
+
spotify: {
|
|
296
|
+
id: "spotify",
|
|
297
|
+
category: "marketing",
|
|
298
|
+
label: "Spotify Player",
|
|
299
|
+
provider: "Spotify AB",
|
|
300
|
+
description: "Reproducci\xF3n embebida de canciones, podcasts y listas de reproducci\xF3n de Spotify.",
|
|
301
|
+
policyUrl: "https://www.spotify.com/legal/privacy-policy/",
|
|
302
|
+
cookies: [
|
|
303
|
+
{ name: "sp_t", duration: "1 a\xF1o", purpose: "Identificador de usuario \xFAnico de Spotify para contenido embebido." },
|
|
304
|
+
{ name: "sp_m", duration: "1 a\xF1o", purpose: "Preferencias de reproducci\xF3n y cookies de sesi\xF3n." }
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
// --- CHAT & CUSTOMER SUPPORT ---
|
|
308
|
+
intercom: {
|
|
309
|
+
id: "intercom",
|
|
310
|
+
category: "marketing",
|
|
311
|
+
label: "Intercom Messenger",
|
|
312
|
+
provider: "Intercom R&D Unlimited Company",
|
|
313
|
+
description: "Widget de mensajer\xEDa, soporte al cliente en tiempo real y asistencia guiada.",
|
|
314
|
+
policyUrl: "https://www.intercom.com/legal/privacy",
|
|
315
|
+
cookies: [
|
|
316
|
+
{ name: "intercom-id-*", duration: "9 meses", purpose: "Identificador an\xF3nimo de visitante para conversaciones en Intercom." },
|
|
317
|
+
{ name: "intercom-session-*", duration: "7 d\xEDas", purpose: "Persistir la sesi\xF3n de chat activa." },
|
|
318
|
+
{ name: "intercom-device-id-*", duration: "9 meses", purpose: "Identificador del dispositivo del visitante." }
|
|
319
|
+
],
|
|
320
|
+
storageKeys: ["intercom*"]
|
|
321
|
+
},
|
|
322
|
+
crisp: {
|
|
323
|
+
id: "crisp",
|
|
324
|
+
category: "marketing",
|
|
325
|
+
label: "Crisp Live Chat",
|
|
326
|
+
provider: "Crisp IM SARL",
|
|
327
|
+
description: "Chat en vivo de soporte y mensajer\xEDa multicanal para visitantes.",
|
|
328
|
+
policyUrl: "https://crisp.chat/privacy",
|
|
329
|
+
cookies: [
|
|
330
|
+
{ name: "crisp-client/*", duration: "6 meses", purpose: "Identificador de sesi\xF3n de chat en Crisp." }
|
|
331
|
+
],
|
|
332
|
+
storageKeys: ["crisp-client*"]
|
|
333
|
+
},
|
|
334
|
+
// --- NECESSARY / TECHNICAL / SECURITY ---
|
|
335
|
+
gtm: {
|
|
336
|
+
id: "gtm",
|
|
337
|
+
category: "necessary",
|
|
338
|
+
label: "Google Tag Manager",
|
|
339
|
+
provider: "Google LLC",
|
|
340
|
+
description: "Contenedor t\xE9cnico para la inyecci\xF3n y gesti\xF3n centralizada de scripts del sitio web.",
|
|
341
|
+
policyUrl: "https://policies.google.com/privacy",
|
|
342
|
+
cookies: [
|
|
343
|
+
{ name: "_gtm_*", duration: "Sesi\xF3n", purpose: "Depuraci\xF3n t\xE9cnica de contenedores de Google Tag Manager." }
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
google_recaptcha: {
|
|
347
|
+
id: "google_recaptcha",
|
|
348
|
+
category: "necessary",
|
|
349
|
+
label: "Google reCAPTCHA",
|
|
350
|
+
provider: "Google LLC",
|
|
351
|
+
description: "Protecci\xF3n contra bots automatizados, ataques de fuerza bruta y spam en formularios.",
|
|
352
|
+
policyUrl: "https://policies.google.com/privacy",
|
|
353
|
+
cookies: [
|
|
354
|
+
{ name: "_GRECAPTCHA", duration: "6 meses", purpose: "Evaluaci\xF3n de riesgo de bots para protecci\xF3n de formularios." },
|
|
355
|
+
{ name: "rc::a", duration: "Persistente", purpose: "Distinguir entre humanos y bots automatizados." },
|
|
356
|
+
{ name: "rc::b", duration: "Sesi\xF3n", purpose: "Distinguir entre humanos y bots automatizados." },
|
|
357
|
+
{ name: "rc::c", duration: "Sesi\xF3n", purpose: "Distinguir entre humanos y bots automatizados." }
|
|
358
|
+
]
|
|
359
|
+
},
|
|
360
|
+
cloudflare: {
|
|
361
|
+
id: "cloudflare",
|
|
362
|
+
category: "necessary",
|
|
363
|
+
label: "Cloudflare Security & Turnstile",
|
|
364
|
+
provider: "Cloudflare, Inc.",
|
|
365
|
+
description: "Mitigaci\xF3n de ataques DDoS, balanceo de carga CDN y validaci\xF3n de seguridad Turnstile.",
|
|
366
|
+
policyUrl: "https://www.cloudflare.com/privacypolicy/",
|
|
367
|
+
cookies: [
|
|
368
|
+
{ name: "__cf_bm", duration: "30 minutos", purpose: "Gesti\xF3n de bots y mitigaci\xF3n de tr\xE1fico malicioso en Cloudflare." },
|
|
369
|
+
{ name: "cf_clearance", duration: "1 a\xF1o", purpose: "Autorizaci\xF3n de paso tras superar desaf\xEDo de seguridad Turnstile/Cloudflare." }
|
|
370
|
+
]
|
|
371
|
+
},
|
|
372
|
+
stripe: {
|
|
373
|
+
id: "stripe",
|
|
374
|
+
category: "necessary",
|
|
375
|
+
label: "Stripe Payments & Fraud Prevention",
|
|
376
|
+
provider: "Stripe, Inc.",
|
|
377
|
+
description: "Procesamiento seguro de transacciones bancarias, tarjetas y prevenci\xF3n de fraude financiero.",
|
|
378
|
+
policyUrl: "https://stripe.com/privacy",
|
|
379
|
+
cookies: [
|
|
380
|
+
{ name: "__stripe_mid", duration: "1 a\xF1o", purpose: "Prevenci\xF3n de fraude y autenticaci\xF3n de pagos en Stripe." },
|
|
381
|
+
{ name: "__stripe_sid", duration: "30 minutos", purpose: "Identificador de sesi\xF3n de transacci\xF3n en Stripe." },
|
|
382
|
+
{ name: "m", duration: "2 a\xF1os", purpose: "Detecci\xF3n de fraude financiero en la pasarela de pagos Stripe." }
|
|
383
|
+
],
|
|
384
|
+
storageKeys: ["__stripe_*"],
|
|
385
|
+
localStorage: ["__stripe_*"]
|
|
386
|
+
},
|
|
387
|
+
paypal: {
|
|
388
|
+
id: "paypal",
|
|
389
|
+
category: "necessary",
|
|
390
|
+
label: "PayPal Checkout",
|
|
391
|
+
provider: "PayPal (Europe) S.\xE0 r.l. et Cie, S.C.A.",
|
|
392
|
+
description: "Procesamiento de pagos y pasarela de cobro segura de PayPal.",
|
|
393
|
+
policyUrl: "https://www.paypal.com/webapps/mpp/ua/privacy-full",
|
|
394
|
+
cookies: [
|
|
395
|
+
{ name: "ts", duration: "3 a\xF1os", purpose: "Gesti\xF3n segura de pagos y prevenci\xF3n de fraude en PayPal." },
|
|
396
|
+
{ name: "ts_c", duration: "3 a\xF1os", purpose: "Autenticaci\xF3n segura de usuario en la pasarela PayPal." }
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// src/presets/index.ts
|
|
402
|
+
function hasPreset(presetId) {
|
|
403
|
+
return Object.prototype.hasOwnProperty.call(SERVICE_PRESETS, presetId);
|
|
404
|
+
}
|
|
405
|
+
function resolveConfigPresets(config) {
|
|
406
|
+
if (!config || !config.services || typeof config.services !== "object") {
|
|
407
|
+
return config;
|
|
408
|
+
}
|
|
409
|
+
const hydratedServices = {};
|
|
410
|
+
for (const [serviceKey, serviceDef] of Object.entries(config.services)) {
|
|
411
|
+
if (!serviceDef || typeof serviceDef !== "object") {
|
|
412
|
+
hydratedServices[serviceKey] = serviceDef;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
const presetName = serviceDef.preset || (hasPreset(serviceKey) && !serviceDef.category ? serviceKey : void 0);
|
|
416
|
+
if (presetName && hasPreset(presetName)) {
|
|
417
|
+
const preset = SERVICE_PRESETS[presetName];
|
|
418
|
+
hydratedServices[serviceKey] = __spreadValues({
|
|
419
|
+
category: serviceDef.category || preset.category,
|
|
420
|
+
label: serviceDef.label || preset.label,
|
|
421
|
+
provider: serviceDef.provider || preset.provider,
|
|
422
|
+
policyUrl: serviceDef.policyUrl || preset.policyUrl,
|
|
423
|
+
description: serviceDef.description || preset.description,
|
|
424
|
+
cookies: serviceDef.cookies || (preset.cookies ? [...preset.cookies] : void 0),
|
|
425
|
+
storageKeys: serviceDef.storageKeys || (preset.storageKeys ? [...preset.storageKeys] : void 0),
|
|
426
|
+
localStorage: serviceDef.localStorage || (preset.localStorage ? [...preset.localStorage] : void 0),
|
|
427
|
+
sessionStorage: serviceDef.sessionStorage || (preset.sessionStorage ? [...preset.sessionStorage] : void 0)
|
|
428
|
+
}, serviceDef);
|
|
429
|
+
} else {
|
|
430
|
+
hydratedServices[serviceKey] = serviceDef;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return __spreadProps(__spreadValues({}, config), {
|
|
434
|
+
services: hydratedServices
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/audit/auditor.ts
|
|
439
|
+
var KNOWN_TRACKER_PATTERNS = [
|
|
440
|
+
{ domain: "googletagmanager.com/gtm.js", name: "Google Tag Manager", category: "necessary_or_analytics" },
|
|
441
|
+
{ domain: "googletagmanager.com/gtag", name: "Google Analytics / Ads Gtag", category: "analytics" },
|
|
442
|
+
{ domain: "google-analytics.com/analytics.js", name: "Google Universal Analytics", category: "analytics" },
|
|
443
|
+
{ domain: "connect.facebook.net", name: "Meta / Facebook Pixel", category: "marketing" },
|
|
444
|
+
{ domain: "static.hotjar.com", name: "Hotjar Heatmaps & Feedback", category: "analytics" },
|
|
445
|
+
{ domain: "clarity.ms/tag", name: "Microsoft Clarity", category: "analytics" },
|
|
446
|
+
{ domain: "analytics.tiktok.com", name: "TikTok Pixel", category: "marketing" },
|
|
447
|
+
{ domain: "snap.licdn.com/insight", name: "LinkedIn Insight Tag", category: "marketing" },
|
|
448
|
+
{ domain: "cdn.segment.com/analytics", name: "Segment Analytics", category: "analytics" },
|
|
449
|
+
{ domain: "app.posthog.com/static", name: "PostHog Analytics", category: "analytics" },
|
|
450
|
+
{ domain: "mc.yandex.ru/metrika", name: "Yandex Metrika", category: "analytics" }
|
|
451
|
+
];
|
|
452
|
+
var LssiAuditor = class {
|
|
453
|
+
/**
|
|
454
|
+
* Run complete compliance audit on configuration, source code, and/or live URL.
|
|
455
|
+
*/
|
|
456
|
+
static async audit(options) {
|
|
457
|
+
const findings = [];
|
|
458
|
+
let scannedFilesCount = 0;
|
|
459
|
+
let resolvedConfig = null;
|
|
460
|
+
if (options.config) {
|
|
461
|
+
if (typeof options.config === "string") {
|
|
462
|
+
try {
|
|
463
|
+
const raw = fs.readFileSync(path.resolve(options.config), "utf8");
|
|
464
|
+
resolvedConfig = resolveConfigPresets(JSON.parse(raw));
|
|
465
|
+
} catch (err) {
|
|
466
|
+
findings.push({
|
|
467
|
+
id: "CONFIG_FILE_ERROR",
|
|
468
|
+
rule: "LSSI-CONFIG-001",
|
|
469
|
+
severity: "CRITICAL",
|
|
470
|
+
category: "CONFIG",
|
|
471
|
+
message: `No se pudo leer el archivo de configuraci\xF3n: ${err.message}`,
|
|
472
|
+
remediation: "Verifica que la ruta al archivo consent.json sea v\xE1lida y contenga JSON bien formado."
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
resolvedConfig = resolveConfigPresets(options.config);
|
|
477
|
+
}
|
|
478
|
+
} else if (options.configPath) {
|
|
479
|
+
try {
|
|
480
|
+
const raw = fs.readFileSync(path.resolve(options.configPath), "utf8");
|
|
481
|
+
resolvedConfig = resolveConfigPresets(JSON.parse(raw));
|
|
482
|
+
} catch (err) {
|
|
483
|
+
findings.push({
|
|
484
|
+
id: "CONFIG_FILE_ERROR",
|
|
485
|
+
rule: "LSSI-CONFIG-001",
|
|
486
|
+
severity: "CRITICAL",
|
|
487
|
+
category: "CONFIG",
|
|
488
|
+
message: `No se pudo leer el archivo de configuraci\xF3n en '${options.configPath}': ${err.message}`,
|
|
489
|
+
remediation: "Especifica una ruta v\xE1lida con --config <ruta>."
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (resolvedConfig) {
|
|
494
|
+
this.auditConfig(resolvedConfig, findings);
|
|
495
|
+
}
|
|
496
|
+
if (options.srcDir && fs.existsSync(path.resolve(options.srcDir))) {
|
|
497
|
+
scannedFilesCount = this.auditSourceDirectory(path.resolve(options.srcDir), findings);
|
|
498
|
+
}
|
|
499
|
+
if (options.url) {
|
|
500
|
+
await this.auditLiveUrl(options.url, findings);
|
|
501
|
+
}
|
|
502
|
+
const criticalCount = findings.filter((f) => f.severity === "CRITICAL").length;
|
|
503
|
+
const warningCount = findings.filter((f) => f.severity === "WARNING").length;
|
|
504
|
+
const infoCount = findings.filter((f) => f.severity === "INFO").length;
|
|
505
|
+
const penalty = criticalCount * 25 + warningCount * 10 + infoCount * 2;
|
|
506
|
+
const score = Math.max(0, 100 - penalty);
|
|
507
|
+
const compliant = criticalCount === 0;
|
|
508
|
+
return {
|
|
509
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
510
|
+
compliant,
|
|
511
|
+
score,
|
|
512
|
+
summary: {
|
|
513
|
+
critical: criticalCount,
|
|
514
|
+
warnings: warningCount,
|
|
515
|
+
info: infoCount,
|
|
516
|
+
totalPassed: Math.max(0, 10 - criticalCount - warningCount)
|
|
517
|
+
},
|
|
518
|
+
findings,
|
|
519
|
+
scannedFilesCount
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Validate consent configuration against LSSI art. 22.2 and AEPD guidelines.
|
|
524
|
+
*/
|
|
525
|
+
static auditConfig(config, findings) {
|
|
526
|
+
var _a, _b, _c;
|
|
527
|
+
if (!config.categories || typeof config.categories !== "object") {
|
|
528
|
+
findings.push({
|
|
529
|
+
id: "CONFIG_NO_CATEGORIES",
|
|
530
|
+
rule: "AEPD-CATEGORIES-001",
|
|
531
|
+
severity: "CRITICAL",
|
|
532
|
+
category: "CONFIG",
|
|
533
|
+
message: "No se encontraron categor\xEDas de consentimiento definidas.",
|
|
534
|
+
remediation: "Declara al menos la categor\xEDa 'necessary' y las categor\xEDas opcionales ('analytics', 'marketing')."
|
|
535
|
+
});
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const nec = config.categories["necessary"];
|
|
539
|
+
if (!nec) {
|
|
540
|
+
findings.push({
|
|
541
|
+
id: "CONFIG_MISSING_NECESSARY",
|
|
542
|
+
rule: "LSSI-NECESSARY-001",
|
|
543
|
+
severity: "CRITICAL",
|
|
544
|
+
category: "CONFIG",
|
|
545
|
+
message: "Falta la categor\xEDa obligatoria 'necessary' (cookies t\xE9cnicas y de seguridad).",
|
|
546
|
+
remediation: `A\xF1ade 'necessary: { required: true, label: "Necesarias", description: "..." }'.`
|
|
547
|
+
});
|
|
548
|
+
} else if (nec.required !== true) {
|
|
549
|
+
findings.push({
|
|
550
|
+
id: "CONFIG_NECESSARY_NOT_REQUIRED",
|
|
551
|
+
rule: "LSSI-NECESSARY-002",
|
|
552
|
+
severity: "CRITICAL",
|
|
553
|
+
category: "CONFIG",
|
|
554
|
+
message: "La categor\xEDa 'necessary' debe tener siempre 'required: true'.",
|
|
555
|
+
remediation: "Establece required: true en la categor\xEDa necessary."
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
for (const [catId, cat] of Object.entries(config.categories)) {
|
|
559
|
+
if (catId !== "necessary" && !cat.required) {
|
|
560
|
+
if (cat.default === true) {
|
|
561
|
+
findings.push({
|
|
562
|
+
id: "CONFIG_PRECHECKED_OPTIONAL",
|
|
563
|
+
rule: "AEPD-PRECHECK-001",
|
|
564
|
+
severity: "CRITICAL",
|
|
565
|
+
category: "LEGAL",
|
|
566
|
+
message: `Violaci\xF3n AEPD: La categor\xEDa opcional '${catId}' tiene default: true (casilla pre-marcada).`,
|
|
567
|
+
details: "Las directrices de la AEPD y el RGPD proh\xEDben taxativamente las casillas preactivadas por defecto.",
|
|
568
|
+
remediation: "Establece default: false en todas las categor\xEDas opcionales."
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const hasPrivacy = !!(((_a = config.policy) == null ? void 0 : _a.privacyUrl) || config.translations && Object.values(config.translations).some((t) => {
|
|
574
|
+
var _a2;
|
|
575
|
+
return (_a2 = t.policy) == null ? void 0 : _a2.privacyUrl;
|
|
576
|
+
}));
|
|
577
|
+
const hasCookies = !!(((_b = config.policy) == null ? void 0 : _b.cookiesUrl) || config.translations && Object.values(config.translations).some((t) => {
|
|
578
|
+
var _a2;
|
|
579
|
+
return (_a2 = t.policy) == null ? void 0 : _a2.cookiesUrl;
|
|
580
|
+
}));
|
|
581
|
+
if (!hasPrivacy) {
|
|
582
|
+
findings.push({
|
|
583
|
+
id: "CONFIG_MISSING_PRIVACY_URL",
|
|
584
|
+
rule: "RGPD-POLICY-001",
|
|
585
|
+
severity: "WARNING",
|
|
586
|
+
category: "LEGAL",
|
|
587
|
+
message: "No se ha configurado un enlace a la Pol\xEDtica de Privacidad ('policy.privacyUrl').",
|
|
588
|
+
remediation: "A\xF1ade policy.privacyUrl: '/politica-privacidad' en consent.json."
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
if (!hasCookies) {
|
|
592
|
+
findings.push({
|
|
593
|
+
id: "CONFIG_MISSING_COOKIES_URL",
|
|
594
|
+
rule: "LSSI-POLICY-001",
|
|
595
|
+
severity: "WARNING",
|
|
596
|
+
category: "LEGAL",
|
|
597
|
+
message: "No se ha configurado un enlace a la Pol\xEDtica de Cookies ('policy.cookiesUrl').",
|
|
598
|
+
remediation: "A\xF1ade policy.cookiesUrl: '/politica-cookies' en consent.json."
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const badge = (_c = config.ui) == null ? void 0 : _c.floatingBadge;
|
|
602
|
+
if (badge === false) {
|
|
603
|
+
findings.push({
|
|
604
|
+
id: "CONFIG_FLOATING_BADGE_DISABLED",
|
|
605
|
+
rule: "AEPD-REVOCATION-001",
|
|
606
|
+
severity: "WARNING",
|
|
607
|
+
category: "LEGAL",
|
|
608
|
+
message: "El bot\xF3n flotante de revocaci\xF3n ('ui.floatingBadge') est\xE1 desactivado.",
|
|
609
|
+
details: "La AEPD exige que retirar el consentimiento sea tan f\xE1cil como otorgarlo.",
|
|
610
|
+
remediation: "Mant\xE9n el floatingBadge activado o aseg\xFArate de incluir un bot\xF3n persistente [data-consent-open] en el footer."
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Scan source code directory for unblocked third-party scripts.
|
|
616
|
+
*/
|
|
617
|
+
static auditSourceDirectory(dirPath, findings) {
|
|
618
|
+
let count = 0;
|
|
619
|
+
const files = this.walkDir(dirPath);
|
|
620
|
+
const validExtensions = /* @__PURE__ */ new Set([".html", ".htm", ".jsx", ".tsx", ".vue", ".php", ".js", ".ts", ".mjs"]);
|
|
621
|
+
for (const filePath of files) {
|
|
622
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
623
|
+
if (!validExtensions.has(ext)) continue;
|
|
624
|
+
if (filePath.includes("node_modules") || filePath.includes("dist") || filePath.includes(".git") || filePath.includes("/audit/") || filePath.includes("\\audit\\") || filePath.includes("/presets/") || filePath.includes("\\presets\\") || filePath.includes("tests/") || filePath.includes("tests\\")) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
count++;
|
|
628
|
+
try {
|
|
629
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
630
|
+
const scriptTagRegex = /<\s*script\b([^>]*)>/gi;
|
|
631
|
+
let match;
|
|
632
|
+
while ((match = scriptTagRegex.exec(content)) !== null) {
|
|
633
|
+
const tagAttributes = match[1];
|
|
634
|
+
const fullTag = match[0];
|
|
635
|
+
const offset = match.index;
|
|
636
|
+
const lineNumber = content.substring(0, offset).split("\n").length;
|
|
637
|
+
for (const tracker of KNOWN_TRACKER_PATTERNS) {
|
|
638
|
+
if (tagAttributes.includes(tracker.domain) || fullTag.includes(tracker.domain)) {
|
|
639
|
+
const isBlocked = tagAttributes.includes('type="text/plain"') || tagAttributes.includes("type='text/plain'") || tagAttributes.includes("data-consent") || tagAttributes.includes("data-src");
|
|
640
|
+
if (!isBlocked) {
|
|
641
|
+
findings.push({
|
|
642
|
+
id: "SRC_UNBLOCKED_TRACKER",
|
|
643
|
+
rule: "LSSI-SCRIPT-ISOLATION-001",
|
|
644
|
+
severity: "CRITICAL",
|
|
645
|
+
category: "SOURCE_CODE",
|
|
646
|
+
message: `Script de seguimiento no bloqueado detectado (${tracker.name}): '${tracker.domain}'`,
|
|
647
|
+
file: path.relative(process.cwd(), filePath),
|
|
648
|
+
line: lineNumber,
|
|
649
|
+
details: fullTag.replace(/\s+/g, " ").trim(),
|
|
650
|
+
remediation: `Modifica el script para usar type="text/plain" data-src="..." data-consent="${tracker.category}" o envu\xE9lvelo en un <ConsentGate>.`
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
const sanitizedContent = content.replace(/<\s*script\b[^>]*type=["']text\/plain["'][^>]*>[\s\S]*?<\s*\/\s*script\s*>/gi, "");
|
|
657
|
+
const lines = sanitizedContent.split("\n");
|
|
658
|
+
for (let i = 0; i < lines.length; i++) {
|
|
659
|
+
const rawLine = lines[i];
|
|
660
|
+
const line = rawLine.trim();
|
|
661
|
+
if (line.startsWith("//") || line.startsWith("*") || line.startsWith("/*") || line.includes("lssi-ignore") || line.includes("<script") || line.includes("data-src") || line.includes('type="text/plain"') || line.includes("type='text/plain'")) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
for (const tracker of KNOWN_TRACKER_PATTERNS) {
|
|
665
|
+
if (line.includes(tracker.domain)) {
|
|
666
|
+
const isBlocked = line.includes("ConsentGate") || line.includes("consentGate") || line.includes("useConsent") || line.includes("useConsentService") || line.includes("data-consent") || line.includes("text/plain");
|
|
667
|
+
if (!isBlocked) {
|
|
668
|
+
findings.push({
|
|
669
|
+
id: "SRC_UNBLOCKED_TRACKER",
|
|
670
|
+
rule: "LSSI-SCRIPT-ISOLATION-001",
|
|
671
|
+
severity: "CRITICAL",
|
|
672
|
+
category: "SOURCE_CODE",
|
|
673
|
+
message: `Referencia a rastreador sin control de consentimiento detectada (${tracker.name}): '${tracker.domain}'`,
|
|
674
|
+
file: path.relative(process.cwd(), filePath),
|
|
675
|
+
line: i + 1,
|
|
676
|
+
details: line.trim(),
|
|
677
|
+
remediation: `Envuelve la invocaci\xF3n en un bloque condicional 'useConsent("${tracker.category}")' o componente <ConsentGate>.`
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
} catch (e) {
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return count;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Audit live URL by fetching HTML and verifying pre-consent isolation.
|
|
690
|
+
*/
|
|
691
|
+
static async auditLiveUrl(url, findings) {
|
|
692
|
+
try {
|
|
693
|
+
const res = await fetch(url, { headers: { "User-Agent": "Solvenza-LSSI-Auditor/1.5.1" } });
|
|
694
|
+
if (!res.ok) {
|
|
695
|
+
findings.push({
|
|
696
|
+
id: "URL_FETCH_FAILED",
|
|
697
|
+
rule: "LSSI-URL-001",
|
|
698
|
+
severity: "WARNING",
|
|
699
|
+
category: "LIVE_URL",
|
|
700
|
+
message: `No se pudo conectar a la URL '${url}' (HTTP ${res.status}).`
|
|
701
|
+
});
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const html = await res.text();
|
|
705
|
+
for (const tracker of KNOWN_TRACKER_PATTERNS) {
|
|
706
|
+
const regex = new RegExp(`<script[^>]*src=["'][^"']*${tracker.domain.replace(".", "\\.")}[^"']*["'][^>]*>`, "gi");
|
|
707
|
+
const matches = html.match(regex);
|
|
708
|
+
if (matches) {
|
|
709
|
+
for (const match of matches) {
|
|
710
|
+
if (!match.includes('type="text/plain"') && !match.includes("data-consent")) {
|
|
711
|
+
findings.push({
|
|
712
|
+
id: "URL_RAW_TRACKER_LEAK",
|
|
713
|
+
rule: "LSSI-LEAK-001",
|
|
714
|
+
severity: "CRITICAL",
|
|
715
|
+
category: "LIVE_URL",
|
|
716
|
+
message: `Fuga de rastreador en tiempo de ejecuci\xF3n: script '${tracker.name}' cargado antes del consentimiento.`,
|
|
717
|
+
details: match,
|
|
718
|
+
remediation: `Aseg\xFArate de que '${tracker.name}' se inyecte con type="text/plain" data-consent="...".`
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
} catch (err) {
|
|
725
|
+
findings.push({
|
|
726
|
+
id: "URL_CONNECT_ERROR",
|
|
727
|
+
rule: "LSSI-URL-002",
|
|
728
|
+
severity: "WARNING",
|
|
729
|
+
category: "LIVE_URL",
|
|
730
|
+
message: `Error al escanear la URL '${url}': ${err.message}`
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Generate GitHub Actions markdown step summary.
|
|
736
|
+
*/
|
|
737
|
+
static formatGitHubSummary(report) {
|
|
738
|
+
const statusEmoji = report.compliant ? "\u2705" : "\u274C";
|
|
739
|
+
const statusText = report.compliant ? "**APROBADO (Cumplimiento LSSI / AEPD / RGPD Correcto)**" : "**RECHAZADO (Violaciones Cr\xEDticas de Privacidad Detectadas)**";
|
|
740
|
+
let md = `## ${statusEmoji} Auditor\xEDa de Cumplimiento LSSI & AEPD - Solvenza Cookies Compliance
|
|
741
|
+
|
|
742
|
+
`;
|
|
743
|
+
md += `> **Resultado:** ${statusText}
|
|
744
|
+
`;
|
|
745
|
+
md += `> **Puntuaci\xF3n de Cumplimiento:** **${report.score} / 100**
|
|
746
|
+
`;
|
|
747
|
+
md += `> **Archivos Escaneados:** ${report.scannedFilesCount} | **Fecha:** ${new Date(report.timestamp).toUTCString()}
|
|
748
|
+
|
|
749
|
+
`;
|
|
750
|
+
md += `### \u{1F4CA} Resumen de Hallazgos:
|
|
751
|
+
|
|
752
|
+
`;
|
|
753
|
+
md += `| Gravedad | Cantidad | Descripci\xF3n |
|
|
754
|
+
`;
|
|
755
|
+
md += `|---|---|---|
|
|
756
|
+
`;
|
|
757
|
+
md += `| \u{1F534} **CR\xCDTICO** | **${report.summary.critical}** | Violaciones legales directas (riesgo de sanci\xF3n AEPD) |
|
|
758
|
+
`;
|
|
759
|
+
md += `| \u{1F7E1} **ADVERTENCIA** | **${report.summary.warnings}** | Recomendaciones t\xE9cnicas y mejores pr\xE1cticas |
|
|
760
|
+
`;
|
|
761
|
+
md += `| \u{1F535} **INFO** | **${report.summary.info}** | Sugerencias de optimizaci\xF3n |
|
|
762
|
+
|
|
763
|
+
`;
|
|
764
|
+
if (report.findings.length > 0) {
|
|
765
|
+
md += `### \u{1F50D} Detalle de Hallazgos:
|
|
766
|
+
|
|
767
|
+
`;
|
|
768
|
+
md += `| Regla | Gravedad | Categor\xEDa | Archivo/L\xEDnea | Mensaje | Soluci\xF3n |
|
|
769
|
+
`;
|
|
770
|
+
md += `|---|---|---|---|---|---|
|
|
771
|
+
`;
|
|
772
|
+
for (const f of report.findings) {
|
|
773
|
+
const sevBadge = f.severity === "CRITICAL" ? "\u{1F534} CR\xCDTICO" : f.severity === "WARNING" ? "\u{1F7E1} ADVERTENCIA" : "\u{1F535} INFO";
|
|
774
|
+
const location = f.file ? `\`${f.file}${f.line ? `:${f.line}` : ""}\`` : "-";
|
|
775
|
+
const remediation = f.remediation ? f.remediation.replace(/\|/g, "\\|") : "-";
|
|
776
|
+
const message = f.message.replace(/\|/g, "\\|");
|
|
777
|
+
md += `| \`${f.rule}\` | ${sevBadge} | ${f.category} | ${location} | ${message} | ${remediation} |
|
|
778
|
+
`;
|
|
779
|
+
}
|
|
780
|
+
md += `
|
|
781
|
+
`;
|
|
782
|
+
} else {
|
|
783
|
+
md += `\u{1F389} **No se encontraron violaciones ni fugas de privacidad.** Todos los recursos de terceros cumplen con el aislamiento previo al consentimiento (LSSI art. 22.2).
|
|
784
|
+
|
|
785
|
+
`;
|
|
786
|
+
}
|
|
787
|
+
return md;
|
|
788
|
+
}
|
|
789
|
+
static walkDir(dir) {
|
|
790
|
+
let results = [];
|
|
791
|
+
try {
|
|
792
|
+
const list = fs.readdirSync(dir);
|
|
793
|
+
for (const file of list) {
|
|
794
|
+
const fullPath = path.join(dir, file);
|
|
795
|
+
const stat = fs.statSync(fullPath);
|
|
796
|
+
if (stat && stat.isDirectory()) {
|
|
797
|
+
results = results.concat(this.walkDir(fullPath));
|
|
798
|
+
} else {
|
|
799
|
+
results.push(fullPath);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} catch (e) {
|
|
803
|
+
}
|
|
804
|
+
return results;
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
808
|
+
0 && (module.exports = {
|
|
809
|
+
LssiAuditor
|
|
810
|
+
});
|
|
811
|
+
//# sourceMappingURL=audit.cjs.map
|