@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.
@@ -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
- const violations = this.scanFile(filePath, content);
1224
- // Then enhance with AST analysis
1244
+ let violations = this.scanFile(filePath, content);
1245
+ // Sprint 8: Enhanced AST analysis with ASTRuleEngine + framework overrides + ContextAnalyzer
1225
1246
  try {
1226
- const identifiers = this.treeSitter.extractIdentifiers(content);
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