@runhalo/engine 0.3.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/dist/index.js CHANGED
@@ -44,8 +44,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
44
  return (mod && mod.__esModule) ? mod : { "default": mod };
45
45
  };
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.SCAFFOLD_REGISTRY = exports.detectFramework = exports.ScaffoldEngine = exports.ComplianceScoreEngine = exports.transformSetDefault = exports.transformSanitizeInput = exports.transformRemoveDefault = exports.transformUrlUpgrade = exports.FixEngine = exports.REMEDIATION_MAP = exports.HaloEngine = exports.ETHICAL_RULES = exports.COPPA_RULES = exports.treeSitterParser = exports.TreeSitterParser = void 0;
47
+ exports.SCAFFOLD_REGISTRY = exports.detectFramework = exports.ScaffoldEngine = exports.ComplianceScoreEngine = exports.transformSetDefault = exports.transformSanitizeInput = exports.transformRemoveDefault = exports.transformUrlUpgrade = exports.FixEngine = exports.REMEDIATION_MAP = exports.HaloEngine = exports.AU_SBD_RULES = exports.AI_AUDIT_RULES = exports.ETHICAL_RULES = exports.COPPA_RULES = exports.treeSitterParser = exports.TreeSitterParser = void 0;
48
48
  exports.loadRulesFromYAML = loadRulesFromYAML;
49
+ exports.loadRulesFromJSON = loadRulesFromJSON;
50
+ exports.loadRulesFromJSONByPack = loadRulesFromJSONByPack;
51
+ exports.compileRawRules = compileRawRules;
49
52
  exports.parseHaloignore = parseHaloignore;
50
53
  exports.shouldIgnoreFile = shouldIgnoreFile;
51
54
  exports.shouldIgnoreViolation = shouldIgnoreViolation;
@@ -56,10 +59,14 @@ const tree_sitter_1 = __importDefault(require("tree-sitter"));
56
59
  const tree_sitter_typescript_1 = __importDefault(require("tree-sitter-typescript"));
57
60
  const tree_sitter_javascript_1 = __importDefault(require("tree-sitter-javascript"));
58
61
  const yaml = __importStar(require("js-yaml"));
59
- // Extract category from ruleId (e.g. "coppa-auth-001" → "auth", "ETHICAL-001" → "ethical")
62
+ // Extract category from ruleId (e.g. "coppa-auth-001" → "auth", "ETHICAL-001" → "ethical", "AU-SBD-001" → "au-sbd")
60
63
  function extractCategory(ruleId) {
61
64
  if (ruleId.startsWith('ETHICAL'))
62
65
  return 'ethical';
66
+ if (ruleId.startsWith('AI-AUDIT'))
67
+ return 'ai-audit';
68
+ if (ruleId.startsWith('AU-SBD'))
69
+ return 'au-sbd';
63
70
  const match = ruleId.match(/^coppa-(\w+)-\d+$/);
64
71
  return match ? match[1] : 'unknown';
65
72
  }
@@ -71,7 +78,8 @@ function detectLanguage(filePath) {
71
78
  '.py': 'python', '.swift': 'swift', '.java': 'java', '.kt': 'kotlin',
72
79
  '.html': 'html', '.vue': 'vue', '.svelte': 'svelte', '.php': 'php',
73
80
  '.cpp': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.cs': 'csharp',
74
- '.qml': 'qml', '.sql': 'sql',
81
+ '.qml': 'qml', '.sql': 'sql', '.go': 'go', '.rb': 'ruby',
82
+ '.xml': 'xml', '.erb': 'ruby',
75
83
  };
76
84
  return langMap[ext] || 'unknown';
77
85
  }
@@ -106,6 +114,71 @@ function loadRulesFromYAML(yamlPath) {
106
114
  return [];
107
115
  }
108
116
  }
