privalint 0.2.0 → 0.4.0
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.de.md +68 -2
- package/README.md +77 -13
- package/dist/assessment.d.ts +6 -0
- package/dist/assessment.js +115 -0
- package/dist/assessment.js.map +1 -0
- package/dist/capture.d.ts +5 -0
- package/dist/capture.js +162 -0
- package/dist/capture.js.map +1 -0
- package/dist/cli.js +48 -14
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +21 -0
- package/dist/config.js +113 -0
- package/dist/config.js.map +1 -0
- package/dist/consent.d.ts +3 -0
- package/dist/consent.js +81 -12
- package/dist/consent.js.map +1 -1
- package/dist/coverage.d.ts +13 -0
- package/dist/coverage.js +86 -0
- package/dist/coverage.js.map +1 -0
- package/dist/data/third-parties.js +9 -0
- package/dist/data/third-parties.js.map +1 -1
- package/dist/diagnostics.d.ts +28 -0
- package/dist/diagnostics.js +202 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/network.d.ts +68 -0
- package/dist/network.js +237 -0
- package/dist/network.js.map +1 -0
- package/dist/observations.d.ts +3 -0
- package/dist/observations.js +4 -0
- package/dist/observations.js.map +1 -0
- package/dist/policy.d.ts +6 -0
- package/dist/policy.js +37 -0
- package/dist/policy.js.map +1 -0
- package/dist/report-contract.d.ts +13 -0
- package/dist/report-contract.js +16 -0
- package/dist/report-contract.js.map +1 -0
- package/dist/reporters/capture.d.ts +5 -0
- package/dist/reporters/capture.js +87 -0
- package/dist/reporters/capture.js.map +1 -0
- package/dist/reporters/markdown.js +13 -1
- package/dist/reporters/markdown.js.map +1 -1
- package/dist/reporters/messages.d.ts +1 -0
- package/dist/reporters/messages.js +18 -6
- package/dist/reporters/messages.js.map +1 -1
- package/dist/reporters/sarif.js +48 -10
- package/dist/reporters/sarif.js.map +1 -1
- package/dist/reporters/shared.js +3 -2
- package/dist/reporters/shared.js.map +1 -1
- package/dist/reporters/text.js +10 -2
- package/dist/reporters/text.js.map +1 -1
- package/dist/rules/google-fonts.js +2 -1
- package/dist/rules/google-fonts.js.map +1 -1
- package/dist/rules/reject-option.js +6 -4
- package/dist/rules/reject-option.js.map +1 -1
- package/dist/rules/third-party-embeds.js +2 -2
- package/dist/rules/third-party-embeds.js.map +1 -1
- package/dist/rules/third-party-hits.js +2 -1
- package/dist/rules/third-party-hits.js.map +1 -1
- package/dist/rules/tracker-hits.js +2 -1
- package/dist/rules/tracker-hits.js.map +1 -1
- package/dist/scan-options.d.ts +45 -0
- package/dist/scan-options.js +2 -0
- package/dist/scan-options.js.map +1 -0
- package/dist/scanner.d.ts +4 -38
- package/dist/scanner.js +177 -114
- package/dist/scanner.js.map +1 -1
- package/dist/types.d.ts +119 -0
- package/package.json +7 -1
- package/schemas/report-1.0.0.schema.json +813 -0
- package/schemas/report-1.1.0.schema.json +833 -0
- package/schemas/sarif-properties-1.0.0.schema.json +785 -0
- package/schemas/sarif-properties-1.1.0.schema.json +810 -0
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,67 @@ export type Severity = 'error' | 'warning' | 'info';
|
|
|
4
4
|
export interface RecordedRequest {
|
|
5
5
|
url: string;
|
|
6
6
|
resourceType: string;
|
|
7
|
+
/** Absent on legacy snapshots. Only received responses count as network evidence. */
|
|
8
|
+
outcome?: 'pending' | 'response' | 'failed';
|
|
9
|
+
status?: number;
|
|
10
|
+
}
|
|
11
|
+
export type LoadIssueKind = 'security-blocked' | 'unsupported-method' | 'unsupported-protocol' | 'dns' | 'tls' | 'connection' | 'timeout' | 'resource-limit' | 'http-error' | 'browser-failure' | 'skipped' | 'aborted';
|
|
12
|
+
export type CapturePhase = 'before-consent' | 'after-reject';
|
|
13
|
+
export type SkipReason = 'beacon' | 'unsupported-method' | 'unsupported-protocol' | 'policy';
|
|
14
|
+
export interface LoadDiagnostic {
|
|
15
|
+
kind: LoadIssueKind;
|
|
16
|
+
phase: CapturePhase;
|
|
17
|
+
/** Origin only: user info, path, query and fragment are never retained. */
|
|
18
|
+
url: string;
|
|
19
|
+
status?: number;
|
|
20
|
+
limit?: 'requests' | 'response-bytes' | 'total-bytes' | 'redirects';
|
|
21
|
+
skippedReason?: SkipReason;
|
|
22
|
+
}
|
|
23
|
+
export interface LoadDiagnostics {
|
|
24
|
+
/** Disjoint terminal outcomes; finished + these three equals requests. */
|
|
25
|
+
skippedRequests?: number;
|
|
26
|
+
failedRequests?: number;
|
|
27
|
+
securityBlockedRequests?: number;
|
|
28
|
+
/** HTTP(S) browser requests, including documents and every redirect hop, across both phases. */
|
|
29
|
+
requests: number;
|
|
30
|
+
/** Requests counted but omitted from bounded in-memory rule evidence. */
|
|
31
|
+
omittedRequests: number;
|
|
32
|
+
/** Received HTTP responses, including redirects and HTTP errors; may overlap failed. */
|
|
33
|
+
responses: number;
|
|
34
|
+
/** Browser requestfinished events (including HTTP errors). */
|
|
35
|
+
finished: number;
|
|
36
|
+
/** @deprecated Aggregate of skippedRequests + failedRequests + securityBlockedRequests. */
|
|
37
|
+
failed: number;
|
|
38
|
+
/** Subset of failed: explicitly denied by the adapter's security policy. */
|
|
39
|
+
blocked: number;
|
|
40
|
+
/** Subset of responses: status >= 400. */
|
|
41
|
+
httpErrors: number;
|
|
42
|
+
/** Adapter response-body bytes consumed, including the chunk exceeding a limit. */
|
|
43
|
+
bytes: number;
|
|
44
|
+
/** All issues, even when their details exceed the storage cap. */
|
|
45
|
+
issueCount: number;
|
|
46
|
+
dropped: number;
|
|
47
|
+
issues: LoadDiagnostic[];
|
|
48
|
+
}
|
|
49
|
+
export type ObservationKind = 'document' | 'network' | 'consent' | 'after-reject';
|
|
50
|
+
export interface RuleEvaluation {
|
|
51
|
+
pageId?: string;
|
|
52
|
+
ruleId: string;
|
|
53
|
+
status: 'executed' | 'skipped' | 'not-evaluable';
|
|
54
|
+
reason?: 'disabled' | 'consent-disabled' | 'not-start-page' | 'missing-observation';
|
|
55
|
+
/** Executed against available positive evidence, without ruling out additional findings. */
|
|
56
|
+
partial: boolean;
|
|
57
|
+
outcome?: 'checked-no-finding' | 'finding-detected' | 'not-evaluable';
|
|
58
|
+
explanation?: RuleExplanation;
|
|
59
|
+
findingCount?: number;
|
|
60
|
+
}
|
|
61
|
+
export type RuleExplanation = 'disabled' | 'not-start-page' | 'consent-disabled' | 'missing-reject-observation' | 'missing-consent-observation' | 'incomplete-document' | 'positive-evidence' | 'incomplete-observation' | 'required-observations-available' | 'no-supported-banner' | 'no-supported-reject-control' | 'unsupported-banner-markup' | 'browser-interaction-failed' | 'reject-clicked-and-reloaded';
|
|
62
|
+
export interface CaptureObservation {
|
|
63
|
+
complete: boolean;
|
|
64
|
+
/** Loading coverage before the consent interaction, for pre-consent rule evaluation. */
|
|
65
|
+
beforeConsentComplete?: boolean;
|
|
66
|
+
document: boolean;
|
|
67
|
+
diagnostics: LoadDiagnostics;
|
|
7
68
|
}
|
|
8
69
|
export interface RecordedCookie {
|
|
9
70
|
name: string;
|
|
@@ -23,6 +84,9 @@ export interface ResourceHint {
|
|
|
23
84
|
}
|
|
24
85
|
/** What happened when privalint looked for a consent banner and clicked "reject". */
|
|
25
86
|
export interface ConsentObservation {
|
|
87
|
+
outcome?: 'no-banner-detected' | 'unsupported-banner' | 'interaction-failed' | 'reject-succeeded';
|
|
88
|
+
reason?: 'no-supported-banner' | 'no-supported-reject-control' | 'unsupported-banner-markup' | 'browser-interaction-failed' | 'reject-clicked-and-reloaded';
|
|
89
|
+
state?: 'not-detected' | 'reject-unavailable' | 'interaction-failed' | 'reject-tested';
|
|
26
90
|
bannerFound: boolean;
|
|
27
91
|
/** Name of the consent manager, if it was recognised. */
|
|
28
92
|
cmp?: string;
|
|
@@ -37,6 +101,8 @@ export interface ConsentObservation {
|
|
|
37
101
|
requests: RecordedRequest[];
|
|
38
102
|
cookies: RecordedCookie[];
|
|
39
103
|
};
|
|
104
|
+
/** True only if the reject observation window completed without load issues. */
|
|
105
|
+
afterRejectComplete?: boolean;
|
|
40
106
|
error?: string;
|
|
41
107
|
}
|
|
42
108
|
/**
|
|
@@ -44,10 +110,13 @@ export interface ConsentObservation {
|
|
|
44
110
|
* without touching any consent banner.
|
|
45
111
|
*/
|
|
46
112
|
export interface PageSnapshot {
|
|
113
|
+
pageId?: string;
|
|
47
114
|
url: string;
|
|
48
115
|
finalUrl: string;
|
|
49
116
|
status: number | null;
|
|
50
117
|
requests: RecordedRequest[];
|
|
118
|
+
/** Initial-phase request count, including records beyond the retained evidence cap. */
|
|
119
|
+
requestCount?: number;
|
|
51
120
|
cookies: RecordedCookie[];
|
|
52
121
|
links: PageLink[];
|
|
53
122
|
resourceHints: ResourceHint[];
|
|
@@ -55,8 +124,15 @@ export interface PageSnapshot {
|
|
|
55
124
|
consent?: ConsentObservation;
|
|
56
125
|
/** Set when the page could not be loaded, e.g. a download or a timeout. */
|
|
57
126
|
error?: string;
|
|
127
|
+
capture?: CaptureObservation;
|
|
58
128
|
}
|
|
59
129
|
export interface Finding {
|
|
130
|
+
pageId?: string;
|
|
131
|
+
/** Explicit local policy, not evidence of consent or legal approval. */
|
|
132
|
+
policy?: {
|
|
133
|
+
allowed: boolean;
|
|
134
|
+
originalSeverity: Severity;
|
|
135
|
+
};
|
|
60
136
|
ruleId: string;
|
|
61
137
|
severity: Severity;
|
|
62
138
|
message: string;
|
|
@@ -80,18 +156,28 @@ export interface Rule {
|
|
|
80
156
|
description: string;
|
|
81
157
|
/** Laws, rulings or guidelines the rule is based on. */
|
|
82
158
|
references: string[];
|
|
159
|
+
/** Observations needed by custom rules. Missing means a complete document is required. */
|
|
160
|
+
requires?: readonly ObservationKind[];
|
|
83
161
|
check(context: RuleContext): Finding[];
|
|
84
162
|
}
|
|
85
163
|
export interface PageSummary {
|
|
164
|
+
pageId?: string;
|
|
86
165
|
url: string;
|
|
87
166
|
finalUrl: string;
|
|
88
167
|
status: number | null;
|
|
89
168
|
requestCount: number;
|
|
90
169
|
cookieCount: number;
|
|
91
170
|
error?: string;
|
|
171
|
+
capture?: CaptureObservation;
|
|
172
|
+
ruleEvaluations?: RuleEvaluation[];
|
|
92
173
|
}
|
|
93
174
|
export type ConsentSummary = Omit<ConsentObservation, 'afterReject'>;
|
|
94
175
|
export interface ScanResult {
|
|
176
|
+
scanId?: string;
|
|
177
|
+
/** Absent on reports created before schema versioning. */
|
|
178
|
+
schemaVersion?: string;
|
|
179
|
+
scannerVersion?: string;
|
|
180
|
+
options?: ReportOptions;
|
|
95
181
|
siteUrl: string;
|
|
96
182
|
lang: Lang;
|
|
97
183
|
region: Region;
|
|
@@ -102,4 +188,37 @@ export interface ScanResult {
|
|
|
102
188
|
consent?: ConsentSummary;
|
|
103
189
|
findings: Finding[];
|
|
104
190
|
rules: Array<Omit<Rule, 'check'>>;
|
|
191
|
+
/** Observation coverage, not a legal compliance judgement. */
|
|
192
|
+
coverage?: 'complete' | 'partial';
|
|
193
|
+
}
|
|
194
|
+
export type RuleSettings = Record<string, Severity | 'off'>;
|
|
195
|
+
export interface ScanProgressEvent {
|
|
196
|
+
stage: 'starting' | 'loading-page' | 'checking-consent' | 'page-captured' | 'evaluating-rules' | 'completed';
|
|
197
|
+
/** Random attempt ID; multiple redirecting attempts may resolve to the same pageId. */
|
|
198
|
+
captureId?: string;
|
|
199
|
+
pageId?: string;
|
|
200
|
+
scanId?: string;
|
|
201
|
+
elapsedMs: number;
|
|
202
|
+
/** Origin-only, no target path, credentials, query or fragment. */
|
|
203
|
+
pageUrl?: string;
|
|
204
|
+
pageIndex?: number;
|
|
205
|
+
}
|
|
206
|
+
export interface ReportOptions {
|
|
207
|
+
maxPages: number;
|
|
208
|
+
settleMs: number;
|
|
209
|
+
timeoutMs: number;
|
|
210
|
+
locale: string;
|
|
211
|
+
lang: Lang;
|
|
212
|
+
region: Region;
|
|
213
|
+
consentCheck: boolean;
|
|
214
|
+
headed: boolean;
|
|
215
|
+
channel: string;
|
|
216
|
+
captureTimeoutMs: number | null;
|
|
217
|
+
maxDiagnostics: number;
|
|
218
|
+
failureMode: 'throw' | 'report';
|
|
219
|
+
captureMode: 'native' | 'adapter' | 'offline-fixture';
|
|
220
|
+
networkLimits: Record<string, number> | null;
|
|
221
|
+
selectedRules: string[];
|
|
222
|
+
ruleSettings: RuleSettings;
|
|
223
|
+
allowedVendors: string[];
|
|
105
224
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "privalint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Lint your website for GDPR problems: trackers before consent and after reject, Google Fonts, embeds, missing Impressum. Real browser, CLI and GitHub Action.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
11
|
"exports": {
|
|
12
|
+
"./schemas/*": "./schemas/*",
|
|
12
13
|
".": {
|
|
13
14
|
"types": "./dist/index.d.ts",
|
|
14
15
|
"default": "./dist/index.js"
|
|
15
16
|
}
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
19
|
+
"schemas",
|
|
18
20
|
"dist",
|
|
19
21
|
"README.de.md"
|
|
20
22
|
],
|
|
@@ -24,6 +26,9 @@
|
|
|
24
26
|
"scripts": {
|
|
25
27
|
"build": "tsc -p tsconfig.build.json",
|
|
26
28
|
"typecheck": "tsc --noEmit",
|
|
29
|
+
"schemas:build": "node scripts/build-report-schemas.mjs",
|
|
30
|
+
"schemas:check": "node scripts/build-report-schemas.mjs --check",
|
|
31
|
+
"fixtures:reports": "npm run build && node scripts/build-report-fixtures.mjs",
|
|
27
32
|
"test": "vitest run",
|
|
28
33
|
"test:watch": "vitest",
|
|
29
34
|
"prepublishOnly": "npm run build",
|
|
@@ -55,6 +60,7 @@
|
|
|
55
60
|
"devDependencies": {
|
|
56
61
|
"@biomejs/biome": "2.6.3",
|
|
57
62
|
"@types/node": "^20.19.43",
|
|
63
|
+
"ajv": "^8.20.0",
|
|
58
64
|
"typescript": "^7.0.2",
|
|
59
65
|
"vitest": "^4.1.12"
|
|
60
66
|
},
|