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.
Files changed (75) hide show
  1. package/README.de.md +68 -2
  2. package/README.md +77 -13
  3. package/dist/assessment.d.ts +6 -0
  4. package/dist/assessment.js +115 -0
  5. package/dist/assessment.js.map +1 -0
  6. package/dist/capture.d.ts +5 -0
  7. package/dist/capture.js +162 -0
  8. package/dist/capture.js.map +1 -0
  9. package/dist/cli.js +48 -14
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config.d.ts +21 -0
  12. package/dist/config.js +113 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/consent.d.ts +3 -0
  15. package/dist/consent.js +81 -12
  16. package/dist/consent.js.map +1 -1
  17. package/dist/coverage.d.ts +13 -0
  18. package/dist/coverage.js +86 -0
  19. package/dist/coverage.js.map +1 -0
  20. package/dist/data/third-parties.js +9 -0
  21. package/dist/data/third-parties.js.map +1 -1
  22. package/dist/diagnostics.d.ts +28 -0
  23. package/dist/diagnostics.js +202 -0
  24. package/dist/diagnostics.js.map +1 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +6 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/network.d.ts +68 -0
  29. package/dist/network.js +237 -0
  30. package/dist/network.js.map +1 -0
  31. package/dist/observations.d.ts +3 -0
  32. package/dist/observations.js +4 -0
  33. package/dist/observations.js.map +1 -0
  34. package/dist/policy.d.ts +6 -0
  35. package/dist/policy.js +37 -0
  36. package/dist/policy.js.map +1 -0
  37. package/dist/report-contract.d.ts +13 -0
  38. package/dist/report-contract.js +16 -0
  39. package/dist/report-contract.js.map +1 -0
  40. package/dist/reporters/capture.d.ts +5 -0
  41. package/dist/reporters/capture.js +87 -0
  42. package/dist/reporters/capture.js.map +1 -0
  43. package/dist/reporters/markdown.js +13 -1
  44. package/dist/reporters/markdown.js.map +1 -1
  45. package/dist/reporters/messages.d.ts +1 -0
  46. package/dist/reporters/messages.js +18 -6
  47. package/dist/reporters/messages.js.map +1 -1
  48. package/dist/reporters/sarif.js +48 -10
  49. package/dist/reporters/sarif.js.map +1 -1
  50. package/dist/reporters/shared.js +3 -2
  51. package/dist/reporters/shared.js.map +1 -1
  52. package/dist/reporters/text.js +10 -2
  53. package/dist/reporters/text.js.map +1 -1
  54. package/dist/rules/google-fonts.js +2 -1
  55. package/dist/rules/google-fonts.js.map +1 -1
  56. package/dist/rules/reject-option.js +6 -4
  57. package/dist/rules/reject-option.js.map +1 -1
  58. package/dist/rules/third-party-embeds.js +2 -2
  59. package/dist/rules/third-party-embeds.js.map +1 -1
  60. package/dist/rules/third-party-hits.js +2 -1
  61. package/dist/rules/third-party-hits.js.map +1 -1
  62. package/dist/rules/tracker-hits.js +2 -1
  63. package/dist/rules/tracker-hits.js.map +1 -1
  64. package/dist/scan-options.d.ts +45 -0
  65. package/dist/scan-options.js +2 -0
  66. package/dist/scan-options.js.map +1 -0
  67. package/dist/scanner.d.ts +4 -38
  68. package/dist/scanner.js +177 -114
  69. package/dist/scanner.js.map +1 -1
  70. package/dist/types.d.ts +119 -0
  71. package/package.json +7 -1
  72. package/schemas/report-1.0.0.schema.json +813 -0
  73. package/schemas/report-1.1.0.schema.json +833 -0
  74. package/schemas/sarif-properties-1.0.0.schema.json +785 -0
  75. 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.2.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
  },