117
+ // Convert a JSON rule definition to the engine's Rule interface
118
+ function jsonRuleToRule(jsonRule) {
119
+ const patterns = jsonRule.patterns.map(p => new RegExp(p.pattern, p.flags));
120
+ // Map fixability string to Fixability type
121
+ const fixabilityMap = {
122
+ 'auto': 'auto',
123
+ 'guided': 'guided',
124
+ 'flag-only': 'flag-only',
125
+ };
126
+ const remediation = {
127
+ fixability: fixabilityMap[jsonRule.fixability] || 'flag-only',
128
+ transformType: jsonRule.transform_type || undefined,
129
+ scaffoldId: jsonRule.scaffold_id || undefined,
130
+ guidanceUrl: jsonRule.guidance_url || undefined,
131
+ };
132
+ return {
133
+ id: jsonRule.id,
134
+ name: jsonRule.name,
135
+ severity: jsonRule.severity,
136
+ description: jsonRule.description,
137
+ patterns,
138
+ fixSuggestion: jsonRule.fix_suggestion,
139
+ penalty: jsonRule.penalty,
140
+ languages: jsonRule.languages,
141
+ remediation,
142
+ };
143
+ }
144
+ // JSON Rule Loader — Load rules from rules.json
145
+ function loadRulesFromJSON(jsonPath) {
146
+ try {
147
+ const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
148
+ const config = JSON.parse(jsonContent);
149
+ if (!config?.rules || !Array.isArray(config.rules)) {
150
+ console.warn(`No rules found in JSON file: ${jsonPath}`);
151
+ return [];
152
+ }
153
+ return config.rules.map(jsonRuleToRule);
154
+ }
155
+ catch (error) {
156
+ console.error(`Error loading JSON rules from ${jsonPath}:`, error);
157
+ return [];
158
+ }
159
+ }
160
+ // JSON Rule Loader — Load rules filtered by pack IDs
161
+ function loadRulesFromJSONByPack(jsonPath, packIds) {
162
+ try {
163
+ const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
164
+ const config = JSON.parse(jsonContent);
165
+ if (!config?.rules || !Array.isArray(config.rules)) {
166
+ console.warn(`No rules found in JSON file: ${jsonPath}`);
167
+ return [];
168
+ }
169
+ const packSet = new Set(packIds);
170
+ const filtered = config.rules.filter(r => r.packs.some(p => packSet.has(p)));
171
+ return filtered.map(jsonRuleToRule);
172
+ }
173
+ catch (error) {
174
+ console.error(`Error loading JSON rules from ${jsonPath}:`, error);
175
+ return [];
176
+ }
177
+ }
178
+ // Compile raw JSON rule objects (from API/cache) to engine Rule objects
179
+ function compileRawRules(rawRules) {
180
+ return rawRules.map(jsonRuleToRule);
181
+ }
109
182
  // Tree-sitter Parser for AST analysis
