@runhalo/engine 0.4.0 → 0.5.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/ast-engine.d.ts +60 -0
- package/dist/ast-engine.js +653 -0
- package/dist/ast-engine.js.map +1 -0
- package/dist/context-analyzer.d.ts +209 -0
- package/dist/context-analyzer.js +401 -0
- package/dist/context-analyzer.js.map +1 -0
- package/dist/data-flow-tracer.d.ts +106 -0
- package/dist/data-flow-tracer.js +506 -0
- package/dist/data-flow-tracer.js.map +1 -0
- package/dist/frameworks/django.d.ts +11 -0
- package/dist/frameworks/django.js +57 -0
- package/dist/frameworks/django.js.map +1 -0
- package/dist/frameworks/index.d.ts +59 -0
- package/dist/frameworks/index.js +93 -0
- package/dist/frameworks/index.js.map +1 -0
- package/dist/frameworks/nextjs.d.ts +11 -0
- package/dist/frameworks/nextjs.js +59 -0
- package/dist/frameworks/nextjs.js.map +1 -0
- package/dist/frameworks/rails.d.ts +11 -0
- package/dist/frameworks/rails.js +58 -0
- package/dist/frameworks/rails.js.map +1 -0
- package/dist/frameworks/types.d.ts +29 -0
- package/dist/frameworks/types.js +11 -0
- package/dist/frameworks/types.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +105 -7
- package/dist/index.js.map +1 -1
- package/dist/scope-analyzer.d.ts +91 -0
- package/dist/scope-analyzer.js +300 -0
- package/dist/scope-analyzer.js.map +1 -0
- package/package.json +6 -2
- package/rules/rules.json +1699 -72
- package/rules/validation-report.json +58 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Django Framework Profile
|
|
4
|
+
*
|
|
5
|
+
* Django provides built-in protections that overlap with several COPPA rules:
|
|
6
|
+
* - Template engine auto-escapes all variables by default
|
|
7
|
+
* - SecurityMiddleware enforces HTTPS when configured
|
|
8
|
+
* - Built-in password validators enforce complexity requirements
|
|
9
|
+
* - django-lifecycle and django-reversion can handle data retention externally
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.djangoProfile = void 0;
|
|
13
|
+
exports.djangoProfile = {
|
|
14
|
+
id: 'django',
|
|
15
|
+
name: 'Django',
|
|
16
|
+
ecosystem: 'python',
|
|
17
|
+
handled_rules: [
|
|
18
|
+
{
|
|
19
|
+
rule_id: 'coppa-sec-015',
|
|
20
|
+
action: 'suppress',
|
|
21
|
+
reason: 'Django templates auto-escape all variables by default.',
|
|
22
|
+
documentation_url: 'https://docs.djangoproject.com/en/stable/ref/templates/language/#automatic-html-escaping',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
rule_id: 'coppa-retention-005',
|
|
26
|
+
action: 'downgrade',
|
|
27
|
+
downgrade_to: 'low',
|
|
28
|
+
reason: 'Django models with django-lifecycle or django-reversion may handle retention externally.',
|
|
29
|
+
documentation_url: 'https://docs.djangoproject.com/en/stable/topics/db/models/',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
rule_id: 'coppa-sec-006',
|
|
33
|
+
action: 'suppress',
|
|
34
|
+
reason: 'Django SecurityMiddleware enforces HTTPS when configured.',
|
|
35
|
+
documentation_url: 'https://docs.djangoproject.com/en/stable/ref/middleware/#module-django.middleware.security',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
rule_id: 'coppa-sec-010',
|
|
39
|
+
action: 'suppress',
|
|
40
|
+
reason: 'Django built-in password validators enforce complexity.',
|
|
41
|
+
documentation_url: 'https://docs.djangoproject.com/en/stable/topics/auth/passwords/#module-django.contrib.auth.password_validation',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
safe_patterns: [
|
|
45
|
+
{
|
|
46
|
+
description: 'Django CSRF middleware for cross-site request forgery protection',
|
|
47
|
+
patterns: [/CsrfViewMiddleware/, /csrf_token/],
|
|
48
|
+
applies_to_rules: ['coppa-sec-015'],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
description: 'Django admin interface with built-in auth and access controls',
|
|
52
|
+
patterns: [/admin\.site\.register/, /class.*Admin\(/, /admin\.py/],
|
|
53
|
+
applies_to_rules: ['coppa-auth-001', 'coppa-ui-008', 'coppa-default-020'],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=django.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"django.js","sourceRoot":"","sources":["../../src/frameworks/django.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAIU,QAAA,aAAa,GAAqB;IAC7C,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,QAAQ;IACnB,aAAa,EAAE;QACb;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,wDAAwD;YAChE,iBAAiB,EAAE,0FAA0F;SAC9G;QACD;YACE,OAAO,EAAE,qBAAqB;YAC9B,MAAM,EAAE,WAAW;YACnB,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,0FAA0F;YAClG,iBAAiB,EAAE,4DAA4D;SAChF;QACD;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,2DAA2D;YACnE,iBAAiB,EAAE,4FAA4F;SAChH;QACD;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,yDAAyD;YACjE,iBAAiB,EAAE,gHAAgH;SACpI;KACF;IACD,aAAa,EAAE;QACb;YACE,WAAW,EAAE,kEAAkE;YAC/E,QAAQ,EAAE,CAAC,oBAAoB,EAAE,YAAY,CAAC;YAC9C,gBAAgB,EAAE,CAAC,eAAe,CAAC;SACpC;QACD;YACE,WAAW,EAAE,+DAA+D;YAC5E,QAAQ,EAAE,CAAC,uBAAuB,EAAE,gBAAgB,EAAE,WAAW,CAAC;YAClE,gBAAgB,EAAE,CAAC,gBAAgB,EAAE,cAAc,EAAE,mBAAmB,CAAC;SAC1E;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Allowlisting System
|
|
3
|
+
*
|
|
4
|
+
* Entry point for Halo's framework-aware rule management. When a developer
|
|
5
|
+
* declares their framework in .halorc.json (e.g. { "framework": "nextjs" }),
|
|
6
|
+
* Halo loads the corresponding profile and automatically suppresses or
|
|
7
|
+
* downgrades rules that the framework already handles natively.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { applyFrameworkOverrides } from './frameworks';
|
|
11
|
+
* const result = applyFrameworkOverrides(violations, 'nextjs');
|
|
12
|
+
* // result.violations — filtered/downgraded violations
|
|
13
|
+
* // result.suppressedCount — number of violations removed
|
|
14
|
+
* // result.downgradedCount — number of violations with reduced severity
|
|
15
|
+
*/
|
|
16
|
+
export type { FrameworkAction, FrameworkRuleOverride, FrameworkSafePattern, FrameworkProfile } from './types';
|
|
17
|
+
import { FrameworkProfile } from './types';
|
|
18
|
+
/**
|
|
19
|
+
* Minimal violation shape used by the framework system to avoid circular
|
|
20
|
+
* imports with the engine's Violation type. Any object with at least ruleId
|
|
21
|
+
* and severity will work.
|
|
22
|
+
*/
|
|
23
|
+
interface ViolationLike {
|
|
24
|
+
ruleId: string;
|
|
25
|
+
severity: string;
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Look up a framework profile by its id.
|
|
30
|
+
*
|
|
31
|
+
* @param id - Framework identifier (e.g. "nextjs", "django", "rails")
|
|
32
|
+
* @returns The matching FrameworkProfile, or null if not found
|
|
33
|
+
*/
|
|
34
|
+
export declare function getFrameworkProfile(id: string): FrameworkProfile | null;
|
|
35
|
+
/**
|
|
36
|
+
* List all registered framework ids.
|
|
37
|
+
*
|
|
38
|
+
* @returns Sorted array of framework id strings
|
|
39
|
+
*/
|
|
40
|
+
export declare function listFrameworks(): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Apply a framework's rule overrides to a set of violations.
|
|
43
|
+
*
|
|
44
|
+
* For each violation whose ruleId appears in the framework's handled_rules:
|
|
45
|
+
* - "suppress" removes the violation from the output entirely
|
|
46
|
+
* - "downgrade" reduces the violation's severity to the specified level
|
|
47
|
+
*
|
|
48
|
+
* Violations whose ruleId is NOT in the framework's profile are passed
|
|
49
|
+
* through unchanged.
|
|
50
|
+
*
|
|
51
|
+
* @param violations - Array of violation objects to filter
|
|
52
|
+
* @param frameworkId - Framework identifier to look up
|
|
53
|
+
* @returns Object with the filtered violations array and counts
|
|
54
|
+
*/
|
|
55
|
+
export declare function applyFrameworkOverrides<T extends ViolationLike>(violations: T[], frameworkId: string): {
|
|
56
|
+
violations: T[];
|
|
57
|
+
suppressedCount: number;
|
|
58
|
+
downgradedCount: number;
|
|
59
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Framework Allowlisting System
|
|
4
|
+
*
|
|
5
|
+
* Entry point for Halo's framework-aware rule management. When a developer
|
|
6
|
+
* declares their framework in .halorc.json (e.g. { "framework": "nextjs" }),
|
|
7
|
+
* Halo loads the corresponding profile and automatically suppresses or
|
|
8
|
+
* downgrades rules that the framework already handles natively.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { applyFrameworkOverrides } from './frameworks';
|
|
12
|
+
* const result = applyFrameworkOverrides(violations, 'nextjs');
|
|
13
|
+
* // result.violations — filtered/downgraded violations
|
|
14
|
+
* // result.suppressedCount — number of violations removed
|
|
15
|
+
* // result.downgradedCount — number of violations with reduced severity
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.getFrameworkProfile = getFrameworkProfile;
|
|
19
|
+
exports.listFrameworks = listFrameworks;
|
|
20
|
+
exports.applyFrameworkOverrides = applyFrameworkOverrides;
|
|
21
|
+
const nextjs_1 = require("./nextjs");
|
|
22
|
+
const django_1 = require("./django");
|
|
23
|
+
const rails_1 = require("./rails");
|
|
24
|
+
/** Registry of all built-in framework profiles keyed by id. */
|
|
25
|
+
const FRAMEWORK_REGISTRY = {
|
|
26
|
+
nextjs: nextjs_1.nextjsProfile,
|
|
27
|
+
django: django_1.djangoProfile,
|
|
28
|
+
rails: rails_1.railsProfile,
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Look up a framework profile by its id.
|
|
32
|
+
*
|
|
33
|
+
* @param id - Framework identifier (e.g. "nextjs", "django", "rails")
|
|
34
|
+
* @returns The matching FrameworkProfile, or null if not found
|
|
35
|
+
*/
|
|
36
|
+
function getFrameworkProfile(id) {
|
|
37
|
+
return FRAMEWORK_REGISTRY[id] ?? null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* List all registered framework ids.
|
|
41
|
+
*
|
|
42
|
+
* @returns Sorted array of framework id strings
|
|
43
|
+
*/
|
|
44
|
+
function listFrameworks() {
|
|
45
|
+
return Object.keys(FRAMEWORK_REGISTRY).sort();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Apply a framework's rule overrides to a set of violations.
|
|
49
|
+
*
|
|
50
|
+
* For each violation whose ruleId appears in the framework's handled_rules:
|
|
51
|
+
* - "suppress" removes the violation from the output entirely
|
|
52
|
+
* - "downgrade" reduces the violation's severity to the specified level
|
|
53
|
+
*
|
|
54
|
+
* Violations whose ruleId is NOT in the framework's profile are passed
|
|
55
|
+
* through unchanged.
|
|
56
|
+
*
|
|
57
|
+
* @param violations - Array of violation objects to filter
|
|
58
|
+
* @param frameworkId - Framework identifier to look up
|
|
59
|
+
* @returns Object with the filtered violations array and counts
|
|
60
|
+
*/
|
|
61
|
+
function applyFrameworkOverrides(violations, frameworkId) {
|
|
62
|
+
const profile = getFrameworkProfile(frameworkId);
|
|
63
|
+
if (!profile) {
|
|
64
|
+
return { violations: [...violations], suppressedCount: 0, downgradedCount: 0 };
|
|
65
|
+
}
|
|
66
|
+
// Build a lookup map from rule_id to override for O(1) access
|
|
67
|
+
const overrideMap = new Map(profile.handled_rules.map((override) => [override.rule_id, override]));
|
|
68
|
+
let suppressedCount = 0;
|
|
69
|
+
let downgradedCount = 0;
|
|
70
|
+
const filtered = [];
|
|
71
|
+
for (const violation of violations) {
|
|
72
|
+
const override = overrideMap.get(violation.ruleId);
|
|
73
|
+
if (!override) {
|
|
74
|
+
filtered.push(violation);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (override.action === 'suppress') {
|
|
78
|
+
suppressedCount++;
|
|
79
|
+
// Violation is removed from output
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (override.action === 'downgrade' && override.downgrade_to) {
|
|
83
|
+
downgradedCount++;
|
|
84
|
+
// Create a shallow copy with the new severity to avoid mutating the original
|
|
85
|
+
filtered.push({ ...violation, severity: override.downgrade_to });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Fallback: pass through if action is unrecognized
|
|
89
|
+
filtered.push(violation);
|
|
90
|
+
}
|
|
91
|
+
return { violations: filtered, suppressedCount, downgradedCount };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/frameworks/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAiCH,kDAEC;AAOD,wCAEC;AAgBD,0DA6CC;AApGD,qCAAyC;AACzC,qCAAyC;AACzC,mCAAuC;AAavC,+DAA+D;AAC/D,MAAM,kBAAkB,GAAqC;IAC3D,MAAM,EAAE,sBAAa;IACrB,MAAM,EAAE,sBAAa;IACrB,KAAK,EAAE,oBAAY;CACpB,CAAC;AAEF;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,EAAU;IAC5C,OAAO,kBAAkB,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,SAAgB,cAAc;IAC5B,OAAO,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAgB,uBAAuB,CACrC,UAAe,EACf,WAAmB;IAEnB,MAAM,OAAO,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAEjD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,UAAU,EAAE,CAAC,GAAG,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IACjF,CAAC;IAED,8DAA8D;IAC9D,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CACtE,CAAC;IAEF,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,MAAM,QAAQ,GAAQ,EAAE,CAAC;IAEzB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAEnD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACnC,eAAe,EAAE,CAAC;YAClB,mCAAmC;YACnC,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,WAAW,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;YAC7D,eAAe,EAAE,CAAC;YAClB,6EAA6E;YAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;YACjE,SAAS;QACX,CAAC;QAED,mDAAmD;QACnD,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js Framework Profile
|
|
3
|
+
*
|
|
4
|
+
* Next.js provides built-in protections that overlap with several COPPA rules:
|
|
5
|
+
* - React JSX auto-escaping mitigates most XSS vectors
|
|
6
|
+
* - HTTPS enforcement in production covers unencrypted PII transmission
|
|
7
|
+
* - Next.js Link component handles external navigation safely
|
|
8
|
+
* - Middleware provides centralized cookie management
|
|
9
|
+
*/
|
|
10
|
+
import { FrameworkProfile } from './types';
|
|
11
|
+
export declare const nextjsProfile: FrameworkProfile;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Next.js Framework Profile
|
|
4
|
+
*
|
|
5
|
+
* Next.js provides built-in protections that overlap with several COPPA rules:
|
|
6
|
+
* - React JSX auto-escaping mitigates most XSS vectors
|
|
7
|
+
* - HTTPS enforcement in production covers unencrypted PII transmission
|
|
8
|
+
* - Next.js Link component handles external navigation safely
|
|
9
|
+
* - Middleware provides centralized cookie management
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.nextjsProfile = void 0;
|
|
13
|
+
exports.nextjsProfile = {
|
|
14
|
+
id: 'nextjs',
|
|
15
|
+
name: 'Next.js',
|
|
16
|
+
ecosystem: 'javascript',
|
|
17
|
+
handled_rules: [
|
|
18
|
+
{
|
|
19
|
+
rule_id: 'coppa-sec-015',
|
|
20
|
+
action: 'downgrade',
|
|
21
|
+
downgrade_to: 'low',
|
|
22
|
+
reason: 'React auto-escapes JSX output by default. dangerouslySetInnerHTML is the only XSS vector and is flagged separately.',
|
|
23
|
+
documentation_url: 'https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
rule_id: 'coppa-sec-006',
|
|
27
|
+
action: 'suppress',
|
|
28
|
+
reason: 'Next.js enforces HTTPS in production. API routes run server-side. Development HTTP is expected.',
|
|
29
|
+
documentation_url: 'https://nextjs.org/docs/app/api-reference/next-config-js/headers',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
rule_id: 'coppa-ext-017',
|
|
33
|
+
action: 'downgrade',
|
|
34
|
+
downgrade_to: 'low',
|
|
35
|
+
reason: "Next.js Link component handles external navigation. rel='noopener noreferrer' is auto-added.",
|
|
36
|
+
documentation_url: 'https://nextjs.org/docs/app/api-reference/components/link',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
rule_id: 'coppa-cookies-016',
|
|
40
|
+
action: 'downgrade',
|
|
41
|
+
downgrade_to: 'low',
|
|
42
|
+
reason: 'Next.js middleware can intercept and manage cookies centrally.',
|
|
43
|
+
documentation_url: 'https://nextjs.org/docs/app/building-your-application/routing/middleware',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
safe_patterns: [
|
|
47
|
+
{
|
|
48
|
+
description: 'Next.js Image component for optimized, safe image handling',
|
|
49
|
+
patterns: [/next\/image/, /<Image\s/],
|
|
50
|
+
applies_to_rules: ['coppa-ugc-014'],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
description: 'Next.js middleware for centralized request/response interception',
|
|
54
|
+
patterns: [/middleware\.(ts|js)/, /NextResponse/],
|
|
55
|
+
applies_to_rules: ['coppa-sec-015'],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=nextjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../../src/frameworks/nextjs.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAIU,QAAA,aAAa,GAAqB;IAC7C,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,SAAS;IACf,SAAS,EAAE,YAAY;IACvB,aAAa,EAAE;QACb;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,WAAW;YACnB,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,qHAAqH;YAC7H,iBAAiB,EAAE,4FAA4F;SAChH;QACD;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,iGAAiG;YACzG,iBAAiB,EAAE,kEAAkE;SACtF;QACD;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,WAAW;YACnB,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,8FAA8F;YACtG,iBAAiB,EAAE,2DAA2D;SAC/E;QACD;YACE,OAAO,EAAE,mBAAmB;YAC5B,MAAM,EAAE,WAAW;YACnB,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,gEAAgE;YACxE,iBAAiB,EAAE,0EAA0E;SAC9F;KACF;IACD,aAAa,EAAE;QACb;YACE,WAAW,EAAE,4DAA4D;YACzE,QAAQ,EAAE,CAAC,aAAa,EAAE,UAAU,CAAC;YACrC,gBAAgB,EAAE,CAAC,eAAe,CAAC;SACpC;QACD;YACE,WAAW,EAAE,kEAAkE;YAC/E,QAAQ,EAAE,CAAC,qBAAqB,EAAE,cAAc,CAAC;YACjD,gBAAgB,EAAE,CAAC,eAAe,CAAC;SACpC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruby on Rails Framework Profile
|
|
3
|
+
*
|
|
4
|
+
* Rails provides built-in protections that overlap with several COPPA rules:
|
|
5
|
+
* - ERB templates auto-escape all output by default
|
|
6
|
+
* - Strong parameters filter mass assignment (mitigates PII leaks)
|
|
7
|
+
* - ActiveRecord supports soft delete via acts_as_paranoid or discard gem
|
|
8
|
+
* - force_ssl config enforces HTTPS application-wide
|
|
9
|
+
*/
|
|
10
|
+
import { FrameworkProfile } from './types';
|
|
11
|
+
export declare const railsProfile: FrameworkProfile;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ruby on Rails Framework Profile
|
|
4
|
+
*
|
|
5
|
+
* Rails provides built-in protections that overlap with several COPPA rules:
|
|
6
|
+
* - ERB templates auto-escape all output by default
|
|
7
|
+
* - Strong parameters filter mass assignment (mitigates PII leaks)
|
|
8
|
+
* - ActiveRecord supports soft delete via acts_as_paranoid or discard gem
|
|
9
|
+
* - force_ssl config enforces HTTPS application-wide
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.railsProfile = void 0;
|
|
13
|
+
exports.railsProfile = {
|
|
14
|
+
id: 'rails',
|
|
15
|
+
name: 'Ruby on Rails',
|
|
16
|
+
ecosystem: 'ruby',
|
|
17
|
+
handled_rules: [
|
|
18
|
+
{
|
|
19
|
+
rule_id: 'coppa-sec-015',
|
|
20
|
+
action: 'suppress',
|
|
21
|
+
reason: 'ERB templates auto-escape all output by default.',
|
|
22
|
+
documentation_url: 'https://guides.rubyonrails.org/security.html#cross-site-scripting-xss',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
rule_id: 'coppa-data-002',
|
|
26
|
+
action: 'downgrade',
|
|
27
|
+
downgrade_to: 'low',
|
|
28
|
+
reason: 'Rails strong parameters filter mass assignment.',
|
|
29
|
+
documentation_url: 'https://guides.rubyonrails.org/action_controller_overview.html#strong-parameters',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
rule_id: 'coppa-retention-005',
|
|
33
|
+
action: 'downgrade',
|
|
34
|
+
downgrade_to: 'low',
|
|
35
|
+
reason: 'ActiveRecord supports soft delete via acts_as_paranoid or discard gem.',
|
|
36
|
+
documentation_url: 'https://github.com/jhawthorn/discard',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
rule_id: 'coppa-sec-006',
|
|
40
|
+
action: 'suppress',
|
|
41
|
+
reason: 'Rails force_ssl config enforces HTTPS.',
|
|
42
|
+
documentation_url: 'https://guides.rubyonrails.org/configuring.html#config-force-ssl',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
safe_patterns: [
|
|
46
|
+
{
|
|
47
|
+
description: 'Rails CSRF protection via protect_from_forgery and authenticity tokens',
|
|
48
|
+
patterns: [/protect_from_forgery/, /authenticity_token/],
|
|
49
|
+
applies_to_rules: ['coppa-sec-015'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
description: 'Rails ActiveRecord encryption for sensitive attributes',
|
|
53
|
+
patterns: [/encrypts\s+:/, /has_encrypted/],
|
|
54
|
+
applies_to_rules: ['coppa-sec-006'],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=rails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rails.js","sourceRoot":"","sources":["../../src/frameworks/rails.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAIU,QAAA,YAAY,GAAqB;IAC5C,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,eAAe;IACrB,SAAS,EAAE,MAAM;IACjB,aAAa,EAAE;QACb;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,kDAAkD;YAC1D,iBAAiB,EAAE,uEAAuE;SAC3F;QACD;YACE,OAAO,EAAE,gBAAgB;YACzB,MAAM,EAAE,WAAW;YACnB,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,iDAAiD;YACzD,iBAAiB,EAAE,kFAAkF;SACtG;QACD;YACE,OAAO,EAAE,qBAAqB;YAC9B,MAAM,EAAE,WAAW;YACnB,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,wEAAwE;YAChF,iBAAiB,EAAE,sCAAsC;SAC1D;QACD;YACE,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,wCAAwC;YAChD,iBAAiB,EAAE,kEAAkE;SACtF;KACF;IACD,aAAa,EAAE;QACb;YACE,WAAW,EAAE,wEAAwE;YACrF,QAAQ,EAAE,CAAC,sBAAsB,EAAE,oBAAoB,CAAC;YACxD,gBAAgB,EAAE,CAAC,eAAe,CAAC;SACpC;QACD;YACE,WAAW,EAAE,wDAAwD;YACrE,QAAQ,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC;YAC3C,gBAAgB,EAAE,CAAC,eAAe,CAAC;SACpC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Allowlisting Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Defines the type contracts for framework profiles that declare which
|
|
5
|
+
* COPPA/ethical rules a framework already handles natively. When a developer
|
|
6
|
+
* declares their framework in .halorc.json, Halo uses these profiles to
|
|
7
|
+
* suppress or downgrade rules the framework covers automatically.
|
|
8
|
+
*/
|
|
9
|
+
export type FrameworkAction = 'suppress' | 'downgrade';
|
|
10
|
+
export interface FrameworkRuleOverride {
|
|
11
|
+
rule_id: string;
|
|
12
|
+
action: FrameworkAction;
|
|
13
|
+
downgrade_to?: 'critical' | 'high' | 'medium' | 'low';
|
|
14
|
+
condition?: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
documentation_url?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface FrameworkSafePattern {
|
|
19
|
+
description: string;
|
|
20
|
+
patterns: RegExp[];
|
|
21
|
+
applies_to_rules: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface FrameworkProfile {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
ecosystem: 'javascript' | 'python' | 'ruby';
|
|
27
|
+
handled_rules: FrameworkRuleOverride[];
|
|
28
|
+
safe_patterns: FrameworkSafePattern[];
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Framework Allowlisting Type Definitions
|
|
4
|
+
*
|
|
5
|
+
* Defines the type contracts for framework profiles that declare which
|
|
6
|
+
* COPPA/ethical rules a framework already handles natively. When a developer
|
|
7
|
+
* declares their framework in .halorc.json, Halo uses these profiles to
|
|
8
|
+
* suppress or downgrade rules the framework covers automatically.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/frameworks/types.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG"}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* Sprint 1 Fixes: Added tree-sitter for AST analysis, YAML rule loading
|
|
8
8
|
*/
|
|
9
9
|
import Parser from 'tree-sitter';
|
|
10
|
+
import { ConfidenceResult, ConfidenceSignals, ConfidenceInterpretation, ViolationInput } from './context-analyzer';
|
|
11
|
+
export type { ConfidenceResult, ConfidenceSignals, ConfidenceInterpretation, ViolationInput };
|
|
10
12
|
export type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
13
|
+
export type ASTVerdict = 'confirmed' | 'suppressed' | 'regex_only';
|
|
11
14
|
export type Fixability = 'auto' | 'guided' | 'flag-only';
|
|
12
15
|
export interface RemediationSpec {
|
|
13
16
|
fixability: Fixability;
|
|
@@ -34,6 +37,20 @@ export interface Violation {
|
|
|
34
37
|
matchType?: 'regex' | 'ast' | 'hybrid';
|
|
35
38
|
fixability?: Fixability;
|
|
36
39
|
remediation?: RemediationSpec;
|
|
40
|
+
/** AST analysis verdict: confirmed, suppressed, or regex_only */
|
|
41
|
+
astVerdict?: ASTVerdict;
|
|
42
|
+
/** AST analysis confidence: 0.0 to 1.0 */
|
|
43
|
+
astConfidence?: number;
|
|
44
|
+
/** Reason for AST verdict */
|
|
45
|
+
astReason?: string;
|
|
46
|
+
/** Whether this violation was suppressed by framework profile */
|
|
47
|
+
frameworkSuppressed?: boolean;
|
|
48
|
+
/** ContextAnalyzer confidence score (0.0-1.0) */
|
|
49
|
+
confidence?: number;
|
|
50
|
+
/** ContextAnalyzer interpretation */
|
|
51
|
+
confidenceInterpretation?: ConfidenceInterpretation;
|
|
52
|
+
/** ContextAnalyzer reason string */
|
|
53
|
+
confidenceReason?: string;
|
|
37
54
|
}
|
|
38
55
|
export interface Rule {
|
|
39
56
|
id: string;
|
|
@@ -161,8 +178,13 @@ export interface EngineConfig {
|
|
|
161
178
|
ethical?: boolean;
|
|
162
179
|
aiAudit?: boolean;
|
|
163
180
|
sectorAuSbd?: boolean;
|
|
181
|
+
sectorAuOsa?: boolean;
|
|
164
182
|
packs?: string[];
|
|
165
183
|
loadedRules?: Rule[];
|
|
184
|
+
framework?: string;
|
|
185
|
+
astAnalysis?: boolean;
|
|
186
|
+
historicalFPRates?: Record<string, number>;
|
|
187
|
+
suppressionRates?: Record<string, number>;
|
|
166
188
|
}
|
|
167
189
|
export interface ScanResult {
|
|
168
190
|
filePath: string;
|
|
@@ -176,6 +198,8 @@ export declare class HaloEngine {
|
|
|
176
198
|
private config;
|
|
177
199
|
private rules;
|
|
178
200
|
private treeSitter;
|
|
201
|
+
private astEngine;
|
|
202
|
+
private contextAnalyzer;
|
|
179
203
|
constructor(config?: EngineConfig);
|
|
180
204
|
/**
|
|
181
205
|
* Load rules from the bundled rules.json file, filtered by pack IDs.
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,9 @@ const tree_sitter_1 = __importDefault(require("tree-sitter"));
|
|
|
59
59
|
const tree_sitter_typescript_1 = __importDefault(require("tree-sitter-typescript"));
|
|
60
60
|
const tree_sitter_javascript_1 = __importDefault(require("tree-sitter-javascript"));
|
|
61
61
|
const yaml = __importStar(require("js-yaml"));
|
|
62
|
+
const ast_engine_1 = require("./ast-engine");
|
|
63
|
+
const frameworks_1 = require("./frameworks");
|
|
64
|
+
const context_analyzer_1 = require("./context-analyzer");
|
|
62
65
|
// Extract category from ruleId (e.g. "coppa-auth-001" → "auth", "ETHICAL-001" → "ethical", "AU-SBD-001" → "au-sbd")
|
|
63
66
|
function extractCategory(ruleId) {
|
|
64
67
|
if (ruleId.startsWith('ETHICAL'))
|
|
@@ -67,6 +70,16 @@ function extractCategory(ruleId) {
|
|
|
67
70
|
return 'ai-audit';
|
|
68
71
|
if (ruleId.startsWith('AU-SBD'))
|
|
69
72
|
return 'au-sbd';
|
|
73
|
+
if (ruleId.startsWith('AU-OSA'))
|
|
74
|
+
return 'au-osa';
|
|
75
|
+
if (ruleId.startsWith('caadca'))
|
|
76
|
+
return 'caadca';
|
|
77
|
+
if (ruleId.startsWith('AI-RISK'))
|
|
78
|
+
return 'ai-risk';
|
|
79
|
+
if (ruleId.startsWith('AI-TRANSPARENCY'))
|
|
80
|
+
return 'ai-transparency';
|
|
81
|
+
if (ruleId.startsWith('CAI-'))
|
|
82
|
+
return 'constitutional-ai';
|
|
70
83
|
const match = ruleId.match(/^coppa-(\w+)-\d+$/);
|
|
71
84
|
return match ? match[1] : 'unknown';
|
|
72
85
|
}
|
|
@@ -1140,6 +1153,12 @@ class HaloEngine {
|
|
|
1140
1153
|
constructor(config = {}) {
|
|
1141
1154
|
this.config = config;
|
|
1142
1155
|
this.treeSitter = new TreeSitterParser();
|
|
1156
|
+
this.astEngine = new ast_engine_1.ASTRuleEngine();
|
|
1157
|
+
this.contextAnalyzer = new context_analyzer_1.ContextAnalyzer({
|
|
1158
|
+
framework: config.framework,
|
|
1159
|
+
historicalFPRates: config.historicalFPRates,
|
|
1160
|
+
suppressionRates: config.suppressionRates,
|
|
1161
|
+
});
|
|
1143
1162
|
// Rule loading priority chain:
|
|
1144
1163
|
// 1. config.loadedRules — pre-compiled rules from CLI API fetch
|
|
1145
1164
|
// 2. config.rulesPath — YAML file (legacy)
|
|
@@ -1207,6 +1226,8 @@ class HaloEngine {
|
|
|
1207
1226
|
packs.push('ai-audit');
|
|
1208
1227
|
if (config.sectorAuSbd)
|
|
1209
1228
|
packs.push('au-sbd');
|
|
1229
|
+
if (config.sectorAuOsa)
|
|
1230
|
+
packs.push('au-osa');
|
|
1210
1231
|
return packs;
|
|
1211
1232
|
}
|
|
1212
1233
|
/**
|
|
@@ -1220,18 +1241,17 @@ class HaloEngine {
|
|
|
1220
1241
|
*/
|
|
1221
1242
|
scanFileWithAST(filePath, content, language = 'typescript') {
|
|
1222
1243
|
// First get regex-based violations
|
|
1223
|
-
|
|
1224
|
-
//
|
|
1244
|
+
let violations = this.scanFile(filePath, content);
|
|
1245
|
+
// Sprint 8: Enhanced AST analysis with ASTRuleEngine + framework overrides + ContextAnalyzer
|
|
1225
1246
|
try {
|
|
1226
|
-
const
|
|
1247
|
+
const tree = this.treeSitter.parse(content, language);
|
|
1248
|
+
// Legacy Sprint 1: AST-based detection for social login (signInWithPopup)
|
|
1227
1249
|
const functionCalls = this.treeSitter.findFunctionCalls(content, 'signInWithPopup');
|
|
1228
|
-
// Add AST-based detection for social login
|
|
1229
1250
|
for (const call of functionCalls) {
|
|
1230
|
-
// Check if already detected by regex
|
|
1231
1251
|
const exists = violations.some(v => v.ruleId === 'coppa-auth-001' &&
|
|
1232
1252
|
v.line === call.line);
|
|
1233
1253
|
if (!exists) {
|
|
1234
|
-
const authRule = exports.COPPA_RULES.find(r => r.id === 'coppa-auth-001');
|
|
1254
|
+
const authRule = this.rules.find(r => r.id === 'coppa-auth-001') || exports.COPPA_RULES.find(r => r.id === 'coppa-auth-001');
|
|
1235
1255
|
if (authRule) {
|
|
1236
1256
|
violations.push({
|
|
1237
1257
|
ruleId: 'coppa-auth-001',
|
|
@@ -1253,10 +1273,65 @@ class HaloEngine {
|
|
|
1253
1273
|
}
|
|
1254
1274
|
}
|
|
1255
1275
|
}
|
|
1276
|
+
// Sprint 8: Run ASTRuleEngine analysis on every violation to classify FPs
|
|
1277
|
+
if (this.config.astAnalysis !== false) {
|
|
1278
|
+
for (const violation of violations) {
|
|
1279
|
+
try {
|
|
1280
|
+
const astResult = this.astEngine.analyzeViolationWithPath(violation.ruleId, filePath, content, {
|
|
1281
|
+
ruleId: violation.ruleId,
|
|
1282
|
+
line: violation.line,
|
|
1283
|
+
column: violation.column,
|
|
1284
|
+
codeSnippet: violation.codeSnippet,
|
|
1285
|
+
}, tree);
|
|
1286
|
+
violation.astVerdict = astResult.verdict;
|
|
1287
|
+
violation.astConfidence = astResult.confidence;
|
|
1288
|
+
violation.astReason = astResult.reason;
|
|
1289
|
+
// Update matchType to reflect AST involvement
|
|
1290
|
+
if (astResult.verdict !== 'regex_only') {
|
|
1291
|
+
violation.matchType = 'hybrid';
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
catch (ruleError) {
|
|
1295
|
+
violation.astVerdict = 'regex_only';
|
|
1296
|
+
violation.astConfidence = 0;
|
|
1297
|
+
violation.astReason = 'AST analysis failed for this violation';
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1256
1301
|
}
|
|
1257
1302
|
catch (error) {
|
|
1258
|
-
// If AST parsing fails, fall back to regex-only
|
|
1303
|
+
// If AST parsing fails entirely, fall back to regex-only
|
|
1259
1304
|
console.warn('AST parsing failed, using regex-only mode:', error);
|
|
1305
|
+
for (const v of violations) {
|
|
1306
|
+
v.astVerdict = 'regex_only';
|
|
1307
|
+
v.astConfidence = 0;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
// Sprint 8: Apply framework overrides
|
|
1311
|
+
if (this.config.framework) {
|
|
1312
|
+
const result = (0, frameworks_1.applyFrameworkOverrides)(violations, this.config.framework);
|
|
1313
|
+
violations = result.violations;
|
|
1314
|
+
}
|
|
1315
|
+
// Sprint 8: ContextAnalyzer — compute confidence scores
|
|
1316
|
+
const violationInputs = violations.map(v => ({
|
|
1317
|
+
ruleId: v.ruleId,
|
|
1318
|
+
severity: v.severity,
|
|
1319
|
+
line: v.line,
|
|
1320
|
+
column: v.column,
|
|
1321
|
+
codeSnippet: v.codeSnippet,
|
|
1322
|
+
astVerdict: v.astVerdict,
|
|
1323
|
+
astConfidence: v.astConfidence,
|
|
1324
|
+
astReason: v.astReason,
|
|
1325
|
+
frameworkSuppressed: v.frameworkSuppressed,
|
|
1326
|
+
}));
|
|
1327
|
+
const confidenceResults = this.contextAnalyzer.analyzeFile(violationInputs, filePath, content);
|
|
1328
|
+
for (let i = 0; i < violations.length; i++) {
|
|
1329
|
+
const result = confidenceResults.get(i);
|
|
1330
|
+
if (result) {
|
|
1331
|
+
violations[i].confidence = result.confidence;
|
|
1332
|
+
violations[i].confidenceInterpretation = result.interpretation;
|
|
1333
|
+
violations[i].confidenceReason = result.reason;
|
|
1334
|
+
}
|
|
1260
1335
|
}
|
|
1261
1336
|
return violations;
|
|
1262
1337
|
}
|
|
@@ -1277,6 +1352,25 @@ class HaloEngine {
|
|
|
1277
1352
|
}
|
|
1278
1353
|
const violations = [];
|
|
1279
1354
|
const lines = content.split('\n');
|
|
1355
|
+
// Sprint 8: Test file detection for regex scanner (coppa-sec-010 FP fix)
|
|
1356
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
1357
|
+
const isTestFile = /\.(test|spec)\.(ts|tsx|js|jsx|py|rb|java|go)$/i.test(normalizedPath) ||
|
|
1358
|
+
/(^|\/)__tests__\//.test(normalizedPath) ||
|
|
1359
|
+
/(^|\/)test\//.test(normalizedPath) ||
|
|
1360
|
+
/(^|\/)tests\//.test(normalizedPath) ||
|
|
1361
|
+
/(^|\/)spec\//.test(normalizedPath) ||
|
|
1362
|
+
/(^|\/)fixtures\//.test(normalizedPath) ||
|
|
1363
|
+
/\.(stories|story)\.(ts|tsx|js|jsx)$/i.test(normalizedPath) ||
|
|
1364
|
+
/(^|\/)cypress\//.test(normalizedPath) ||
|
|
1365
|
+
/(^|\/)e2e\//.test(normalizedPath) ||
|
|
1366
|
+
/jest\.config|vitest\.config|playwright\.config/i.test(normalizedPath);
|
|
1367
|
+
// Rules that commonly false-positive in test/fixture files
|
|
1368
|
+
const TEST_FP_RULES = new Set([
|
|
1369
|
+
'coppa-sec-010', // Weak passwords in test fixtures
|
|
1370
|
+
'coppa-tracking-003', // Analytics snippets in test mocks
|
|
1371
|
+
'coppa-auth-001', // Auth patterns in test helpers
|
|
1372
|
+
'coppa-sec-015', // XSS patterns in security test cases
|
|
1373
|
+
]);
|
|
1280
1374
|
// Parse suppression comments
|
|
1281
1375
|
const suppressions = parseSuppressions(content);
|
|
1282
1376
|
// Track lines with global suppressions (at top of file)
|
|
@@ -1287,6 +1381,10 @@ class HaloEngine {
|
|
|
1287
1381
|
}
|
|
1288
1382
|
}
|
|
1289
1383
|
for (const rule of this.rules) {
|
|
1384
|
+
// Sprint 8: Skip rules that commonly FP in test files
|
|
1385
|
+
if (isTestFile && TEST_FP_RULES.has(rule.id)) {
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1290
1388
|
// Special handling for coppa-retention-005: skip if schema has retention fields
|
|
1291
1389
|
if (rule.id === 'coppa-retention-005') {
|
|
1292
1390
|
// Check if the content has retention-related fields
|