@runhalo/engine 0.3.1 → 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/dist/index.d.ts +50 -0
- package/dist/index.js +130 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/rules/rules.json +1005 -0
package/rules/rules.json
ADDED
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"generated_at": "2026-03-02T00:00:00Z",
|
|
4
|
+
"packs": {
|
|
5
|
+
"coppa": {
|
|
6
|
+
"id": "coppa",
|
|
7
|
+
"name": "COPPA 2.0 Core",
|
|
8
|
+
"description": "20 rules for COPPA & COPPA 2.0 compliance. Effective April 22, 2026.",
|
|
9
|
+
"jurisdiction": "US-Federal",
|
|
10
|
+
"jurisdiction_level": "federal",
|
|
11
|
+
"is_free": true,
|
|
12
|
+
"effective_date": "2026-04-22",
|
|
13
|
+
"source_url": "https://www.ecfr.gov/current/title-16/chapter-I/subchapter-C/part-312"
|
|
14
|
+
},
|
|
15
|
+
"ethical": {
|
|
16
|
+
"id": "ethical",
|
|
17
|
+
"name": "Ethical Design",
|
|
18
|
+
"description": "5 rules detecting dark patterns that exploit children's psychology.",
|
|
19
|
+
"jurisdiction": "international",
|
|
20
|
+
"jurisdiction_level": "advisory",
|
|
21
|
+
"is_free": true,
|
|
22
|
+
"effective_date": null,
|
|
23
|
+
"source_url": "https://runhalo.dev/ethical-design"
|
|
24
|
+
},
|
|
25
|
+
"ai-audit": {
|
|
26
|
+
"id": "ai-audit",
|
|
27
|
+
"name": "AI-Generated Code Audit",
|
|
28
|
+
"description": "6 rules catching COPPA risks commonly introduced by AI coding assistants.",
|
|
29
|
+
"jurisdiction": "international",
|
|
30
|
+
"jurisdiction_level": "advisory",
|
|
31
|
+
"is_free": true,
|
|
32
|
+
"effective_date": null,
|
|
33
|
+
"source_url": "https://runhalo.dev/ai-audit"
|
|
34
|
+
},
|
|
35
|
+
"au-sbd": {
|
|
36
|
+
"id": "au-sbd",
|
|
37
|
+
"name": "AU Safety by Design",
|
|
38
|
+
"description": "6 rules based on Australia's eSafety Commissioner Safety by Design framework.",
|
|
39
|
+
"jurisdiction": "AU-Federal",
|
|
40
|
+
"jurisdiction_level": "federal",
|
|
41
|
+
"is_free": false,
|
|
42
|
+
"effective_date": "2021-01-23",
|
|
43
|
+
"source_url": "https://www.esafety.gov.au/industry/safety-by-design"
|
|
44
|
+
},
|
|
45
|
+
"ut-sb142": {
|
|
46
|
+
"id": "ut-sb142",
|
|
47
|
+
"name": "Utah SB 142 (App Store Accountability)",
|
|
48
|
+
"description": "5 rules for Utah SB 142 compliance — age assurance, parental consent, minor account defaults, DM restrictions, and supervisory tools.",
|
|
49
|
+
"jurisdiction": "US-UT",
|
|
50
|
+
"jurisdiction_level": "state",
|
|
51
|
+
"is_free": true,
|
|
52
|
+
"effective_date": "2026-05-06",
|
|
53
|
+
"source_url": "https://le.utah.gov/~2025/bills/static/SB0142.html"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"rules": [
|
|
57
|
+
{
|
|
58
|
+
"id": "coppa-auth-001",
|
|
59
|
+
"name": "Unverified Social Login Providers",
|
|
60
|
+
"severity": "critical",
|
|
61
|
+
"category": "auth",
|
|
62
|
+
"description": "Social login (Google, Facebook, Twitter) without age gating is prohibited for child-directed apps",
|
|
63
|
+
"patterns": [
|
|
64
|
+
{ "pattern": "signInWithPopup\\s*\\(\\s*\\w+\\s*,\\s*['\"](google|facebook|twitter|github)['\"]", "flags": "gi" },
|
|
65
|
+
{ "pattern": "signInWithPopup\\s*\\(\\s*['\"](google|facebook|twitter|github)['\"]", "flags": "gi" },
|
|
66
|
+
{ "pattern": "signInWithPopup\\s*\\(\\s*\\w+\\s*,\\s*\\w+\\s*\\)", "flags": "gi" },
|
|
67
|
+
{ "pattern": "firebase\\.auth\\(\\)\\s*\\.\\s*signInWithPopup", "flags": "gi" },
|
|
68
|
+
{ "pattern": "passport\\.authenticate\\s*\\(\\s*['\"](google|facebook|twitter)['\"]", "flags": "gi" },
|
|
69
|
+
{ "pattern": "SOCIALACCOUNT_PROVIDERS\\s*=\\s*\\{[^}]*(?:google|facebook|twitter|github)", "flags": "gi" },
|
|
70
|
+
{ "pattern": "SOCIAL_AUTH_(?:GOOGLE|FACEBOOK|TWITTER|GITHUB)_(?:KEY|SECRET)", "flags": "gi" },
|
|
71
|
+
{ "pattern": "make_(?:google|facebook|twitter|github)_blueprint\\s*\\(", "flags": "gi" },
|
|
72
|
+
{ "pattern": "oauth\\.register\\s*\\(\\s*['\"](?:google|facebook|twitter|github)['\"]", "flags": "gi" },
|
|
73
|
+
{ "pattern": "goth\\.UseProviders\\s*\\(", "flags": "gi" },
|
|
74
|
+
{ "pattern": "\\.oauth2Login\\s*\\(\\s*\\)", "flags": "gi" },
|
|
75
|
+
{ "pattern": "ClientRegistration\\.withRegistrationId\\s*\\(\\s*['\"](?:google|facebook|twitter|github)['\"]", "flags": "gi" },
|
|
76
|
+
{ "pattern": "Firebase\\.auth\\.signInWithCredential", "flags": "gi" },
|
|
77
|
+
{ "pattern": "GoogleSignIn\\.getClient\\s*\\(", "flags": "gi" },
|
|
78
|
+
{ "pattern": "LoginManager\\.getInstance\\s*\\(\\s*\\)\\s*\\.logIn", "flags": "gi" }
|
|
79
|
+
],
|
|
80
|
+
"fix_suggestion": "Wrap the auth call in a conditional check for user.age >= 13 or use signInWithParentEmail() for children",
|
|
81
|
+
"penalty": "$51,744 per violation",
|
|
82
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin", "swift"],
|
|
83
|
+
"packs": ["coppa"],
|
|
84
|
+
"fixability": "guided",
|
|
85
|
+
"transform_type": null,
|
|
86
|
+
"scaffold_id": "age-gate-auth",
|
|
87
|
+
"guidance_url": null
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "coppa-data-002",
|
|
91
|
+
"name": "PII Collection in URL Parameters",
|
|
92
|
+
"severity": "high",
|
|
93
|
+
"category": "data",
|
|
94
|
+
"description": "Email, name, DOB, or phone in GET request URLs exposes PII in logs",
|
|
95
|
+
"patterns": [
|
|
96
|
+
{ "pattern": "(\\?|&)(email|first_?name|last_?name|dob|phone|birthdate)=", "flags": "gi" },
|
|
97
|
+
{ "pattern": "axios\\.get\\s*\\(\\s*[`'\"]https?://[^\\s]*\\?[^`'\"]*\\$\\{", "flags": "gi" },
|
|
98
|
+
{ "pattern": "fetch\\s*\\(\\s*[`'\"]https?://[^\\s]*\\?[^`'\"]*\\$\\{", "flags": "gi" },
|
|
99
|
+
{ "pattern": "\\?[^'\"`\\s]*\\$\\{[^}]*(?:\\.email|\\.firstName|\\.lastName|\\.dob|\\.phone)[^}]*\\}", "flags": "gi" }
|
|
100
|
+
],
|
|
101
|
+
"fix_suggestion": "Switch to POST method and move PII to request body",
|
|
102
|
+
"penalty": "$51,744 per violation",
|
|
103
|
+
"languages": ["typescript", "javascript", "python", "java", "swift"],
|
|
104
|
+
"packs": ["coppa"],
|
|
105
|
+
"fixability": "guided",
|
|
106
|
+
"transform_type": null,
|
|
107
|
+
"scaffold_id": "pii-sanitizer",
|
|
108
|
+
"guidance_url": null
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "coppa-tracking-003",
|
|
112
|
+
"name": "Third-Party Ad Trackers",
|
|
113
|
+
"severity": "critical",
|
|
114
|
+
"category": "tracking",
|
|
115
|
+
"description": "Facebook Pixel, Google Analytics, or other ad trackers without child_directed_treatment flag",
|
|
116
|
+
"patterns": [
|
|
117
|
+
{ "pattern": "fbq\\s*\\(\\s*['\"]init['\"]", "flags": "gi" },
|
|
118
|
+
{ "pattern": "ga\\s*\\(\\s*['\"]create['\"]", "flags": "gi" },
|
|
119
|
+
{ "pattern": "adsbygoogle", "flags": "gi" },
|
|
120
|
+
{ "pattern": "gtag\\s*\\(\\s*['\"]config['\"]", "flags": "gi" },
|
|
121
|
+
{ "pattern": "google-analytics\\.com/analytics\\.js", "flags": "gi" }
|
|
122
|
+
],
|
|
123
|
+
"fix_suggestion": "Add \"child_directed_treatment\": true or \"restrictDataProcessing\": true to SDK initialization",
|
|
124
|
+
"penalty": "$51,744 per violation",
|
|
125
|
+
"languages": ["typescript", "javascript", "html"],
|
|
126
|
+
"packs": ["coppa"],
|
|
127
|
+
"fixability": "guided",
|
|
128
|
+
"transform_type": null,
|
|
129
|
+
"scaffold_id": "remove-tracker",
|
|
130
|
+
"guidance_url": null
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "coppa-geo-004",
|
|
134
|
+
"name": "Precise Geolocation Collection",
|
|
135
|
+
"severity": "high",
|
|
136
|
+
"category": "geo",
|
|
137
|
+
"description": "High-accuracy geolocation without parental consent is prohibited",
|
|
138
|
+
"patterns": [
|
|
139
|
+
{ "pattern": "navigator\\.geolocation\\.getCurrentPosition", "flags": "gi" },
|
|
140
|
+
{ "pattern": "navigator\\.geolocation\\.watchPosition", "flags": "gi" },
|
|
141
|
+
{ "pattern": "CLLocationManager\\.startUpdatingLocation\\(\\)", "flags": "gi" },
|
|
142
|
+
{ "pattern": "locationServices\\.requestLocation", "flags": "gi" },
|
|
143
|
+
{ "pattern": "LocationManager\\s*\\.\\s*requestLocationUpdates\\s*\\(", "flags": "gi" },
|
|
144
|
+
{ "pattern": "FusedLocationProviderClient|fusedLocationClient\\s*\\.\\s*(?:requestLocationUpdates|getLastLocation|getCurrentLocation)", "flags": "gi" },
|
|
145
|
+
{ "pattern": "LocationRequest\\.create\\s*\\(\\s*\\)\\s*\\.\\s*setPriority\\s*\\(\\s*LocationRequest\\.PRIORITY_HIGH_ACCURACY", "flags": "gi" },
|
|
146
|
+
{ "pattern": "LocationRequest\\.Builder\\s*\\(\\s*Priority\\.PRIORITY_HIGH_ACCURACY", "flags": "gi" },
|
|
147
|
+
{ "pattern": "geocoder\\.(?:ip|google|osm|mapquest)\\s*\\(", "flags": "gi" },
|
|
148
|
+
{ "pattern": "(?:Nominatim|GoogleV3|Bing)\\s*\\([^)]*\\)\\s*\\.(?:geocode|reverse)", "flags": "gi" },
|
|
149
|
+
{ "pattern": "android\\.permission\\.ACCESS_FINE_LOCATION", "flags": "gi" }
|
|
150
|
+
],
|
|
151
|
+
"fix_suggestion": "Downgrade accuracy to kCLLocationAccuracyThreeKilometers or require parental consent",
|
|
152
|
+
"penalty": "$51,744 per violation",
|
|
153
|
+
"languages": ["typescript", "javascript", "swift", "kotlin", "java", "python", "xml"],
|
|
154
|
+
"packs": ["coppa"],
|
|
155
|
+
"fixability": "guided",
|
|
156
|
+
"transform_type": null,
|
|
157
|
+
"scaffold_id": "remove-geo",
|
|
158
|
+
"guidance_url": null
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "coppa-retention-005",
|
|
162
|
+
"name": "Missing Data Retention Policy",
|
|
163
|
+
"severity": "medium",
|
|
164
|
+
"category": "retention",
|
|
165
|
+
"description": "User schemas must have deleted_at, expiration_date, or TTL index for data retention",
|
|
166
|
+
"patterns": [
|
|
167
|
+
{ "pattern": "new\\s+Schema\\s*\\(\\s*\\{[^{}]*\\}", "flags": "gi" },
|
|
168
|
+
{ "pattern": "class\\s+(?:User|Child|Student|Profile|Account|Member)\\w*\\s*\\(\\s*models\\.Model\\s*\\)", "flags": "gi" },
|
|
169
|
+
{ "pattern": "class\\s+(?:User|Child|Student|Profile|Account|Member)\\w*\\s*\\(\\s*(?:Base|db\\.Model)\\s*\\)", "flags": "gi" },
|
|
170
|
+
{ "pattern": "type\\s+(?:User|Child|Student|Profile|Account|Member)\\w*\\s+struct\\s*\\{", "flags": "gi" },
|
|
171
|
+
{ "pattern": "@Entity[\\s\\S]*?class\\s+(?:User|Child|Student|Profile|Account|Member)", "flags": "gi" },
|
|
172
|
+
{ "pattern": "data\\s+class\\s+(?:User|Child|Student|Profile|Account|Member)\\w*\\s*\\(", "flags": "gi" }
|
|
173
|
+
],
|
|
174
|
+
"fix_suggestion": "Add deleted_at column, expiration_date field, or TTL index to database schema",
|
|
175
|
+
"penalty": "Regulatory audit failure",
|
|
176
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin", "sql"],
|
|
177
|
+
"packs": ["coppa"],
|
|
178
|
+
"fixability": "guided",
|
|
179
|
+
"transform_type": null,
|
|
180
|
+
"scaffold_id": "retention-policy",
|
|
181
|
+
"guidance_url": null
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"id": "coppa-sec-006",
|
|
185
|
+
"name": "Unencrypted PII Transmission",
|
|
186
|
+
"severity": "critical",
|
|
187
|
+
"category": "security",
|
|
188
|
+
"description": "HTTP transmission of PII exposes data in transit. All API endpoints handling personal information must use HTTPS.",
|
|
189
|
+
"patterns": [
|
|
190
|
+
{ "pattern": "http://[^\\s]*(/api/|/login|/user|/register|/profile)", "flags": "gi" },
|
|
191
|
+
{ "pattern": "http://localhost:[^\\s]*(/api/)", "flags": "gi" },
|
|
192
|
+
{ "pattern": "axios\\.get\\s*\\(\\s*['\"]http://", "flags": "gi" },
|
|
193
|
+
{ "pattern": "fetch\\s*\\(\\s*['\"]http://", "flags": "gi" },
|
|
194
|
+
{ "pattern": "http://[^\\s]*email[^\\s]*", "flags": "gi" }
|
|
195
|
+
],
|
|
196
|
+
"fix_suggestion": "Replace http:// with https:// for all API endpoints and resources",
|
|
197
|
+
"penalty": "Security breach liability + COPPA penalties",
|
|
198
|
+
"languages": ["typescript", "javascript", "python", "java", "swift"],
|
|
199
|
+
"packs": ["coppa"],
|
|
200
|
+
"fixability": "auto",
|
|
201
|
+
"transform_type": "url-upgrade",
|
|
202
|
+
"scaffold_id": null,
|
|
203
|
+
"guidance_url": null
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"id": "coppa-audio-007",
|
|
207
|
+
"name": "Unauthorized Audio Recording",
|
|
208
|
+
"severity": "high",
|
|
209
|
+
"category": "audio",
|
|
210
|
+
"description": "Audio recording without explicit user consent is prohibited. COPPA 2.0 clarifies voice prints as biometric data.",
|
|
211
|
+
"patterns": [
|
|
212
|
+
{ "pattern": "getUserMedia\\s*\\(\\s*\\{[^}]*audio\\s*:\\s*true[^}]*\\}", "flags": "gi" },
|
|
213
|
+
{ "pattern": "getUserMedia\\s*\\(\\s*\\{\\s*audio\\s*\\}", "flags": "gi" },
|
|
214
|
+
{ "pattern": "getUserMedia\\s*\\(\\s*\\{\\s*audio\\s*,", "flags": "gi" },
|
|
215
|
+
{ "pattern": "AVAudioSession\\s*\\.\\s*sharedInstance", "flags": "gi" },
|
|
216
|
+
{ "pattern": "AVAudioRecorder\\s*\\(", "flags": "gi" },
|
|
217
|
+
{ "pattern": "new\\s+AudioRecord\\s*\\(", "flags": "gi" },
|
|
218
|
+
{ "pattern": "new\\s+MediaRecorder\\s*\\(", "flags": "gi" }
|
|
219
|
+
],
|
|
220
|
+
"fix_suggestion": "Wrap audio recording in click handler and add parental consent check",
|
|
221
|
+
"penalty": "$51,744 per violation",
|
|
222
|
+
"languages": ["typescript", "javascript", "swift", "kotlin"],
|
|
223
|
+
"packs": ["coppa"],
|
|
224
|
+
"fixability": "guided",
|
|
225
|
+
"transform_type": null,
|
|
226
|
+
"scaffold_id": "consent-audio",
|
|
227
|
+
"guidance_url": null
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"id": "coppa-ui-008",
|
|
231
|
+
"name": "Missing Privacy Policy on Registration",
|
|
232
|
+
"severity": "medium",
|
|
233
|
+
"category": "ui",
|
|
234
|
+
"description": "Registration forms collecting PII must include a clear link to the privacy policy",
|
|
235
|
+
"patterns": [
|
|
236
|
+
{ "pattern": "\\b(?:SignUp|Register|Registration|CreateAccount)Form\\b", "flags": "gi" },
|
|
237
|
+
{ "pattern": "\\b(?:sign[-_]?up|register|registration|create[-_]?account)[-_]form\\b", "flags": "gi" },
|
|
238
|
+
{ "pattern": "<form[^>]*(?:id|class|name)\\s*=\\s*[\"'][^\"']*(?:register|signup|sign[-_]up|create[-_]account)[^\"']*[\"']", "flags": "gi" }
|
|
239
|
+
],
|
|
240
|
+
"fix_suggestion": "Add <a href=\"/privacy\">Privacy Policy</a> link to registration form footer",
|
|
241
|
+
"penalty": "Compliance failure",
|
|
242
|
+
"languages": ["typescript", "javascript", "html", "tsx", "jsx", "php"],
|
|
243
|
+
"packs": ["coppa"],
|
|
244
|
+
"fixability": "guided",
|
|
245
|
+
"transform_type": null,
|
|
246
|
+
"scaffold_id": "privacy-link",
|
|
247
|
+
"guidance_url": null
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"id": "coppa-flow-009",
|
|
251
|
+
"name": "Direct Contact Collection Without Parent Context",
|
|
252
|
+
"severity": "high",
|
|
253
|
+
"category": "flow",
|
|
254
|
+
"description": "Forms collecting child email/phone must also require parent email for consent verification",
|
|
255
|
+
"patterns": [
|
|
256
|
+
{ "pattern": "(child_email|student_email)\\s*:\\s*String", "flags": "gi" },
|
|
257
|
+
{ "pattern": "(child_email|student_email|kid_email)\\s*=", "flags": "gi" }
|
|
258
|
+
],
|
|
259
|
+
"fix_suggestion": "Make parent_email required when collecting child contact information",
|
|
260
|
+
"penalty": "$51,744 per violation",
|
|
261
|
+
"languages": ["typescript", "javascript", "python"],
|
|
262
|
+
"packs": ["coppa"],
|
|
263
|
+
"fixability": "guided",
|
|
264
|
+
"transform_type": null,
|
|
265
|
+
"scaffold_id": "parent-email",
|
|
266
|
+
"guidance_url": null
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"id": "coppa-sec-010",
|
|
270
|
+
"name": "Weak Default Student Passwords",
|
|
271
|
+
"severity": "medium",
|
|
272
|
+
"category": "security",
|
|
273
|
+
"description": "Default passwords like \"password\", \"123456\", or \"changeme\" create security vulnerabilities",
|
|
274
|
+
"patterns": [
|
|
275
|
+
{ "pattern": "(password|default_pass|temp_password)\\s*=\\s*['\"](123456|password|changeme|student|welcome)['\"]", "flags": "gi" },
|
|
276
|
+
{ "pattern": "defaultPassword:\\s*['\"](123456|password|changeme)['\"]", "flags": "gi" },
|
|
277
|
+
{ "pattern": "initialPassword:\\s*['\"](123456|password)['\"]", "flags": "gi" },
|
|
278
|
+
{ "pattern": "pass\\s*=\\s*['\"](student123|child123|default)['\"]", "flags": "gi" }
|
|
279
|
+
],
|
|
280
|
+
"fix_suggestion": "Use a secure random string generator for temporary credentials",
|
|
281
|
+
"penalty": "Security audit failure",
|
|
282
|
+
"languages": ["typescript", "javascript", "python", "java", "swift"],
|
|
283
|
+
"packs": ["coppa"],
|
|
284
|
+
"fixability": "auto",
|
|
285
|
+
"transform_type": "remove-default",
|
|
286
|
+
"scaffold_id": null,
|
|
287
|
+
"guidance_url": null
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"id": "coppa-ext-011",
|
|
291
|
+
"name": "Unmoderated Third-Party Chat",
|
|
292
|
+
"severity": "high",
|
|
293
|
+
"category": "external",
|
|
294
|
+
"description": "Third-party chat widgets (Intercom, Zendesk, Drift) allow children to disclose PII freely",
|
|
295
|
+
"patterns": [
|
|
296
|
+
{ "pattern": "intercom\\.init", "flags": "gi" },
|
|
297
|
+
{ "pattern": "zendesk\\.init", "flags": "gi" },
|
|
298
|
+
{ "pattern": "drift\\.init", "flags": "gi" },
|
|
299
|
+
{ "pattern": "<script[^>]+src=['\"][^'\"]*intercom", "flags": "gi" },
|
|
300
|
+
{ "pattern": "<script[^>]+src=['\"][^'\"]*(zendesk|zdassets)", "flags": "gi" },
|
|
301
|
+
{ "pattern": "Freshdesk|FreshChat", "flags": "gi" }
|
|
302
|
+
],
|
|
303
|
+
"fix_suggestion": "Disable chat widget for unauthenticated or under-13 users via conditional rendering",
|
|
304
|
+
"penalty": "$51,744 per violation",
|
|
305
|
+
"languages": ["typescript", "javascript", "html"],
|
|
306
|
+
"packs": ["coppa"],
|
|
307
|
+
"fixability": "guided",
|
|
308
|
+
"transform_type": null,
|
|
309
|
+
"scaffold_id": "chat-moderation",
|
|
310
|
+
"guidance_url": null
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"id": "coppa-bio-012",
|
|
314
|
+
"name": "Biometric Data Collection",
|
|
315
|
+
"severity": "critical",
|
|
316
|
+
"category": "biometric",
|
|
317
|
+
"description": "Face recognition, voice prints, or gait analysis requires explicit parental consent. COPPA 2.0 explicitly classifies biometrics as PI.",
|
|
318
|
+
"patterns": [
|
|
319
|
+
{ "pattern": "(?:import\\s+.*from\\s+['\"]face-api\\.js['\"]|require\\s*\\(\\s*['\"]face-api\\.js['\"]\\s*\\))", "flags": "gi" },
|
|
320
|
+
{ "pattern": "LocalAuthentication.*evaluatePolicy", "flags": "gi" },
|
|
321
|
+
{ "pattern": "FaceID|TouchID", "flags": "gi" },
|
|
322
|
+
{ "pattern": "biometricAuth|BiometricAuth", "flags": "g" },
|
|
323
|
+
{ "pattern": "voicePrint|VoicePrint", "flags": "g" },
|
|
324
|
+
{ "pattern": "livenessCheck|LivenessCheck", "flags": "g" },
|
|
325
|
+
{ "pattern": "FaceMatcher|FaceDetector|FaceRecognizer", "flags": "g" }
|
|
326
|
+
],
|
|
327
|
+
"fix_suggestion": "Ensure biometric data remains local-only (on-device) or obtain verifiable parental consent",
|
|
328
|
+
"penalty": "$51,744 per violation",
|
|
329
|
+
"languages": ["typescript", "javascript", "swift", "kotlin"],
|
|
330
|
+
"packs": ["coppa"],
|
|
331
|
+
"fixability": "guided",
|
|
332
|
+
"transform_type": null,
|
|
333
|
+
"scaffold_id": "consent-biometric",
|
|
334
|
+
"guidance_url": null
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"id": "coppa-notif-013",
|
|
338
|
+
"name": "Direct Push Notifications Without Consent",
|
|
339
|
+
"severity": "medium",
|
|
340
|
+
"category": "notification",
|
|
341
|
+
"description": "Push notifications are \"Online Contact Info\" under COPPA 2.0. Direct notifications to children require parental consent.",
|
|
342
|
+
"patterns": [
|
|
343
|
+
{ "pattern": "FirebaseMessaging\\.subscribeToTopic", "flags": "gi" },
|
|
344
|
+
{ "pattern": "OneSignal\\.promptForPushNotifications", "flags": "gi" },
|
|
345
|
+
{ "pattern": "sendPushNotification\\s*\\(", "flags": "gi" },
|
|
346
|
+
{ "pattern": "fcm\\.send\\s*\\(", "flags": "gi" },
|
|
347
|
+
{ "pattern": "PushManager\\.subscribe\\s*\\(", "flags": "gi" },
|
|
348
|
+
{ "pattern": "Notification\\.requestPermission", "flags": "gi" },
|
|
349
|
+
{ "pattern": "new\\s+Notification\\s*\\(", "flags": "gi" }
|
|
350
|
+
],
|
|
351
|
+
"fix_suggestion": "Gate push notification subscription behind parental dashboard setting",
|
|
352
|
+
"penalty": "$51,744 per violation",
|
|
353
|
+
"languages": ["typescript", "javascript", "swift", "kotlin"],
|
|
354
|
+
"packs": ["coppa"],
|
|
355
|
+
"fixability": "guided",
|
|
356
|
+
"transform_type": null,
|
|
357
|
+
"scaffold_id": "consent-notif",
|
|
358
|
+
"guidance_url": null
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
"id": "coppa-ugc-014",
|
|
362
|
+
"name": "UGC Upload Without PII Filter",
|
|
363
|
+
"severity": "high",
|
|
364
|
+
"category": "ugc",
|
|
365
|
+
"description": "Text areas for \"bio\", \"about me\", or comments must pass through PII scrubbing before database storage",
|
|
366
|
+
"patterns": [
|
|
367
|
+
{ "pattern": "<textarea[^>]*placeholder=[\"'](?:bio|about me|describe yourself)[^\"']*[\"']", "flags": "gi" },
|
|
368
|
+
{ "pattern": "user\\.bio\\s*=", "flags": "gi" },
|
|
369
|
+
{ "pattern": "aboutMe\\s*=", "flags": "gi" },
|
|
370
|
+
{ "pattern": "(?:submit|save|post)Comment\\s*\\(", "flags": "gi" },
|
|
371
|
+
{ "pattern": "saveBio\\s*\\(|updateBio\\s*\\(", "flags": "gi" },
|
|
372
|
+
{ "pattern": "commentForm.*submit|handleCommentSubmit", "flags": "gi" }
|
|
373
|
+
],
|
|
374
|
+
"fix_suggestion": "Add middleware hook for PII scrubbing (regex or AWS Comprehend) before database storage",
|
|
375
|
+
"penalty": "$51,744 per violation",
|
|
376
|
+
"languages": ["typescript", "javascript", "python"],
|
|
377
|
+
"packs": ["coppa"],
|
|
378
|
+
"fixability": "guided",
|
|
379
|
+
"transform_type": null,
|
|
380
|
+
"scaffold_id": "pii-filter",
|
|
381
|
+
"guidance_url": null
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"id": "coppa-sec-015",
|
|
385
|
+
"name": "Reflected XSS Risk",
|
|
386
|
+
"severity": "medium",
|
|
387
|
+
"category": "security",
|
|
388
|
+
"description": "DangerouslySetInnerHTML or innerHTML with user-controlled content creates XSS vulnerabilities",
|
|
389
|
+
"patterns": [
|
|
390
|
+
{ "pattern": "dangerouslySetInnerHTML\\s*=\\s*\\{\\s*\\{\\s*__html\\s*:\\s*(?!['\"]<)[^}]*\\}\\s*\\}", "flags": "gi" },
|
|
391
|
+
{ "pattern": "\\.innerHTML\\s*=\\s*\\$\\{", "flags": "gi" },
|
|
392
|
+
{ "pattern": "\\.innerHTML\\s*=\\s*(?!['\"]?\\s*['\"]?\\s*;)(?!.*[Ll]ocal(?:ize|ization))(?!.*styleContent)[^;]*\\b(?:user|input|query|param|req\\.|request\\.|body\\.|data\\.)\\w*", "flags": "gi" },
|
|
393
|
+
{ "pattern": "\\.html\\s*\\(\\s*(?:user|req\\.|request\\.|params?\\.)", "flags": "gi" },
|
|
394
|
+
{ "pattern": "v-html\\s*=\\s*[\"']?(?!.*sanitize)", "flags": "gi" }
|
|
395
|
+
],
|
|
396
|
+
"fix_suggestion": "Use standard JSX rendering or DOMPurify before setting HTML content",
|
|
397
|
+
"penalty": "Security failure",
|
|
398
|
+
"languages": ["typescript", "javascript", "tsx", "jsx", "vue"],
|
|
399
|
+
"packs": ["coppa"],
|
|
400
|
+
"fixability": "auto",
|
|
401
|
+
"transform_type": "sanitize-input",
|
|
402
|
+
"scaffold_id": null,
|
|
403
|
+
"guidance_url": null
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"id": "coppa-cookies-016",
|
|
407
|
+
"name": "Missing Cookie Notice",
|
|
408
|
+
"severity": "low",
|
|
409
|
+
"category": "cookies",
|
|
410
|
+
"description": "Cookies or localStorage storing tracking data or PII requires a consent banner",
|
|
411
|
+
"patterns": [
|
|
412
|
+
{ "pattern": "document\\.cookie\\s*=\\s*[^;]*(?:user|email|name|token|session|track|id|uid|analytics)", "flags": "gi" },
|
|
413
|
+
{ "pattern": "localStorage\\.setItem\\s*\\(\\s*['\"][^'\"]*(?:user|email|token|session|track|auth|login|id|uid|analytics)[^'\"]*['\"]", "flags": "gi" },
|
|
414
|
+
{ "pattern": "sessionStorage\\.setItem\\s*\\(\\s*['\"][^'\"]*(?:user|email|token|session|track|auth|login|id|uid|analytics)[^'\"]*['\"]", "flags": "gi" },
|
|
415
|
+
{ "pattern": "\\.set_cookie\\s*\\(\\s*['\"][^'\"]*(?:user|email|token|session|track|auth|login|uid|analytics)[^'\"]*['\"]", "flags": "gi" },
|
|
416
|
+
{ "pattern": "http\\.SetCookie\\s*\\(\\s*\\w+\\s*,\\s*&http\\.Cookie\\s*\\{", "flags": "gi" },
|
|
417
|
+
{ "pattern": "\\.addCookie\\s*\\(\\s*new\\s+Cookie\\s*\\(", "flags": "gi" },
|
|
418
|
+
{ "pattern": "ResponseCookie\\.from\\s*\\(", "flags": "gi" },
|
|
419
|
+
{ "pattern": "(?:set_cookie|SetCookie|addCookie|add_cookie)\\s*\\([^)]*(?:user|email|token|session|track|auth|uid|analytics)", "flags": "gi" }
|
|
420
|
+
],
|
|
421
|
+
"fix_suggestion": "Add a cookie consent banner component before setting tracking or PII cookies",
|
|
422
|
+
"penalty": "Compliance warning",
|
|
423
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin"],
|
|
424
|
+
"packs": ["coppa"],
|
|
425
|
+
"fixability": "guided",
|
|
426
|
+
"transform_type": null,
|
|
427
|
+
"scaffold_id": "consent-cookies",
|
|
428
|
+
"guidance_url": null
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"id": "coppa-ext-017",
|
|
432
|
+
"name": "Unwarned External Links",
|
|
433
|
+
"severity": "medium",
|
|
434
|
+
"category": "external",
|
|
435
|
+
"description": "External links in child-facing views should trigger a \"You are leaving...\" modal",
|
|
436
|
+
"patterns": [
|
|
437
|
+
{ "pattern": "<a[^>]+href=[\"']https?://(?!.*(?:privacy|terms|legal|tos|policy|consent|support|help|docs|documentation))[^\"']+[\"'][^>]*target=[\"']_blank[\"'][^>]*>", "flags": "gi" },
|
|
438
|
+
{ "pattern": "window\\.open\\s*\\(\\s*['\"]https?://(?!.*(?:privacy|terms|legal|tos|policy))", "flags": "gi" }
|
|
439
|
+
],
|
|
440
|
+
"fix_suggestion": "Wrap external links in SafeLink component with warning modal",
|
|
441
|
+
"penalty": "Warning",
|
|
442
|
+
"languages": ["typescript", "javascript", "html", "tsx", "jsx"],
|
|
443
|
+
"packs": ["coppa"],
|
|
444
|
+
"fixability": "guided",
|
|
445
|
+
"transform_type": null,
|
|
446
|
+
"scaffold_id": "exit-modal",
|
|
447
|
+
"guidance_url": null
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
"id": "coppa-analytics-018",
|
|
451
|
+
"name": "Mapping PII to Analytics User IDs",
|
|
452
|
+
"severity": "high",
|
|
453
|
+
"category": "analytics",
|
|
454
|
+
"description": "Passing email, name, or phone to analytics.identify() exposes PII to third parties",
|
|
455
|
+
"patterns": [
|
|
456
|
+
{ "pattern": "analytics\\.identify\\s*\\([^)]*email", "flags": "gi" },
|
|
457
|
+
{ "pattern": "mixpanel\\.identify.*email", "flags": "gi" },
|
|
458
|
+
{ "pattern": "segment\\.identify.*email", "flags": "gi" },
|
|
459
|
+
{ "pattern": "amplitude\\.identify.*email", "flags": "gi" },
|
|
460
|
+
{ "pattern": "identify\\s*\\(\\s*\\{[^}]*(?:email|name|phone)[^}]*\\}", "flags": "gi" },
|
|
461
|
+
{ "pattern": "analytics\\.identify\\s*\\(\\s*\\w+\\s*,\\s*\\{[^}]*(?:email|name|phone)", "flags": "gi" },
|
|
462
|
+
{ "pattern": "mp\\.people_set\\s*\\([^)]*(?:email|\\$email|name|phone)", "flags": "gi" },
|
|
463
|
+
{ "pattern": "analytics\\.Enqueue\\s*\\(\\s*analytics\\.Identify\\s*\\{[^}]*(?:Email|Name|Phone)", "flags": "gi" },
|
|
464
|
+
{ "pattern": "Amplitude\\.getInstance\\s*\\(\\s*\\)\\s*\\.setUserId\\s*\\([^)]*email", "flags": "gi" },
|
|
465
|
+
{ "pattern": "MixpanelAPI\\.\\w*identify\\s*\\([^)]*email", "flags": "gi" },
|
|
466
|
+
{ "pattern": "FirebaseAnalytics\\.setUserId\\s*\\([^)]*(?:email|name)", "flags": "gi" },
|
|
467
|
+
{ "pattern": "(?:setUserId|set_user_id)\\s*\\([^)]*(?:email|\\.name|phone)", "flags": "gi" }
|
|
468
|
+
],
|
|
469
|
+
"fix_suggestion": "Hash user ID and omit email/name from analytics payload",
|
|
470
|
+
"penalty": "$51,744 per violation",
|
|
471
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin"],
|
|
472
|
+
"packs": ["coppa"],
|
|
473
|
+
"fixability": "guided",
|
|
474
|
+
"transform_type": null,
|
|
475
|
+
"scaffold_id": "anonymize-analytics",
|
|
476
|
+
"guidance_url": null
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"id": "coppa-edu-019",
|
|
480
|
+
"name": "Missing Teacher/School Verification",
|
|
481
|
+
"severity": "medium",
|
|
482
|
+
"category": "education",
|
|
483
|
+
"description": "Teacher accounts using generic email (@gmail.com) bypass \"School Official\" consent exception",
|
|
484
|
+
"patterns": [
|
|
485
|
+
{ "pattern": "(?:teacher|educator)(?:Sign[Uu]p|[Rr]egist(?:er|ration))\\s*(?:\\(|=|:)", "flags": "gi" },
|
|
486
|
+
{ "pattern": "createTeacherAccount|registerTeacher|teacherAuth", "flags": "gi" },
|
|
487
|
+
{ "pattern": "role\\s*(?:=|:)\\s*['\"]teacher['\"].*(?:@gmail|@yahoo|@hotmail)", "flags": "gi" },
|
|
488
|
+
{ "pattern": "isTeacher\\s*&&\\s*!.*\\.edu", "flags": "gi" }
|
|
489
|
+
],
|
|
490
|
+
"fix_suggestion": "Restrict teacher sign-ups to verified EDU domains or require manual approval",
|
|
491
|
+
"penalty": "Loss of School Official consent status",
|
|
492
|
+
"languages": ["typescript", "javascript", "python"],
|
|
493
|
+
"packs": ["coppa"],
|
|
494
|
+
"fixability": "guided",
|
|
495
|
+
"transform_type": null,
|
|
496
|
+
"scaffold_id": "school-verify",
|
|
497
|
+
"guidance_url": null
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
"id": "coppa-default-020",
|
|
501
|
+
"name": "Default Public Profile Visibility",
|
|
502
|
+
"severity": "critical",
|
|
503
|
+
"category": "defaults",
|
|
504
|
+
"description": "Default profile visibility must be private. COPPA 2.0 requires privacy by design.",
|
|
505
|
+
"patterns": [
|
|
506
|
+
{ "pattern": "isProfileVisible:\\s*true", "flags": "gi" },
|
|
507
|
+
{ "pattern": "visibility:\\s*['\"]public['\"]", "flags": "gi" },
|
|
508
|
+
{ "pattern": "defaultPrivacy:\\s*['\"]public['\"]", "flags": "gi" },
|
|
509
|
+
{ "pattern": "isPublic:\\s*true[^}]*(profile|User)", "flags": "gi" },
|
|
510
|
+
{ "pattern": "profileVisibility\\s*=\\s*['\"]?(?:public|Public)['\"]?", "flags": "gi" }
|
|
511
|
+
],
|
|
512
|
+
"fix_suggestion": "Change default visibility to \"private\" or false",
|
|
513
|
+
"penalty": "$51,744 per violation",
|
|
514
|
+
"languages": ["typescript", "javascript", "python", "swift"],
|
|
515
|
+
"packs": ["coppa"],
|
|
516
|
+
"fixability": "auto",
|
|
517
|
+
"transform_type": "set-default",
|
|
518
|
+
"scaffold_id": null,
|
|
519
|
+
"guidance_url": null
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"id": "ETHICAL-001",
|
|
523
|
+
"name": "Infinite Scroll / Endless Feed",
|
|
524
|
+
"severity": "high",
|
|
525
|
+
"category": "ethical-design",
|
|
526
|
+
"description": "Infinite scroll exploits lack of impulse control. Children spend 2-3x longer on infinite feeds.",
|
|
527
|
+
"patterns": [
|
|
528
|
+
{ "pattern": "IntersectionObserver.*isIntersecting.*loadMore", "flags": "gi" },
|
|
529
|
+
{ "pattern": "window\\.addEventListener.*['\"]scroll['\"].*fetchNext", "flags": "gi" },
|
|
530
|
+
{ "pattern": "import.*InfiniteScroll", "flags": "gi" },
|
|
531
|
+
{ "pattern": "<InfiniteScroll", "flags": "gi" },
|
|
532
|
+
{ "pattern": "ngx-infinite-scroll", "flags": "gi" },
|
|
533
|
+
{ "pattern": "vue-infinite-loading", "flags": "gi" }
|
|
534
|
+
],
|
|
535
|
+
"fix_suggestion": "Replace infinite scroll with pagination or \"Load More\" buttons to create natural stopping points",
|
|
536
|
+
"penalty": "Ethical Design Violation",
|
|
537
|
+
"languages": ["typescript", "javascript", "tsx", "jsx", "vue"],
|
|
538
|
+
"packs": ["ethical"],
|
|
539
|
+
"fixability": "flag-only",
|
|
540
|
+
"transform_type": null,
|
|
541
|
+
"scaffold_id": null,
|
|
542
|
+
"guidance_url": "https://docs.runhalo.dev/ethical/infinite-scroll"
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
"id": "ETHICAL-002",
|
|
546
|
+
"name": "Streak Pressure Mechanics",
|
|
547
|
+
"severity": "high",
|
|
548
|
+
"category": "ethical-design",
|
|
549
|
+
"description": "Streak mechanics use loss aversion to manufacture daily compulsive usage",
|
|
550
|
+
"patterns": [
|
|
551
|
+
{ "pattern": "streak.*>.*0.*loss", "flags": "gi" },
|
|
552
|
+
{ "pattern": "lose.*your.*streak", "flags": "gi" },
|
|
553
|
+
{ "pattern": "streak.*ends.*in", "flags": "gi" },
|
|
554
|
+
{ "pattern": "don'?t.*break.*streak", "flags": "gi" },
|
|
555
|
+
{ "pattern": "dailyStreak", "flags": "gi" },
|
|
556
|
+
{ "pattern": "consecutiveDays", "flags": "gi" }
|
|
557
|
+
],
|
|
558
|
+
"fix_suggestion": "Remove streak loss penalties. Frame progress as cumulative (e.g., \"15 days practiced\") rather than consecutive.",
|
|
559
|
+
"penalty": "Ethical Design Violation",
|
|
560
|
+
"languages": ["typescript", "javascript", "swift", "kotlin"],
|
|
561
|
+
"packs": ["ethical"],
|
|
562
|
+
"fixability": "flag-only",
|
|
563
|
+
"transform_type": null,
|
|
564
|
+
"scaffold_id": null,
|
|
565
|
+
"guidance_url": "https://docs.runhalo.dev/ethical/streak-pressure"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
"id": "ETHICAL-003",
|
|
569
|
+
"name": "Variable Ratio Rewards (Loot Boxes)",
|
|
570
|
+
"severity": "critical",
|
|
571
|
+
"category": "ethical-design",
|
|
572
|
+
"description": "Randomized rewards (loot boxes, gacha) exploit gambling psychology in developing brains",
|
|
573
|
+
"patterns": [
|
|
574
|
+
{ "pattern": "Math\\.random\\(\\).*reward", "flags": "gi" },
|
|
575
|
+
{ "pattern": "loot_?box", "flags": "gi" },
|
|
576
|
+
{ "pattern": "gacha", "flags": "gi" },
|
|
577
|
+
{ "pattern": "mystery_?crate", "flags": "gi" },
|
|
578
|
+
{ "pattern": "openCrate", "flags": "gi" },
|
|
579
|
+
{ "pattern": "rarityTable", "flags": "gi" },
|
|
580
|
+
{ "pattern": "dropRates", "flags": "gi" }
|
|
581
|
+
],
|
|
582
|
+
"fix_suggestion": "Replace random rewards with transparent, effort-based rewards. Disclosure of odds is a minimum requirement.",
|
|
583
|
+
"penalty": "Ethical Design Violation",
|
|
584
|
+
"languages": ["typescript", "javascript", "python", "csharp"],
|
|
585
|
+
"packs": ["ethical"],
|
|
586
|
+
"fixability": "flag-only",
|
|
587
|
+
"transform_type": null,
|
|
588
|
+
"scaffold_id": null,
|
|
589
|
+
"guidance_url": "https://docs.runhalo.dev/ethical/variable-rewards"
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
"id": "ETHICAL-004",
|
|
593
|
+
"name": "Manipulative Notification Language",
|
|
594
|
+
"severity": "medium",
|
|
595
|
+
"category": "ethical-design",
|
|
596
|
+
"description": "Notifications using urgency (\"Hurry!\", \"Missing out\") manipulate children's fear of social exclusion",
|
|
597
|
+
"patterns": [
|
|
598
|
+
{ "pattern": "hurry|limited\\s*time|missing\\s*out|don'?t\\s*miss|left\\s*behind", "flags": "gi" },
|
|
599
|
+
{ "pattern": "everyone\\s*else\\s*is", "flags": "gi" },
|
|
600
|
+
{ "pattern": "last\\s*chance", "flags": "gi" },
|
|
601
|
+
{ "pattern": "running\\s*out", "flags": "gi" }
|
|
602
|
+
],
|
|
603
|
+
"fix_suggestion": "Use neutral, informational language. Avoid FOMO (Fear Of Missing Out) triggers.",
|
|
604
|
+
"penalty": "Ethical Design Violation",
|
|
605
|
+
"languages": ["typescript", "javascript", "json", "xml"],
|
|
606
|
+
"packs": ["ethical"],
|
|
607
|
+
"fixability": "flag-only",
|
|
608
|
+
"transform_type": null,
|
|
609
|
+
"scaffold_id": null,
|
|
610
|
+
"guidance_url": "https://docs.runhalo.dev/ethical/manipulative-notifications"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
"id": "ETHICAL-005",
|
|
614
|
+
"name": "Artificial Scarcity / Countdowns",
|
|
615
|
+
"severity": "medium",
|
|
616
|
+
"category": "ethical-design",
|
|
617
|
+
"description": "Fake scarcity (\"Only 2 left!\") and countdown timers pressure children into impulsive decisions",
|
|
618
|
+
"patterns": [
|
|
619
|
+
{ "pattern": "CountdownTimer", "flags": "gi" },
|
|
620
|
+
{ "pattern": "only\\s*\\d+\\s*left", "flags": "gi" },
|
|
621
|
+
{ "pattern": "offer\\s*ends\\s*in", "flags": "gi" },
|
|
622
|
+
{ "pattern": "selling\\s*fast", "flags": "gi" },
|
|
623
|
+
{ "pattern": "almost\\s*sold\\s*out", "flags": "gi" },
|
|
624
|
+
{ "pattern": "limited\\s*availability", "flags": "gi" }
|
|
625
|
+
],
|
|
626
|
+
"fix_suggestion": "Remove artificial urgency. If an offer expires, state the date calmly without countdown pressure.",
|
|
627
|
+
"penalty": "Ethical Design Violation",
|
|
628
|
+
"languages": ["typescript", "javascript", "tsx", "jsx", "html"],
|
|
629
|
+
"packs": ["ethical"],
|
|
630
|
+
"fixability": "flag-only",
|
|
631
|
+
"transform_type": null,
|
|
632
|
+
"scaffold_id": null,
|
|
633
|
+
"guidance_url": "https://docs.runhalo.dev/ethical/artificial-scarcity"
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
"id": "AI-AUDIT-001",
|
|
637
|
+
"name": "Placeholder Analytics Script",
|
|
638
|
+
"severity": "high",
|
|
639
|
+
"category": "ai-audit",
|
|
640
|
+
"description": "AI-generated code frequently includes placeholder analytics (UA-XXXXX, G-XXXXXX, fbq) copied from training data. These may activate real tracking without child_directed_treatment flags.",
|
|
641
|
+
"patterns": [
|
|
642
|
+
{ "pattern": "gtag\\s*\\(\\s*['\"]config['\"]\\s*,\\s*['\"](?:UA-|G-)X{3,}['\"]", "flags": "gi" },
|
|
643
|
+
{ "pattern": "fbq\\s*\\(\\s*['\"]init['\"]\\s*,\\s*['\"](?:0{5,}|1{5,}|X{5,}|YOUR_|PIXEL_ID|123456789)['\"]", "flags": "gi" },
|
|
644
|
+
{ "pattern": "ga\\s*\\(\\s*['\"]create['\"]\\s*,\\s*['\"]UA-(?:0{5,}|X{5,}|YOUR_)['\"]", "flags": "gi" },
|
|
645
|
+
{ "pattern": "['\"](?:UA|G)-(?:XXXXXXX|0000000|YOUR_ID|REPLACE_ME)['\"]", "flags": "gi" },
|
|
646
|
+
{ "pattern": "analytics_id\\s*[:=]\\s*['\"](?:placeholder|test|example|TODO|FIXME)['\"]", "flags": "gi" }
|
|
647
|
+
],
|
|
648
|
+
"fix_suggestion": "Remove placeholder analytics IDs. If analytics are needed, use a COPPA-compliant provider with child_directed_treatment: true.",
|
|
649
|
+
"penalty": "AI-generated compliance risk",
|
|
650
|
+
"languages": ["typescript", "javascript", "html"],
|
|
651
|
+
"packs": ["ai-audit"],
|
|
652
|
+
"fixability": "flag-only",
|
|
653
|
+
"transform_type": null,
|
|
654
|
+
"scaffold_id": null,
|
|
655
|
+
"guidance_url": null
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"id": "AI-AUDIT-002",
|
|
659
|
+
"name": "AI-Generated Hardcoded Secrets",
|
|
660
|
+
"severity": "critical",
|
|
661
|
+
"category": "ai-audit",
|
|
662
|
+
"description": "AI coding assistants frequently generate placeholder API keys, tokens, and secrets inline. These may be committed to version control and exposed.",
|
|
663
|
+
"patterns": [
|
|
664
|
+
{ "pattern": "(?:api_?key|apiKey|API_KEY)\\s*[:=]\\s*['\"](?:sk-|pk-|ak-|key-)[a-zA-Z0-9]{10,}['\"]", "flags": "gi" },
|
|
665
|
+
{ "pattern": "(?:secret|SECRET|token|TOKEN)\\s*[:=]\\s*['\"](?!process\\.env)[a-zA-Z0-9_-]{20,}['\"]", "flags": "gi" },
|
|
666
|
+
{ "pattern": "SUPABASE_(?:ANON_KEY|SERVICE_ROLE_KEY)\\s*[:=]\\s*['\"]ey[a-zA-Z0-9_.+-]{30,}['\"]", "flags": "gi" },
|
|
667
|
+
{ "pattern": "FIREBASE_(?:API_KEY|CONFIG)\\s*[:=]\\s*['\"]AI[a-zA-Z0-9_-]{30,}['\"]", "flags": "gi" },
|
|
668
|
+
{ "pattern": "(?:password|passwd|pwd)\\s*[:=]\\s*['\"](?!process\\.env)[^'\"]{8,}['\"]\\s*(?:,|;|\\})", "flags": "gi" }
|
|
669
|
+
],
|
|
670
|
+
"fix_suggestion": "Move all secrets to environment variables. Use process.env.API_KEY or a secrets manager. Never hardcode credentials.",
|
|
671
|
+
"penalty": "Security exposure — credentials in source code",
|
|
672
|
+
"languages": ["typescript", "javascript", "python", "java"],
|
|
673
|
+
"packs": ["ai-audit"],
|
|
674
|
+
"fixability": "flag-only",
|
|
675
|
+
"transform_type": null,
|
|
676
|
+
"scaffold_id": null,
|
|
677
|
+
"guidance_url": null
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"id": "AI-AUDIT-003",
|
|
681
|
+
"name": "Hallucinated/Placeholder API URLs",
|
|
682
|
+
"severity": "medium",
|
|
683
|
+
"category": "ai-audit",
|
|
684
|
+
"description": "AI models often generate fake API endpoints (api.example.com, jsonplaceholder, reqres.in) that may be replaced with real endpoints without proper review.",
|
|
685
|
+
"patterns": [
|
|
686
|
+
{ "pattern": "fetch\\s*\\(\\s*['\"]https?://(?:api\\.example\\.com|jsonplaceholder\\.typicode\\.com|reqres\\.in|httpbin\\.org|mockapi\\.io|dummyjson\\.com)[^'\"]*['\"]", "flags": "gi" },
|
|
687
|
+
{ "pattern": "axios\\.\\w+\\s*\\(\\s*['\"]https?://(?:api\\.example\\.com|jsonplaceholder\\.typicode\\.com|reqres\\.in|httpbin\\.org|dummyjson\\.com)[^'\"]*['\"]", "flags": "gi" },
|
|
688
|
+
{ "pattern": "(?:BASE_URL|API_URL|ENDPOINT)\\s*[:=]\\s*['\"]https?://(?:api\\.example\\.com|your-api|my-api|TODO|REPLACE)[^'\"]*['\"]", "flags": "gi" }
|
|
689
|
+
],
|
|
690
|
+
"fix_suggestion": "Replace placeholder URLs with actual endpoints from environment variables. Review all API calls for COPPA data handling compliance.",
|
|
691
|
+
"penalty": "AI-generated placeholder risk",
|
|
692
|
+
"languages": ["typescript", "javascript", "python"],
|
|
693
|
+
"packs": ["ai-audit"],
|
|
694
|
+
"fixability": "flag-only",
|
|
695
|
+
"transform_type": null,
|
|
696
|
+
"scaffold_id": null,
|
|
697
|
+
"guidance_url": null
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
"id": "AI-AUDIT-004",
|
|
701
|
+
"name": "Copy-Paste Tracking Boilerplate",
|
|
702
|
+
"severity": "high",
|
|
703
|
+
"category": "ai-audit",
|
|
704
|
+
"description": "AI assistants reproduce common analytics setup patterns from training data. These often include user identification, event tracking, and session recording without consent flows.",
|
|
705
|
+
"patterns": [
|
|
706
|
+
{ "pattern": "hotjar\\.init\\s*\\(", "flags": "gi" },
|
|
707
|
+
{ "pattern": "Sentry\\.init\\s*\\(\\s*\\{[^}]*dsn", "flags": "gi" },
|
|
708
|
+
{ "pattern": "LogRocket\\.init\\s*\\(", "flags": "gi" },
|
|
709
|
+
{ "pattern": "FullStory\\.init\\s*\\(", "flags": "gi" },
|
|
710
|
+
{ "pattern": "heap\\.load\\s*\\(", "flags": "gi" },
|
|
711
|
+
{ "pattern": "posthog\\.init\\s*\\(", "flags": "gi" },
|
|
712
|
+
{ "pattern": "amplitude\\.init\\s*\\(", "flags": "gi" },
|
|
713
|
+
{ "pattern": "mixpanel\\.init\\s*\\(", "flags": "gi" },
|
|
714
|
+
{ "pattern": "window\\.clarity\\s*\\(", "flags": "gi" }
|
|
715
|
+
],
|
|
716
|
+
"fix_suggestion": "Remove session recording and analytics initialization unless COPPA consent is obtained. These tools capture keystrokes, mouse movements, and user behavior — all PII for children.",
|
|
717
|
+
"penalty": "Third-party data collection without consent",
|
|
718
|
+
"languages": ["typescript", "javascript", "html"],
|
|
719
|
+
"packs": ["ai-audit"],
|
|
720
|
+
"fixability": "flag-only",
|
|
721
|
+
"transform_type": null,
|
|
722
|
+
"scaffold_id": null,
|
|
723
|
+
"guidance_url": null
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
"id": "AI-AUDIT-005",
|
|
727
|
+
"name": "AI-Generated Insecure Defaults",
|
|
728
|
+
"severity": "medium",
|
|
729
|
+
"category": "ai-audit",
|
|
730
|
+
"description": "AI models commonly generate code with insecure default configurations: CORS *, disabled SSL verification, permissive CSP, or open CORS origins.",
|
|
731
|
+
"patterns": [
|
|
732
|
+
{ "pattern": "cors\\s*\\(\\s*\\{\\s*origin\\s*:\\s*(?:['\"]?\\*['\"]?|true)", "flags": "gi" },
|
|
733
|
+
{ "pattern": "Access-Control-Allow-Origin['\"]\\s*,\\s*['\"]\\*", "flags": "gi" },
|
|
734
|
+
{ "pattern": "rejectUnauthorized\\s*:\\s*false", "flags": "gi" },
|
|
735
|
+
{ "pattern": "NODE_TLS_REJECT_UNAUTHORIZED\\s*=\\s*['\"]?0['\"]?", "flags": "gi" },
|
|
736
|
+
{ "pattern": "content-security-policy['\"]\\s*,\\s*['\"]default-src\\s+\\*", "flags": "gi" },
|
|
737
|
+
{ "pattern": "sameSite\\s*:\\s*['\"]none['\"]\\s*,?\\s*secure\\s*:\\s*false", "flags": "gi" }
|
|
738
|
+
],
|
|
739
|
+
"fix_suggestion": "Replace wildcard CORS with specific allowed origins. Enable SSL verification. Use restrictive CSP. Set secure cookies.",
|
|
740
|
+
"penalty": "Security misconfiguration",
|
|
741
|
+
"languages": ["typescript", "javascript", "python"],
|
|
742
|
+
"packs": ["ai-audit"],
|
|
743
|
+
"fixability": "flag-only",
|
|
744
|
+
"transform_type": null,
|
|
745
|
+
"scaffold_id": null,
|
|
746
|
+
"guidance_url": null
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
"id": "AI-AUDIT-006",
|
|
750
|
+
"name": "Unresolved Compliance TODOs",
|
|
751
|
+
"severity": "low",
|
|
752
|
+
"category": "ai-audit",
|
|
753
|
+
"description": "AI-generated code often includes TODO/FIXME comments for compliance-related features (consent, age verification, privacy policy) that may ship unimplemented.",
|
|
754
|
+
"patterns": [
|
|
755
|
+
{ "pattern": "//\\s*(?:TODO|FIXME|HACK|XXX).*(?:consent|age\\s*verif|privacy\\s*policy|parental|coppa|gdpr|data\\s*retention)", "flags": "gi" },
|
|
756
|
+
{ "pattern": "//\\s*(?:TODO|FIXME).*(?:implement|add|need|require).*(?:auth|login|permission|access\\s*control)", "flags": "gi" },
|
|
757
|
+
{ "pattern": "#\\s*(?:TODO|FIXME).*(?:consent|age\\s*verif|privacy|parental|coppa)", "flags": "gi" }
|
|
758
|
+
],
|
|
759
|
+
"fix_suggestion": "Resolve all compliance-related TODOs before shipping. Each unresolved TODO is a potential COPPA violation in production.",
|
|
760
|
+
"penalty": "Unimplemented compliance requirement",
|
|
761
|
+
"languages": ["typescript", "javascript", "python", "java", "swift", "kotlin"],
|
|
762
|
+
"packs": ["ai-audit"],
|
|
763
|
+
"fixability": "flag-only",
|
|
764
|
+
"transform_type": null,
|
|
765
|
+
"scaffold_id": null,
|
|
766
|
+
"guidance_url": null
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
"id": "AU-SBD-001",
|
|
770
|
+
"name": "Default Public Profile Visibility",
|
|
771
|
+
"severity": "high",
|
|
772
|
+
"category": "safety-by-design",
|
|
773
|
+
"description": "User profiles default to public or visible. AU Safety by Design requires privacy-by-default for minors — profiles should be private until explicitly changed by the user or a verified parent.",
|
|
774
|
+
"patterns": [
|
|
775
|
+
{ "pattern": "(?:visibility|profile_?visibility|is_?public|isPublic)\\s*[:=]\\s*(?:['\"]public['\"]|true)", "flags": "gi" },
|
|
776
|
+
{ "pattern": "default(?:_?visibility|_?privacy|_?profile)\\s*[:=]\\s*['\"](?:public|open|visible|everyone)['\"]", "flags": "gi" },
|
|
777
|
+
{ "pattern": "(?:privacy|profilePrivacy)\\s*[:=]\\s*\\{[^}]*default\\s*:\\s*['\"](?:public|open|everyone)['\"]", "flags": "gi" },
|
|
778
|
+
{ "pattern": "(?:showProfile|profileVisible|publicByDefault|show_profile)\\s*[:=]\\s*true", "flags": "gi" },
|
|
779
|
+
{ "pattern": "(?:searchable|discoverable|findable)\\s*[:=]\\s*true\\b", "flags": "gi" }
|
|
780
|
+
],
|
|
781
|
+
"fix_suggestion": "Set profile visibility to \"private\" by default. Require explicit user action (or parental consent for children) to make profiles public. AU SbD Principle 1: safety as a fundamental design consideration.",
|
|
782
|
+
"penalty": "Default public exposure of minor profiles",
|
|
783
|
+
"languages": ["typescript", "javascript", "python", "java", "swift", "kotlin", "php", "csharp"],
|
|
784
|
+
"packs": ["au-sbd"],
|
|
785
|
+
"fixability": "flag-only",
|
|
786
|
+
"transform_type": null,
|
|
787
|
+
"scaffold_id": null,
|
|
788
|
+
"guidance_url": null
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
"id": "AU-SBD-002",
|
|
792
|
+
"name": "Social Features Without Report/Block",
|
|
793
|
+
"severity": "medium",
|
|
794
|
+
"category": "safety-by-design",
|
|
795
|
+
"description": "Social interaction features (comments, posts, messaging) detected without corresponding report or block mechanisms. AU SbD Principle 2 requires users to have tools to protect themselves from harmful interactions.",
|
|
796
|
+
"patterns": [
|
|
797
|
+
{ "pattern": "(?:addComment|postComment|submitComment|createComment|commentCreate)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
798
|
+
{ "pattern": "(?:sendMessage|createMessage|postMessage|submitMessage|messageCreate)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
799
|
+
{ "pattern": "(?:createPost|submitPost|publishPost|addPost|postCreate)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
800
|
+
{ "pattern": "(?:addReview|submitReview|createReview|postReview|reviewCreate)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
801
|
+
{ "pattern": "(?:shareContent|createShare|submitShare)\\s*(?:=|:|\\()", "flags": "gi" }
|
|
802
|
+
],
|
|
803
|
+
"fix_suggestion": "Implement report and block mechanisms alongside every social feature. Users must be able to report harmful content and block abusive accounts. AU SbD Principle 2: user empowerment and autonomy.",
|
|
804
|
+
"penalty": "Social features without safety controls",
|
|
805
|
+
"languages": ["typescript", "javascript", "python", "java", "swift", "kotlin", "php"],
|
|
806
|
+
"packs": ["au-sbd"],
|
|
807
|
+
"fixability": "flag-only",
|
|
808
|
+
"transform_type": null,
|
|
809
|
+
"scaffold_id": null,
|
|
810
|
+
"guidance_url": null
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
"id": "AU-SBD-003",
|
|
814
|
+
"name": "Unrestricted Direct Messaging for Minors",
|
|
815
|
+
"severity": "critical",
|
|
816
|
+
"category": "safety-by-design",
|
|
817
|
+
"description": "Direct messaging or chat functionality without safety controls (contact restrictions, message filtering, or parental oversight). The AU Online Safety Act requires platforms to take reasonable steps to prevent child exploitation in private communications.",
|
|
818
|
+
"patterns": [
|
|
819
|
+
{ "pattern": "(?:directMessage|sendDM|privateMess|createDM|dmChannel|startChat|privateChat|initiateChat)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
820
|
+
{ "pattern": "(?:WebSocket|io\\.connect|socket\\.emit)\\s*\\([^)]*(?:chat|message|dm|private)", "flags": "gi" },
|
|
821
|
+
{ "pattern": "(?:allowDMs?|enableDMs?|allow_?direct_?message|enable_?private_?message)\\s*[:=]\\s*true", "flags": "gi" },
|
|
822
|
+
{ "pattern": "(?:contactStranger|messageAnyone|openChat|unrestricted_?message)\\s*[:=]\\s*true", "flags": "gi" }
|
|
823
|
+
],
|
|
824
|
+
"fix_suggestion": "Add safety controls to messaging: restrict contacts to approved friends, implement message filtering, enable parental oversight, and log communications for safety review. AU Online Safety Act 2021 s.45-46.",
|
|
825
|
+
"penalty": "Unrestricted private communication channel",
|
|
826
|
+
"languages": ["typescript", "javascript", "python", "java", "swift", "kotlin"],
|
|
827
|
+
"packs": ["au-sbd"],
|
|
828
|
+
"fixability": "flag-only",
|
|
829
|
+
"transform_type": null,
|
|
830
|
+
"scaffold_id": null,
|
|
831
|
+
"guidance_url": null
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
"id": "AU-SBD-004",
|
|
835
|
+
"name": "Recommendation Algorithm Without Safety Guardrails",
|
|
836
|
+
"severity": "high",
|
|
837
|
+
"category": "safety-by-design",
|
|
838
|
+
"description": "Content recommendation or feed algorithms detected without safety filtering, content classification, or age-appropriate guardrails. AU SbD requires platforms to assess and mitigate algorithmic harms, particularly for young users.",
|
|
839
|
+
"patterns": [
|
|
840
|
+
{ "pattern": "(?:recommendContent|getRecommendations|suggestContent|personalizedFeed|forYouFeed|contentFeed)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
841
|
+
{ "pattern": "(?:algorithm|algo)(?:_?feed|_?rank|_?recommend|_?suggest)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
842
|
+
{ "pattern": "(?:trending|viral|popular)(?:_?content|_?posts|_?feed|_?items)\\s*(?:=|:|\\()", "flags": "gi" },
|
|
843
|
+
{ "pattern": "(?:engagement_?score|clickBait|engagement_?rank|watch_?next|autoplay_?next)\\s*[:=]", "flags": "gi" },
|
|
844
|
+
{ "pattern": "(?:rabbit_?hole|endless_?feed|infinite_?recommend|auto_?suggest)\\s*[:=]\\s*true", "flags": "gi" }
|
|
845
|
+
],
|
|
846
|
+
"fix_suggestion": "Add age-appropriate content filters to recommendation algorithms. Classify content before serving, implement safety guardrails, and provide transparency on how content is selected. AU SbD Principle 3: transparency and accountability.",
|
|
847
|
+
"penalty": "Unfiltered algorithmic content delivery",
|
|
848
|
+
"languages": ["typescript", "javascript", "python", "java", "swift", "kotlin"],
|
|
849
|
+
"packs": ["au-sbd"],
|
|
850
|
+
"fixability": "flag-only",
|
|
851
|
+
"transform_type": null,
|
|
852
|
+
"scaffold_id": null,
|
|
853
|
+
"guidance_url": null
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
"id": "AU-SBD-005",
|
|
857
|
+
"name": "Engagement Features Without Time Awareness",
|
|
858
|
+
"severity": "medium",
|
|
859
|
+
"category": "safety-by-design",
|
|
860
|
+
"description": "High-engagement features (autoplay, continuous scrolling, notifications) detected without corresponding digital wellbeing controls (screen time limits, break reminders, usage dashboards). AU SbD encourages platforms to build in digital wellbeing tools.",
|
|
861
|
+
"patterns": [
|
|
862
|
+
{ "pattern": "(?:autoplay|auto_?play)\\s*[:=]\\s*true", "flags": "gi" },
|
|
863
|
+
{ "pattern": "(?:autoPlay|autoPlayNext|playNext|nextEpisode)\\s*[:=]\\s*true", "flags": "gi" },
|
|
864
|
+
{ "pattern": "(?:continuous_?play|binge_?mode|marathon_?mode|watch_?party)\\s*[:=]\\s*true", "flags": "gi" },
|
|
865
|
+
{ "pattern": "(?:push_?notification|sendNotification|scheduleNotif|notif_?trigger).*(?:re_?engage|comeback|miss_?you|inactive)", "flags": "gi" },
|
|
866
|
+
{ "pattern": "(?:daily_?reward|login_?bonus|daily_?streak|come_?back_?reward)\\s*(?:=|:|\\()", "flags": "gi" }
|
|
867
|
+
],
|
|
868
|
+
"fix_suggestion": "Implement digital wellbeing features: screen time dashboards, break reminders after sustained use, and configurable usage limits (especially for accounts under 18). AU SbD: promote healthy technology use.",
|
|
869
|
+
"penalty": "Engagement-maximizing features without wellbeing controls",
|
|
870
|
+
"languages": ["typescript", "javascript", "python", "java", "swift", "kotlin"],
|
|
871
|
+
"packs": ["au-sbd"],
|
|
872
|
+
"fixability": "flag-only",
|
|
873
|
+
"transform_type": null,
|
|
874
|
+
"scaffold_id": null,
|
|
875
|
+
"guidance_url": null
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"id": "AU-SBD-006",
|
|
879
|
+
"name": "Location Data Without Explicit Consent",
|
|
880
|
+
"severity": "critical",
|
|
881
|
+
"category": "safety-by-design",
|
|
882
|
+
"description": "Location data collection or sharing enabled without explicit, informed opt-in. AU SbD and the Privacy Act 1988 require data minimization, especially for children's geolocation data — location should never be collected by default.",
|
|
883
|
+
"patterns": [
|
|
884
|
+
{ "pattern": "(?:shareLocation|share_?location|locationSharing|broadcastLocation)\\s*[:=]\\s*true", "flags": "gi" },
|
|
885
|
+
{ "pattern": "(?:location_?visible|show_?location|display_?location|location_?public)\\s*[:=]\\s*true", "flags": "gi" },
|
|
886
|
+
{ "pattern": "(?:trackLocation|location_?tracking|geo_?tracking|locationTracker)\\s*[:=]\\s*true", "flags": "gi" },
|
|
887
|
+
{ "pattern": "navigator\\.geolocation\\.(?:watchPosition|getCurrentPosition)\\s*\\(", "flags": "gi" },
|
|
888
|
+
{ "pattern": "(?:CLLocationManager|LocationManager|FusedLocationProvider).*(?:startUpdating|requestLocation|requestLocationUpdates)", "flags": "gi" }
|
|
889
|
+
],
|
|
890
|
+
"fix_suggestion": "Require explicit opt-in for any location data collection. Never enable location sharing by default. For minors, require parental consent before any geolocation access. AU Privacy Act 1988 APP 3.2.",
|
|
891
|
+
"penalty": "Default location data exposure",
|
|
892
|
+
"languages": ["typescript", "javascript", "python", "java", "swift", "kotlin"],
|
|
893
|
+
"packs": ["au-sbd"],
|
|
894
|
+
"fixability": "flag-only",
|
|
895
|
+
"transform_type": null,
|
|
896
|
+
"scaffold_id": null,
|
|
897
|
+
"guidance_url": null
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
"id": "ut-sb142-001",
|
|
901
|
+
"name": "Minor Account Creation Without Age Assurance",
|
|
902
|
+
"severity": "critical",
|
|
903
|
+
"category": "age-verification",
|
|
904
|
+
"description": "Utah SB 142 requires an age assurance system with at least 95% accuracy to identify minor account holders (under 18). Account creation flows that lack age verification gates violate this requirement.",
|
|
905
|
+
"patterns": [
|
|
906
|
+
{ "pattern": "(?:createUser|signUp|register)\\s*\\((?![^)]*(?:age|dateOfBirth|dob|birthDate|birth_date))[^)]*\\)", "flags": "gi" },
|
|
907
|
+
{ "pattern": "(?:create_user|sign_up|create_account)\\s*\\((?![^)]*(?:age|date_of_birth|dob|birth_date))[^)]*\\)", "flags": "gi" },
|
|
908
|
+
{ "pattern": "(?:UserFactory|AccountFactory|RegistrationService)\\.(?:create|build|register)\\s*\\(", "flags": "gi" },
|
|
909
|
+
{ "pattern": "INSERT\\s+INTO\\s+(?:users|accounts)\\s*\\([^)]*\\)\\s*VALUES", "flags": "gi" }
|
|
910
|
+
],
|
|
911
|
+
"fix_suggestion": "Add an age assurance step (date-of-birth collection, age estimation, or ID verification) before account creation. If the user is under 18, flag the account as a minor account and require parental consent before activation. See Utah SB 142 §13-72-201.",
|
|
912
|
+
"penalty": "Up to $2,500 per violation; private right of action for parents",
|
|
913
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin", "swift"],
|
|
914
|
+
"packs": ["ut-sb142"],
|
|
915
|
+
"fixability": "guided",
|
|
916
|
+
"transform_type": null,
|
|
917
|
+
"scaffold_id": "age-gate-auth",
|
|
918
|
+
"guidance_url": null
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
"id": "ut-sb142-002",
|
|
922
|
+
"name": "Missing Parental Consent for Minor Account",
|
|
923
|
+
"severity": "critical",
|
|
924
|
+
"category": "parental-consent",
|
|
925
|
+
"description": "Utah SB 142 requires verifiable parental consent before a minor (under 18) can use a social media platform or download apps. Code that activates minor accounts without a parental consent verification step violates this requirement.",
|
|
926
|
+
"patterns": [
|
|
927
|
+
{ "pattern": "(?:activateAccount|enableAccount|verifyEmail|confirmRegistration)\\s*\\((?![^)]*(?:parent|guardian|consent|parental))[^)]*\\)", "flags": "gi" },
|
|
928
|
+
{ "pattern": "(?:activate_account|enable_account|verify_email|confirm_registration)\\s*\\((?![^)]*(?:parent|guardian|consent|parental))[^)]*\\)", "flags": "gi" },
|
|
929
|
+
{ "pattern": "(?:isMinor|is_minor|isUnder18|is_under_18)\\s*(?:&&|\\|\\||and|or)\\s*(?!.*(?:parentConsent|parent_consent|guardianApproval|guardian_approval))", "flags": "gi" },
|
|
930
|
+
{ "pattern": "(?:minor|child|teen)(?:Account|_account|Profile|_profile)\\s*[:=]\\s*(?:true|1|'active'|\"active\")", "flags": "gi" }
|
|
931
|
+
],
|
|
932
|
+
"fix_suggestion": "Before activating any minor account, implement a verifiable parental consent flow: send a verification email/SMS to a parent-linked account, require parental ID verification, or integrate with a COPPA-safe consent provider. See Utah SB 142 §13-72-202.",
|
|
933
|
+
"penalty": "Up to $2,500 per violation; private right of action for parents",
|
|
934
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin", "swift"],
|
|
935
|
+
"packs": ["ut-sb142"],
|
|
936
|
+
"fixability": "guided",
|
|
937
|
+
"transform_type": null,
|
|
938
|
+
"scaffold_id": null,
|
|
939
|
+
"guidance_url": null
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
"id": "ut-sb142-003",
|
|
943
|
+
"name": "Default DM Access Open for Minors",
|
|
944
|
+
"severity": "high",
|
|
945
|
+
"category": "messaging-safety",
|
|
946
|
+
"description": "Utah SB 142 requires that direct messaging for minor accounts be restricted to connected accounts only by default. Code that enables open or unrestricted DM access for all users (including minors) violates this requirement.",
|
|
947
|
+
"patterns": [
|
|
948
|
+
{ "pattern": "(?:allowDirectMessages|allow_direct_messages|dmEnabled|dm_enabled|messagesEnabled|messages_enabled)\\s*[:=]\\s*(?:true|1|'all'|\"all\"|'everyone'|\"everyone\")", "flags": "gi" },
|
|
949
|
+
{ "pattern": "(?:messagingPermission|messaging_permission|dmAccess|dm_access|messageAccess|message_access)\\s*[:=]\\s*(?:'public'|\"public\"|'open'|\"open\"|'all'|\"all\")", "flags": "gi" },
|
|
950
|
+
{ "pattern": "(?:canMessage|can_message|canDM|can_dm)\\s*[:=]\\s*(?:true|1)(?!.*(?:friend|follow|connect|mutual))", "flags": "gi" },
|
|
951
|
+
{ "pattern": "default(?:_?[Mm]essaging|_?[Dd][Mm])\\s*[:=]\\s*(?:'open'|\"open\"|'public'|\"public\"|true)", "flags": "gi" }
|
|
952
|
+
],
|
|
953
|
+
"fix_suggestion": "Set default DM permissions for minor accounts to 'connected-only' or 'friends-only'. Only allow messaging between mutually connected accounts. Provide parental controls to further restrict messaging. See Utah SB 142 §13-72-301.",
|
|
954
|
+
"penalty": "Up to $2,500 per violation; private right of action for parents",
|
|
955
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin", "swift"],
|
|
956
|
+
"packs": ["ut-sb142"],
|
|
957
|
+
"fixability": "guided",
|
|
958
|
+
"transform_type": null,
|
|
959
|
+
"scaffold_id": null,
|
|
960
|
+
"guidance_url": null
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
"id": "ut-sb142-004",
|
|
964
|
+
"name": "Missing Parental Supervisory Tools",
|
|
965
|
+
"severity": "high",
|
|
966
|
+
"category": "parental-controls",
|
|
967
|
+
"description": "Utah SB 142 requires platforms to offer parents supervisory tools: time limits, mandatory break scheduling, usage data viewing, connected account lists, and setting-change notifications. Applications that provide user accounts for minors without implementing or referencing a parental dashboard violate this requirement.",
|
|
968
|
+
"patterns": [
|
|
969
|
+
{ "pattern": "(?:userSettings|user_settings|accountSettings|account_settings)\\s*[:=]\\s*\\{(?![^}]*(?:parent|guardian|supervisory|parental|family))[^}]*\\}", "flags": "gi" },
|
|
970
|
+
{ "pattern": "(?:screenTime|screen_time|timeLimit|time_limit|usageLimit|usage_limit)\\s*[:=]\\s*(?:null|undefined|false|0|'disabled'|\"disabled\")", "flags": "gi" },
|
|
971
|
+
{ "pattern": "(?:parentalControls|parental_controls|familySettings|family_settings)\\s*[:=]\\s*(?:false|null|undefined|'disabled'|\"disabled\")", "flags": "gi" }
|
|
972
|
+
],
|
|
973
|
+
"fix_suggestion": "Implement a parental supervisory dashboard that allows parents to: set daily time limits, schedule mandatory breaks, view usage data and connected account lists, and receive notifications when account settings change. See Utah SB 142 §13-72-302.",
|
|
974
|
+
"penalty": "Up to $2,500 per violation; private right of action for parents",
|
|
975
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin", "swift"],
|
|
976
|
+
"packs": ["ut-sb142"],
|
|
977
|
+
"fixability": "flag-only",
|
|
978
|
+
"transform_type": null,
|
|
979
|
+
"scaffold_id": null,
|
|
980
|
+
"guidance_url": null
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
"id": "ut-sb142-005",
|
|
984
|
+
"name": "Minor Profile Visible to Search Engines",
|
|
985
|
+
"severity": "medium",
|
|
986
|
+
"category": "privacy-defaults",
|
|
987
|
+
"description": "Utah SB 142 requires that minor accounts have search engine indexing disabled by default and account visibility restricted to connected users only. Code that allows public profile indexing or unrestricted profile visibility for all users violates this requirement.",
|
|
988
|
+
"patterns": [
|
|
989
|
+
{ "pattern": "(?:indexable|searchable|seo_?visible|allow_?indexing|search_?engine_?visible)\\s*[:=]\\s*(?:true|1|'yes'|\"yes\")", "flags": "gi" },
|
|
990
|
+
{ "pattern": "<meta\\s+name=['\"]robots['\"]\\s+content=['\"](?:index|all|follow)['\"]", "flags": "gi" },
|
|
991
|
+
{ "pattern": "(?:profileVisibility|profile_visibility|accountVisibility|account_visibility)\\s*[:=]\\s*(?:'public'|\"public\"|'everyone'|\"everyone\"|'all'|\"all\")", "flags": "gi" },
|
|
992
|
+
{ "pattern": "(?:isPublicProfile|is_public_profile|publicProfile|public_profile)\\s*[:=]\\s*(?:true|1)", "flags": "gi" },
|
|
993
|
+
{ "pattern": "X-Robots-Tag\\s*[:=]\\s*(?:'index'|\"index\"|'all'|\"all\")", "flags": "gi" }
|
|
994
|
+
],
|
|
995
|
+
"fix_suggestion": "Default minor accounts to non-indexable (noindex, nofollow) and restrict profile visibility to connected/approved users only. Add a robots meta tag or X-Robots-Tag header that blocks search engine crawling for minor profiles. See Utah SB 142 §13-72-301.",
|
|
996
|
+
"penalty": "Up to $2,500 per violation; private right of action for parents",
|
|
997
|
+
"languages": ["typescript", "javascript", "python", "go", "java", "kotlin", "swift"],
|
|
998
|
+
"packs": ["ut-sb142"],
|
|
999
|
+
"fixability": "guided",
|
|
1000
|
+
"transform_type": null,
|
|
1001
|
+
"scaffold_id": null,
|
|
1002
|
+
"guidance_url": null
|
|
1003
|
+
}
|
|
1004
|
+
]
|
|
1005
|
+
}
|