110
183
  class TreeSitterParser {
111
184
  constructor() {
@@ -263,15 +336,37 @@ exports.COPPA_RULES = [
263
336
  severity: 'critical',
264
337
  description: 'Social login (Google, Facebook, Twitter) without age gating is prohibited for child-directed apps',
265
338
  patterns: [
339
+ // JS/TS — Firebase
266
340
  /signInWithPopup\s*\(\s*\w+\s*,\s*['"](google|facebook|twitter|github)['"]/gi,
267
341
  /signInWithPopup\s*\(\s*['"](google|facebook|twitter|github)['"]/gi,
268
342
  /signInWithPopup\s*\(\s*\w+\s*,\s*\w+\s*\)/gi,
269
343
  /firebase\.auth\(\)\s*\.\s*signInWithPopup/gi,
270
- /passport\.authenticate\s*\(\s*['"](google|facebook|twitter)['"]/gi
344
+ // JS/TS — Passport.js
345
+ /passport\.authenticate\s*\(\s*['"](google|facebook|twitter)['"]/gi,
346
+ // Python — django-allauth social providers config
347
+ /SOCIALACCOUNT_PROVIDERS\s*=\s*\{[^}]*(?:google|facebook|twitter|github)/gi,
348
+ // Python — python-social-auth backends
349
+ /SOCIAL_AUTH_(?:GOOGLE|FACEBOOK|TWITTER|GITHUB)_(?:KEY|SECRET)/gi,
350
+ // Python — flask-dance blueprints
351
+ /make_(?:google|facebook|twitter|github)_blueprint\s*\(/gi,
352
+ // Python — authlib OAuth registration
353
+ /oauth\.register\s*\(\s*['"](?:google|facebook|twitter|github)['"]/gi,
354
+ // Go — goth social auth providers
355
+ /goth\.UseProviders\s*\(/gi,
356
+ // Java — Spring Security OAuth2 login
357
+ /\.oauth2Login\s*\(\s*\)/gi,
358
+ // Java — Spring OAuth2 client registration
359
+ /ClientRegistration\.withRegistrationId\s*\(\s*['"](?:google|facebook|twitter|github)['"]/gi,
360
+ // Kotlin/Java — Firebase Android
361
+ /Firebase\.auth\.signInWithCredential/gi,
362
+ // Kotlin/Java — Google Sign-In Android
363
+ /GoogleSignIn\.getClient\s*\(/gi,
364
+ // Kotlin/Java — Facebook Login Android SDK
365
+ /LoginManager\.getInstance\s*\(\s*\)\s*\.logIn/gi
271
366
  ],
272
367
  fixSuggestion: 'Wrap the auth call in a conditional check for user.age >= 13 or use signInWithParentEmail() for children',
273
368
  penalty: '$51,744 per violation',
274
- languages: ['typescript', 'javascript', 'python', 'swift']
369
+ languages: ['typescript', 'javascript', 'python', 'go', 'java', 'kotlin', 'swift']
275
370
  },
276
371
  {
277
372
  id: 'coppa-data-002',
@@ -310,14 +405,30 @@ exports.COPPA_RULES = [
310
405
  severity: 'high',
311
406
  description: 'High-accuracy geolocation without parental consent is prohibited',
312
407
  patterns: [
408
+ // JS/TS — browser Geolocation API
313
409
  /navigator\.geolocation\.getCurrentPosition/gi,
314
410
  /navigator\.geolocation\.watchPosition/gi,
411
+ // Swift — CoreLocation
315
412
  /CLLocationManager\.startUpdatingLocation\(\)/gi,
316
- /locationServices\.requestLocation/gi
413
+ /locationServices\.requestLocation/gi,
414
+ // Java Android — LocationManager
415
+ /LocationManager\s*\.\s*requestLocationUpdates\s*\(/gi,
416
+ // Java/Kotlin Android — Fused Location Provider (Google Play Services)
417
+ /FusedLocationProviderClient|fusedLocationClient\s*\.\s*(?:requestLocationUpdates|getLastLocation|getCurrentLocation)/gi,
418
+ // Java Android — high accuracy priority
419
+ /LocationRequest\.create\s*\(\s*\)\s*\.\s*setPriority\s*\(\s*LocationRequest\.PRIORITY_HIGH_ACCURACY/gi,
420
+ // Kotlin Android — LocationRequest.Builder
421
+ /LocationRequest\.Builder\s*\(\s*Priority\.PRIORITY_HIGH_ACCURACY/gi,
422
+ // Python — geocoder library
423
+ /geocoder\.(?:ip|google|osm|mapquest)\s*\(/gi,
424
+ // Python — geopy geolocators
425
+ /(?:Nominatim|GoogleV3|Bing)\s*\([^)]*\)\s*\.(?:geocode|reverse)/gi,
426
+ // Android manifest — fine location permission
427
+ /android\.permission\.ACCESS_FINE_LOCATION/gi
317
428
  ],
318
429
  fixSuggestion: 'Downgrade accuracy to kCLLocationAccuracyThreeKilometers or require parental consent',
319
430
  penalty: '$51,744 per violation',
320
- languages: ['typescript', 'javascript', 'swift', 'kotlin']
431
+ languages: ['typescript', 'javascript', 'swift', 'kotlin', 'java', 'python', 'xml']
321
432
  },
322
433
  {
323
434
  id: 'coppa-retention-005',
@@ -325,11 +436,22 @@ exports.COPPA_RULES = [
325
436
  severity: 'medium',
326
437
  description: 'User schemas must have deleted_at, expiration_date, or TTL index for data retention',
327
438
  patterns: [
328
- /new\s+Schema\s*\(\s*\{[^{}]*\}/gi
439
+ // JS/TS — Mongoose schemas
440
+ /new\s+Schema\s*\(\s*\{[^{}]*\}/gi,
441
+ // Python — Django models
442
+ /class\s+(?:User|Child|Student|Profile|Account|Member)\w*\s*\(\s*models\.Model\s*\)/gi,
443
+ // Python — SQLAlchemy declarative models
444
+ /class\s+(?:User|Child|Student|Profile|Account|Member)\w*\s*\(\s*(?:Base|db\.Model)\s*\)/gi,
445
+ // Go — GORM model structs with user-related names
446
+ /type\s+(?:User|Child|Student|Profile|Account|Member)\w*\s+struct\s*\{/gi,
447
+ // Java/Kotlin — JPA @Entity on user-related classes
448
+ /@Entity[\s\S]*?class\s+(?:User|Child|Student|Profile|Account|Member)/gi,
449
+ // Kotlin — data class for user models
450
+ /data\s+class\s+(?:User|Child|Student|Profile|Account|Member)\w*\s*\(/gi
329
451
  ],
330
452
  fixSuggestion: 'Add deleted_at column, expiration_date field, or TTL index to database schema',
331
453
  penalty: 'Regulatory audit failure',
332
- languages: ['typescript', 'javascript', 'python', 'sql', 'swift']
454
+ languages: ['typescript', 'javascript', 'python', 'go', 'java', 'kotlin', 'sql']
333
455
  },
334
456
  // ========== Rules 6-20 (Sprint 2) ==========
335
457
  // Rule 6: Unencrypted PII Transmission
@@ -520,13 +642,24 @@ exports.COPPA_RULES = [
520
642
  severity: 'low',
521
643
  description: 'Cookies or localStorage storing tracking data or PII requires a consent banner',
522
644
  patterns: [
645
+ // JS/TS — browser APIs
523
646
  /document\.cookie\s*=\s*[^;]*(?:user|email|name|token|session|track|id|uid|analytics)/gi,
524
647
  /localStorage\.setItem\s*\(\s*['"][^'"]*(?:user|email|token|session|track|auth|login|id|uid|analytics)[^'"]*['"]/gi,
525
- /sessionStorage\.setItem\s*\(\s*['"][^'"]*(?:user|email|token|session|track|auth|login|id|uid|analytics)[^'"]*['"]/gi
648
+ /sessionStorage\.setItem\s*\(\s*['"][^'"]*(?:user|email|token|session|track|auth|login|id|uid|analytics)[^'"]*['"]/gi,
649
+ // Python — Flask/Django response.set_cookie()
650
+ /\.set_cookie\s*\(\s*['"][^'"]*(?:user|email|token|session|track|auth|login|uid|analytics)[^'"]*['"]/gi,
651
+ // Go — net/http SetCookie
652
+ /http\.SetCookie\s*\(\s*\w+\s*,\s*&http\.Cookie\s*\{/gi,
653
+ // Java/Kotlin — HttpServletResponse.addCookie
654
+ /\.addCookie\s*\(\s*new\s+Cookie\s*\(/gi,
655
+ // Java/Kotlin — Spring ResponseCookie
656
+ /ResponseCookie\.from\s*\(/gi,
657
+ // Generic — any language setting cookies with PII field names
658
+ /(?:set_cookie|SetCookie|addCookie|add_cookie)\s*\([^)]*(?:user|email|token|session|track|auth|uid|analytics)/gi
526
659
  ],
527
660
  fixSuggestion: 'Add a cookie consent banner component before setting tracking or PII cookies',
528
661
  penalty: 'Compliance warning',
529
- languages: ['typescript', 'javascript']
662
+ languages: ['typescript', 'javascript', 'python', 'go', 'java', 'kotlin']
530
663
  },
531
664
  // Rule 17: External Links to Non-Child-Safe Sites
532
665
  // Fixed Sprint 4: Exclude privacy/TOS links, mailto, and common safe targets
@@ -550,15 +683,30 @@ exports.COPPA_RULES = [
550
683
  severity: 'high',
551
684
  description: 'Passing email, name, or phone to analytics.identify() exposes PII to third parties',
552
685
  patterns: [
686
+ // JS/TS — client-side analytics SDKs
553
687
  /analytics\.identify\s*\([^)]*email/gi,
554
688
  /mixpanel\.identify.*email/gi,
555
689
  /segment\.identify.*email/gi,
556
690
  /amplitude\.identify.*email/gi,
557
- /identify\s*\(\s*\{[^}]*(?:email|name|phone)[^}]*\}/gi
691
+ /identify\s*\(\s*\{[^}]*(?:email|name|phone)[^}]*\}/gi,
692
+ // Python — Segment analytics-python
693
+ /analytics\.identify\s*\(\s*\w+\s*,\s*\{[^}]*(?:email|name|phone)/gi,
694
+ // Python — Mixpanel people_set with PII
695
+ /mp\.people_set\s*\([^)]*(?:email|\$email|name|phone)/gi,
696
+ // Go — Segment analytics-go Identify with PII
697
+ /analytics\.Enqueue\s*\(\s*analytics\.Identify\s*\{[^}]*(?:Email|Name|Phone)/gi,
698
+ // Java/Kotlin — Amplitude setUserId with PII
699
+ /Amplitude\.getInstance\s*\(\s*\)\s*\.setUserId\s*\([^)]*email/gi,
700
+ // Java/Kotlin — Mixpanel identify with email
701
+ /MixpanelAPI\.\w*identify\s*\([^)]*email/gi,
702
+ // Java/Kotlin — Firebase Analytics with PII
703
+ /FirebaseAnalytics\.setUserId\s*\([^)]*(?:email|name)/gi,
704
+ // Generic — setUserId with email across languages
705
+ /(?:setUserId|set_user_id)\s*\([^)]*(?:email|\.name|phone)/gi
558
706
  ],
559
707
  fixSuggestion: 'Hash user ID and omit email/name from analytics payload',
560
708
  penalty: '$51,744 per violation',
561
- languages: ['typescript', 'javascript']
709
+ languages: ['typescript', 'javascript', 'python', 'go', 'java', 'kotlin']
562
710
  },
563
711
  // Rule 19: School Official Consent Bypass
564
712
  // Fixed Sprint 4: Tightened patterns to match actual auth/registration flows only
@@ -687,6 +835,222 @@ exports.ETHICAL_RULES = [
687
835
  languages: ['typescript', 'javascript', 'tsx', 'jsx', 'html']
688
836
  }
689
837
  ];
838
+ // AI-Generated Code Audit Rules
839
+ // Catches patterns commonly introduced by AI coding assistants (Copilot, Claude, Cursor, etc.)
840
+ // that create COPPA compliance risks. AI models often reproduce training data patterns
841
+ // including analytics boilerplate, insecure defaults, and placeholder credentials.
842
+ exports.AI_AUDIT_RULES = [
843
+ // AI-AUDIT-001: Placeholder Analytics
844
+ {
845
+ id: 'AI-AUDIT-001',
846
+ name: 'Placeholder Analytics Script',
847
+ severity: 'high',
848
+ 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.',
849
+ patterns: [
850
+ /gtag\s*\(\s*['"]config['"]\s*,\s*['"](?:UA-|G-)X{3,}['"]/gi,
851
+ /fbq\s*\(\s*['"]init['"]\s*,\s*['"](?:0{5,}|1{5,}|X{5,}|YOUR_|PIXEL_ID|123456789)['"]/gi,
852
+ /ga\s*\(\s*['"]create['"]\s*,\s*['"]UA-(?:0{5,}|X{5,}|YOUR_)['"]/gi,
853
+ /['"](?:UA|G)-(?:XXXXXXX|0000000|YOUR_ID|REPLACE_ME)['"]/gi,
854
+ /analytics_id\s*[:=]\s*['"](?:placeholder|test|example|TODO|FIXME)['"]/gi
855
+ ],
856
+ fixSuggestion: 'Remove placeholder analytics IDs. If analytics are needed, use a COPPA-compliant provider with child_directed_treatment: true.',
857
+ penalty: 'AI-generated compliance risk',
858
+ languages: ['typescript', 'javascript', 'html']
859
+ },
860
+ // AI-AUDIT-002: Hardcoded Secrets
861
+ {
862
+ id: 'AI-AUDIT-002',
863
+ name: 'AI-Generated Hardcoded Secrets',
864
+ severity: 'critical',
865
+ description: 'AI coding assistants frequently generate placeholder API keys, tokens, and secrets inline. These may be committed to version control and exposed.',
866
+ patterns: [
867
+ /(?:api_?key|apiKey|API_KEY)\s*[:=]\s*['"](?:sk-|pk-|ak-|key-)[a-zA-Z0-9]{10,}['"]/gi,
868
+ /(?:secret|SECRET|token|TOKEN)\s*[:=]\s*['"](?!process\.env)[a-zA-Z0-9_-]{20,}['"]/gi,
869
+ /SUPABASE_(?:ANON_KEY|SERVICE_ROLE_KEY)\s*[:=]\s*['"]ey[a-zA-Z0-9_.+-]{30,}['"]/gi,
870
+ /FIREBASE_(?:API_KEY|CONFIG)\s*[:=]\s*['"]AI[a-zA-Z0-9_-]{30,}['"]/gi,
871
+ /(?:password|passwd|pwd)\s*[:=]\s*['"](?!process\.env)[^'"]{8,}['"]\s*(?:,|;|\})/gi
872
+ ],
873
+ fixSuggestion: 'Move all secrets to environment variables. Use process.env.API_KEY or a secrets manager. Never hardcode credentials.',
874
+ penalty: 'Security exposure — credentials in source code',
875
+ languages: ['typescript', 'javascript', 'python', 'java']
876
+ },
877
+ // AI-AUDIT-003: Hallucinated URLs
878
+ {
879
+ id: 'AI-AUDIT-003',
880
+ name: 'Hallucinated/Placeholder API URLs',
881
+ severity: 'medium',
882
+ description: 'AI models often generate fake API endpoints (api.example.com, jsonplaceholder, reqres.in) that may be replaced with real endpoints without proper review.',
883
+ patterns: [
884
+ /fetch\s*\(\s*['"]https?:\/\/(?:api\.example\.com|jsonplaceholder\.typicode\.com|reqres\.in|httpbin\.org|mockapi\.io|dummyjson\.com)[^'"]*['"]/gi,
885
+ /axios\.\w+\s*\(\s*['"]https?:\/\/(?:api\.example\.com|jsonplaceholder\.typicode\.com|reqres\.in|httpbin\.org|dummyjson\.com)[^'"]*['"]/gi,
886
+ /(?:BASE_URL|API_URL|ENDPOINT)\s*[:=]\s*['"]https?:\/\/(?:api\.example\.com|your-api|my-api|TODO|REPLACE)[^'"]*['"]/gi
887
+ ],
888
+ fixSuggestion: 'Replace placeholder URLs with actual endpoints from environment variables. Review all API calls for COPPA data handling compliance.',
889
+ penalty: 'AI-generated placeholder risk',
890
+ languages: ['typescript', 'javascript', 'python']
891
+ },
892
+ // AI-AUDIT-004: Copy-Paste Tracking Boilerplate
893
+ {
894
+ id: 'AI-AUDIT-004',
895
+ name: 'Copy-Paste Tracking Boilerplate',
896
+ severity: 'high',
897
+ description: 'AI assistants reproduce common analytics setup patterns from training data. These often include user identification, event tracking, and session recording without consent flows.',
898
+ patterns: [
899
+ /hotjar\.init\s*\(/gi,
900
+ /Sentry\.init\s*\(\s*\{[^}]*dsn/gi,
901
+ /LogRocket\.init\s*\(/gi,
902
+ /FullStory\.init\s*\(/gi,
903
+ /heap\.load\s*\(/gi,
904
+ /posthog\.init\s*\(/gi,
905
+ /amplitude\.init\s*\(/gi,
906
+ /mixpanel\.init\s*\(/gi,
907
+ /window\.clarity\s*\(/gi
908
+ ],
909
+ fixSuggestion: 'Remove session recording and analytics initialization unless COPPA consent is obtained. These tools capture keystrokes, mouse movements, and user behavior — all PII for children.',
910
+ penalty: 'Third-party data collection without consent',
911
+ languages: ['typescript', 'javascript', 'html']
912
+ },
913
+ // AI-AUDIT-005: Insecure Defaults
914
+ {
915
+ id: 'AI-AUDIT-005',
916
+ name: 'AI-Generated Insecure Defaults',
917
+ severity: 'medium',
918
+ description: 'AI models commonly generate code with insecure default configurations: CORS *, disabled SSL verification, permissive CSP, or open CORS origins.',
919
+ patterns: [
920
+ /cors\s*\(\s*\{\s*origin\s*:\s*(?:['"]?\*['"]?|true)/gi,
921
+ /Access-Control-Allow-Origin['"]\s*,\s*['"]\*/gi,
922
+ /rejectUnauthorized\s*:\s*false/gi,
923
+ /NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]?0['"]?/gi,
924
+ /content-security-policy['"]\s*,\s*['"]default-src\s+\*/gi,
925
+ /sameSite\s*:\s*['"]none['"]\s*,?\s*secure\s*:\s*false/gi
926
+ ],
927
+ fixSuggestion: 'Replace wildcard CORS with specific allowed origins. Enable SSL verification. Use restrictive CSP. Set secure cookies.',
928
+ penalty: 'Security misconfiguration',
929
+ languages: ['typescript', 'javascript', 'python']
930
+ },
931
+ // AI-AUDIT-006: TODO/FIXME Compliance Gaps
932
+ {
933
+ id: 'AI-AUDIT-006',
934
+ name: 'Unresolved Compliance TODOs',
935
+ severity: 'low',
936
+ description: 'AI-generated code often includes TODO/FIXME comments for compliance-related features (consent, age verification, privacy policy) that may ship unimplemented.',
937
+ patterns: [
938
+ /\/\/\s*(?:TODO|FIXME|HACK|XXX).*(?:consent|age\s*verif|privacy\s*policy|parental|coppa|gdpr|data\s*retention)/gi,
939
+ /\/\/\s*(?:TODO|FIXME).*(?:implement|add|need|require).*(?:auth|login|permission|access\s*control)/gi,
940
+ /#\s*(?:TODO|FIXME).*(?:consent|age\s*verif|privacy|parental|coppa)/gi
941
+ ],
942
+ fixSuggestion: 'Resolve all compliance-related TODOs before shipping. Each unresolved TODO is a potential COPPA violation in production.',
943
+ penalty: 'Unimplemented compliance requirement',
944
+ languages: ['typescript', 'javascript', 'python', 'java', 'swift', 'kotlin']
945
+ }
946
+ ];
947
+ // Australia Safety by Design (SbD) Rules
948
+ // Based on the eSafety Commissioner's Safety by Design framework (Online Safety Act 2021).
949
+ // Detects code patterns that violate SbD principles: service provider responsibility,
950
+ // user empowerment, and transparency/accountability — especially for platforms serving minors.
951
+ exports.AU_SBD_RULES = [
952
+ // AU-SBD-001: Default Public Profiles
953
+ {
954
+ id: 'AU-SBD-001',
955
+ name: 'Default Public Profile Visibility',
956
+ severity: 'high',
957
+ 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.',
958
+ patterns: [
959
+ /(?:visibility|profile_?visibility|is_?public|isPublic)\s*[:=]\s*(?:['"]public['"]|true)/gi,
960
+ /default(?:_?visibility|_?privacy|_?profile)\s*[:=]\s*['"](?:public|open|visible|everyone)['"]/gi,
961
+ /(?:privacy|profilePrivacy)\s*[:=]\s*\{[^}]*default\s*:\s*['"](?:public|open|everyone)['"]/gi,
962
+ /(?:showProfile|profileVisible|publicByDefault|show_profile)\s*[:=]\s*true/gi,
963
+ /(?:searchable|discoverable|findable)\s*[:=]\s*true\b/gi,
964
+ ],
965
+ fixSuggestion: '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.',
966
+ penalty: 'Default public exposure of minor profiles',
967
+ languages: ['typescript', 'javascript', 'python', 'java', 'swift', 'kotlin', 'php', 'csharp']
968
+ },
969
+ // AU-SBD-002: Missing Report/Block Mechanism
970
+ {
971
+ id: 'AU-SBD-002',
972
+ name: 'Social Features Without Report/Block',
973
+ severity: 'medium',
974
+ 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.',
975
+ patterns: [
976
+ /(?:addComment|postComment|submitComment|createComment|commentCreate)\s*(?:=|:|\()/gi,
977
+ /(?:sendMessage|createMessage|postMessage|submitMessage|messageCreate)\s*(?:=|:|\()/gi,
978
+ /(?:createPost|submitPost|publishPost|addPost|postCreate)\s*(?:=|:|\()/gi,
979
+ /(?:addReview|submitReview|createReview|postReview|reviewCreate)\s*(?:=|:|\()/gi,
980
+ /(?:shareContent|createShare|submitShare)\s*(?:=|:|\()/gi,
981
+ ],
982
+ fixSuggestion: '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.',
983
+ penalty: 'Social features without safety controls',
984
+ languages: ['typescript', 'javascript', 'python', 'java', 'swift', 'kotlin', 'php']
985
+ },
986
+ // AU-SBD-003: Unrestricted Direct Messaging
987
+ {
988
+ id: 'AU-SBD-003',
989
+ name: 'Unrestricted Direct Messaging for Minors',
990
+ severity: 'critical',
991
+ 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.',
992
+ patterns: [
993
+ /(?:directMessage|sendDM|privateMess|createDM|dmChannel|startChat|privateChat|initiateChat)\s*(?:=|:|\()/gi,
994
+ /(?:WebSocket|io\.connect|socket\.emit)\s*\([^)]*(?:chat|message|dm|private)/gi,
995
+ /(?:allowDMs?|enableDMs?|allow_?direct_?message|enable_?private_?message)\s*[:=]\s*true/gi,
996
+ /(?:contactStranger|messageAnyone|openChat|unrestricted_?message)\s*[:=]\s*true/gi,
997
+ ],
998
+ fixSuggestion: '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.',
999
+ penalty: 'Unrestricted private communication channel',
1000
+ languages: ['typescript', 'javascript', 'python', 'java', 'swift', 'kotlin']
1001
+ },
1002
+ // AU-SBD-004: Algorithmic Feeds Without Safety Guardrails
1003
+ {
1004
+ id: 'AU-SBD-004',
1005
+ name: 'Recommendation Algorithm Without Safety Guardrails',
1006
+ severity: 'high',
1007
+ 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.',
1008
+ patterns: [
1009
+ /(?:recommendContent|getRecommendations|suggestContent|personalizedFeed|forYouFeed|contentFeed)\s*(?:=|:|\()/gi,
1010
+ /(?:algorithm|algo)(?:_?feed|_?rank|_?recommend|_?suggest)\s*(?:=|:|\()/gi,
1011
+ /(?:trending|viral|popular)(?:_?content|_?posts|_?feed|_?items)\s*(?:=|:|\()/gi,
1012
+ /(?:engagement_?score|clickBait|engagement_?rank|watch_?next|autoplay_?next)\s*[:=]/gi,
1013
+ /(?:rabbit_?hole|endless_?feed|infinite_?recommend|auto_?suggest)\s*[:=]\s*true/gi,
1014
+ ],
1015
+ fixSuggestion: '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.',
1016
+ penalty: 'Unfiltered algorithmic content delivery',
1017
+ languages: ['typescript', 'javascript', 'python', 'java', 'swift', 'kotlin']
1018
+ },
1019
+ // AU-SBD-005: Missing Digital Wellbeing / Screen Time Controls
1020
+ {
1021
+ id: 'AU-SBD-005',
1022
+ name: 'Engagement Features Without Time Awareness',
1023
+ severity: 'medium',
1024
+ 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.',
1025
+ patterns: [
1026
+ /(?:autoplay|auto_?play)\s*[:=]\s*true/gi,
1027
+ /(?:autoPlay|autoPlayNext|playNext|nextEpisode)\s*[:=]\s*true/gi,
1028
+ /(?:continuous_?play|binge_?mode|marathon_?mode|watch_?party)\s*[:=]\s*true/gi,
1029
+ /(?:push_?notification|sendNotification|scheduleNotif|notif_?trigger).*(?:re_?engage|comeback|miss_?you|inactive)/gi,
1030
+ /(?:daily_?reward|login_?bonus|daily_?streak|come_?back_?reward)\s*(?:=|:|\()/gi,
1031
+ ],
1032
+ fixSuggestion: '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.',
1033
+ penalty: 'Engagement-maximizing features without wellbeing controls',
1034
+ languages: ['typescript', 'javascript', 'python', 'java', 'swift', 'kotlin']
1035
+ },
1036
+ // AU-SBD-006: Location Sharing Without Explicit Opt-In
1037
+ {
1038
+ id: 'AU-SBD-006',
1039
+ name: 'Location Data Without Explicit Consent',
1040
+ severity: 'critical',
1041
+ 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.',
1042
+ patterns: [
1043
+ /(?:shareLocation|share_?location|locationSharing|broadcastLocation)\s*[:=]\s*true/gi,
1044
+ /(?:location_?visible|show_?location|display_?location|location_?public)\s*[:=]\s*true/gi,
1045
+ /(?:trackLocation|location_?tracking|geo_?tracking|locationTracker)\s*[:=]\s*true/gi,
1046
+ /navigator\.geolocation\.(?:watchPosition|getCurrentPosition)\s*\(/gi,
1047
+ /(?:CLLocationManager|LocationManager|FusedLocationProvider).*(?:startUpdating|requestLocation|requestLocationUpdates)/gi,
1048
+ ],
1049
+ fixSuggestion: '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.',
1050
+ penalty: 'Default location data exposure',
1051
+ languages: ['typescript', 'javascript', 'python', 'java', 'swift', 'kotlin']
1052
+ }
1053
+ ];
690
1054
  /**
691
1055
  * Parse a .haloignore file content
692
1056
  *
@@ -776,26 +1140,75 @@ class HaloEngine {
776
1140
  constructor(config = {}) {
777
1141
  this.config = config;
778
1142
  this.treeSitter = new TreeSitterParser();
779
- // Load rules - first try YAML if path provided, otherwise use hardcoded rules
780
- let loadedRules = [];
781
- if (config.rulesPath) {
782
- // Load from YAML file
783
- loadedRules = loadRulesFromYAML(config.rulesPath);
1143
+ // Rule loading priority chain:
1144
+ // 1. config.loadedRules pre-compiled rules from CLI API fetch
1145
+ // 2. config.rulesPath — YAML file (legacy)
1146
+ // 3. config.packs — load from bundled rules.json filtered by pack
1147
+ // 4. Default — hardcoded arrays with legacy boolean flags
1148
+ if (config.loadedRules && config.loadedRules.length > 0) {
1149
+ // Priority 1: Pre-loaded rules (from CLI API fetch or external source)
1150
+ this.rules = config.loadedRules;
1151
+ }
1152
+ else if (config.rulesPath) {
1153
+ // Priority 2: YAML file (legacy)
1154
+ const yamlRules = loadRulesFromYAML(config.rulesPath);
1155
+ this.rules = yamlRules.length > 0 ? yamlRules : exports.COPPA_RULES;
784
1156
  }
785
- // If no YAML rules loaded, fall back to hardcoded COPPA_RULES
786
- this.rules = loadedRules.length > 0
787
- ? loadedRules
788
- : (config.rules
1157
+ else if (config.packs) {
1158
+ // Priority 3: Load from bundled rules.json by pack IDs
1159
+ const jsonRules = this.loadBundledRulesByPack(config.packs);
1160
+ this.rules = jsonRules.length > 0 ? jsonRules : exports.COPPA_RULES;
1161
+ }
1162
+ else {
1163
+ // Priority 4: Hardcoded arrays with legacy boolean flags
1164
+ this.rules = config.rules
789
1165
  ? exports.COPPA_RULES.filter(r => config.rules.includes(r.id))
790
- : exports.COPPA_RULES);
791
- // Add Ethical Design Rules if enabled (Sprint 5 Preview)
792
- if (config.ethical) {
793
- this.rules = [...this.rules, ...exports.ETHICAL_RULES];
1166
+ : exports.COPPA_RULES;
1167
+ // Append optional packs via legacy boolean flags
1168
+ if (config.ethical) {
1169
+ this.rules = [...this.rules, ...exports.ETHICAL_RULES];
1170
+ }
1171
+ if (config.aiAudit) {
1172
+ this.rules = [...this.rules, ...exports.AI_AUDIT_RULES];
1173
+ }
1174
+ if (config.sectorAuSbd) {
1175
+ this.rules = [...this.rules, ...exports.AU_SBD_RULES];
1176
+ }
794
1177
  }
795
1178
  if (config.severityFilter) {
796
1179
  this.rules = this.rules.filter(r => config.severityFilter.includes(r.severity));
797
1180
  }
798
1181
  }
1182
+ /**
1183
+ * Load rules from the bundled rules.json file, filtered by pack IDs.
1184
+ * Falls back to empty array if rules.json not found.
1185
+ */
1186
+ loadBundledRulesByPack(packIds) {
1187
+ try {
1188
+ const jsonPath = path.resolve(__dirname, '..', 'rules', 'rules.json');
1189
+ return loadRulesFromJSONByPack(jsonPath, packIds);
1190
+ }
1191
+ catch (error) {
1192
+ return [];
1193
+ }
1194
+ }
1195
+ /**
1196
+ * Resolve legacy boolean flags to pack IDs.
1197
+ * Maps ethical/aiAudit/sectorAuSbd booleans to their pack ID equivalents.
1198
+ * Always includes 'coppa' as the base pack.
1199
+ */
1200
+ static resolvePacks(config) {
1201
+ if (config.packs)
1202
+ return config.packs;
1203
+ const packs = ['coppa'];
1204
+ if (config.ethical)
1205
+ packs.push('ethical');
1206
+ if (config.aiAudit)
1207
+ packs.push('ai-audit');
1208
+ if (config.sectorAuSbd)
1209
+ packs.push('au-sbd');
1210
+ return packs;
1211
+ }
799
1212
  /**
800
1213
  * Get the tree-sitter parser for advanced AST analysis
801
1214
  */
@@ -916,7 +1329,8 @@ class HaloEngine {
916
1329
  const trimmedLine = lineContent.trim();
917
1330
  if (trimmedLine.startsWith('//') || trimmedLine.startsWith('/*') || trimmedLine.startsWith('*') || trimmedLine.startsWith('<!--') || trimmedLine.startsWith('#')) {
918
1331
  // Exception: don't skip halo-ignore comments (those are suppression directives)
919
- if (!trimmedLine.includes('halo-ignore')) {
1332
+ // Exception: AI-AUDIT-006 intentionally scans comments for compliance TODOs
1333
+ if (!trimmedLine.includes('halo-ignore') && rule.id !== 'AI-AUDIT-006') {
920
1334
  continue;
921
1335
  }
922
1336
  }
@@ -989,7 +1403,7 @@ class HaloEngine {
989
1403
  * Get rule by ID
990
1404
  */
991
1405
  getRule(ruleId) {
992
- return exports.COPPA_RULES.find(r => r.id === ruleId);
1406
+ return this.rules.find(r => r.id === ruleId) || exports.COPPA_RULES.find(r => r.id === ruleId);
993
1407
  }
994
1408
  /**
995
1409
  * Explain a rule (for MCP)