@runhalo/engine 0.4.0 → 0.6.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 +408 -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/fp-patterns.d.ts +36 -0
- package/dist/fp-patterns.js +426 -0
- package/dist/fp-patterns.js.map +1 -0
- package/dist/frameworks/angular.d.ts +11 -0
- package/dist/frameworks/angular.js +41 -0
- package/dist/frameworks/angular.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 +99 -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/react.d.ts +13 -0
- package/dist/frameworks/react.js +36 -0
- package/dist/frameworks/react.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/frameworks/vue.d.ts +9 -0
- package/dist/frameworks/vue.js +39 -0
- package/dist/frameworks/vue.js.map +1 -0
- package/dist/graduation/fp-verdict-logger.d.ts +81 -0
- package/dist/graduation/fp-verdict-logger.js +130 -0
- package/dist/graduation/fp-verdict-logger.js.map +1 -0
- package/dist/graduation/graduation-codifier.d.ts +37 -0
- package/dist/graduation/graduation-codifier.js +205 -0
- package/dist/graduation/graduation-codifier.js.map +1 -0
- package/dist/graduation/graduation-validator.d.ts +73 -0
- package/dist/graduation/graduation-validator.js +204 -0
- package/dist/graduation/graduation-validator.js.map +1 -0
- package/dist/graduation/index.d.ts +71 -0
- package/dist/graduation/index.js +105 -0
- package/dist/graduation/index.js.map +1 -0
- package/dist/graduation/pattern-aggregator.d.ts +77 -0
- package/dist/graduation/pattern-aggregator.js +154 -0
- package/dist/graduation/pattern-aggregator.js.map +1 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +718 -61
- package/dist/index.js.map +1 -1
- package/dist/review-board/two-agent-review.d.ts +152 -0
- package/dist/review-board/two-agent-review.js +463 -0
- package/dist/review-board/two-agent-review.js.map +1 -0
- 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 +9 -2
- package/rules/coppa-tier-1.yaml +17 -10
- package/rules/rules.json +2094 -99
- package/rules/validation-report.json +58 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Halo Scope Analyzer
|
|
4
|
+
* Determines the context of code being scanned to reduce false positives.
|
|
5
|
+
*
|
|
6
|
+
* Analyzes WHERE code lives (test file? admin route? type definition?)
|
|
7
|
+
* so that rule violations can be weighted or suppressed based on context.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ScopeAnalyzer = void 0;
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// File path patterns
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const TEST_FILE_PATTERNS = [
|
|
15
|
+
/_test\.go$/i,
|
|
16
|
+
/\.spec\.[tj]sx?$/i,
|
|
17
|
+
/\.test\.[tj]sx?$/i,
|
|
18
|
+
/(^|[/\\])__tests__[/\\]/i,
|
|
19
|
+
/(^|[/\\])test[/\\]/i,
|
|
20
|
+
/(^|[/\\])tests[/\\]/i,
|
|
21
|
+
/conftest\.py$/i,
|
|
22
|
+
/(^|[/\\])Test[A-Z][^/\\]*\.java$/,
|
|
23
|
+
];
|
|
24
|
+
const ADMIN_PATH_PATTERNS = [
|
|
25
|
+
/[/\\]admin[/\\]/i,
|
|
26
|
+
/[/\\]admin\.[a-z]+$/i,
|
|
27
|
+
/[/\\]dashboard[/\\]/i,
|
|
28
|
+
];
|
|
29
|
+
const ADMIN_CONTENT_PATTERNS = [
|
|
30
|
+
/\bAdminPanel\b/,
|
|
31
|
+
/\bAdminDashboard\b/,
|
|
32
|
+
/\bAdminLayout\b/,
|
|
33
|
+
/\bAdminRoute\b/,
|
|
34
|
+
/\bAdminPage\b/,
|
|
35
|
+
];
|
|
36
|
+
const USER_FACING_PATTERNS = [
|
|
37
|
+
/[/\\]pages[/\\]/i,
|
|
38
|
+
/[/\\]components[/\\]/i,
|
|
39
|
+
/[/\\]views[/\\]/i,
|
|
40
|
+
/[/\\]screens[/\\]/i,
|
|
41
|
+
/[/\\]app[/\\]/i,
|
|
42
|
+
/[/\\]src[/\\]components[/\\]/i,
|
|
43
|
+
];
|
|
44
|
+
const TYPE_DEF_PATH_PATTERNS = [
|
|
45
|
+
/\.d\.tsx?$/i,
|
|
46
|
+
];
|
|
47
|
+
const CONFIG_FILE_PATTERNS = [
|
|
48
|
+
/\.config\.[tj]sx?$/i,
|
|
49
|
+
/\.config\.[cm]?js$/i,
|
|
50
|
+
/(^|[/\\])config[/\\]/i,
|
|
51
|
+
/(^|[/\\])settings\.py$/i,
|
|
52
|
+
/(^|[/\\])\.env(\.|$)/i,
|
|
53
|
+
/\.config\./i,
|
|
54
|
+
];
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// AST node types used for line context analysis
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
const INTERFACE_NODE_TYPES = new Set([
|
|
59
|
+
'interface_declaration',
|
|
60
|
+
]);
|
|
61
|
+
const FUNCTION_NODE_TYPES = new Set([
|
|
62
|
+
'function_declaration',
|
|
63
|
+
'arrow_function',
|
|
64
|
+
'function',
|
|
65
|
+
'method_definition',
|
|
66
|
+
'function_expression',
|
|
67
|
+
]);
|
|
68
|
+
const CLASS_NODE_TYPES = new Set([
|
|
69
|
+
'class_declaration',
|
|
70
|
+
'class',
|
|
71
|
+
]);
|
|
72
|
+
const METHOD_NODE_TYPES = new Set([
|
|
73
|
+
'method_definition',
|
|
74
|
+
]);
|
|
75
|
+
const JSX_NODE_TYPES = new Set([
|
|
76
|
+
'jsx_element',
|
|
77
|
+
'jsx_self_closing_element',
|
|
78
|
+
]);
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// ScopeAnalyzer
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Analyzes the scope/context of source files and individual lines
|
|
84
|
+
* to help the engine make smarter decisions about rule applicability.
|
|
85
|
+
*/
|
|
86
|
+
class ScopeAnalyzer {
|
|
87
|
+
/**
|
|
88
|
+
* Analyze file-level scope from path heuristics and optionally content/AST.
|
|
89
|
+
*
|
|
90
|
+
* @param filePath - Relative or absolute path to the source file
|
|
91
|
+
* @param content - Full text content of the file
|
|
92
|
+
* @param tree - Optional tree-sitter AST (used for deeper analysis)
|
|
93
|
+
* @returns ScopeContext describing the file's role
|
|
94
|
+
*/
|
|
95
|
+
analyzeFile(filePath, content, tree) {
|
|
96
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
97
|
+
return {
|
|
98
|
+
isTestFile: this.detectTestFile(normalized),
|
|
99
|
+
isAdminRoute: this.detectAdminRoute(normalized, content),
|
|
100
|
+
isUserFacing: this.detectUserFacing(normalized),
|
|
101
|
+
isTypeDefinition: this.detectTypeDefinition(normalized, content, tree),
|
|
102
|
+
isConfigFile: this.detectConfigFile(normalized),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Analyze what AST construct encloses a given line.
|
|
107
|
+
*
|
|
108
|
+
* Walks the tree to find the deepest node that contains the target line,
|
|
109
|
+
* then walks upward through ancestors to determine enclosing constructs.
|
|
110
|
+
*
|
|
111
|
+
* @param line - 1-based line number
|
|
112
|
+
* @param tree - tree-sitter AST
|
|
113
|
+
* @returns LineContext describing the enclosing constructs
|
|
114
|
+
*/
|
|
115
|
+
analyzeLineContext(line, tree) {
|
|
116
|
+
const context = {
|
|
117
|
+
inInterfaceDecl: false,
|
|
118
|
+
inFunctionBody: false,
|
|
119
|
+
inClassMethod: false,
|
|
120
|
+
inJSXElement: false,
|
|
121
|
+
};
|
|
122
|
+
// tree-sitter uses 0-based row indices
|
|
123
|
+
const targetRow = line - 1;
|
|
124
|
+
const node = this.findDeepestNodeAtLine(tree.rootNode, targetRow);
|
|
125
|
+
if (!node) {
|
|
126
|
+
return context;
|
|
127
|
+
}
|
|
128
|
+
// Walk ancestors from the deepest node upward
|
|
129
|
+
let current = node;
|
|
130
|
+
let insideClassBody = false;
|
|
131
|
+
while (current) {
|
|
132
|
+
const type = current.type;
|
|
133
|
+
if (INTERFACE_NODE_TYPES.has(type)) {
|
|
134
|
+
context.inInterfaceDecl = true;
|
|
135
|
+
}
|
|
136
|
+
if (FUNCTION_NODE_TYPES.has(type)) {
|
|
137
|
+
context.inFunctionBody = true;
|
|
138
|
+
// If we already saw a class ancestor, this function is a class method
|
|
139
|
+
if (insideClassBody || METHOD_NODE_TYPES.has(type)) {
|
|
140
|
+
context.inClassMethod = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (CLASS_NODE_TYPES.has(type)) {
|
|
144
|
+
insideClassBody = true;
|
|
145
|
+
}
|
|
146
|
+
if (JSX_NODE_TYPES.has(type)) {
|
|
147
|
+
context.inJSXElement = true;
|
|
148
|
+
}
|
|
149
|
+
current = current.parent;
|
|
150
|
+
}
|
|
151
|
+
return context;
|
|
152
|
+
}
|
|
153
|
+
// -----------------------------------------------------------------------
|
|
154
|
+
// Private helpers — file scope detection
|
|
155
|
+
// -----------------------------------------------------------------------
|
|
156
|
+
detectTestFile(filePath) {
|
|
157
|
+
return TEST_FILE_PATTERNS.some(p => p.test(filePath));
|
|
158
|
+
}
|
|
159
|
+
detectAdminRoute(filePath, content) {
|
|
160
|
+
// Path-based detection
|
|
161
|
+
if (ADMIN_PATH_PATTERNS.some(p => p.test(filePath))) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
// Content-based detection (e.g., AdminPanel component)
|
|
165
|
+
if (ADMIN_CONTENT_PATTERNS.some(p => p.test(content))) {
|
|
166
|
+
// Only count if the path also suggests dashboard/admin context
|
|
167
|
+
// OR the component name is strongly admin-specific
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
detectUserFacing(filePath) {
|
|
173
|
+
return USER_FACING_PATTERNS.some(p => p.test(filePath));
|
|
174
|
+
}
|
|
175
|
+
detectTypeDefinition(filePath, content, tree) {
|
|
176
|
+
// .d.ts files are always type definitions
|
|
177
|
+
if (TYPE_DEF_PATH_PATTERNS.some(p => p.test(filePath))) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
// Heuristic: if the file content is predominantly interfaces/types
|
|
181
|
+
if (tree) {
|
|
182
|
+
return this.isAstPrimarilyTypes(tree);
|
|
183
|
+
}
|
|
184
|
+
// Fallback: content-based heuristic for non-AST path
|
|
185
|
+
return this.isContentPrimarilyTypes(content);
|
|
186
|
+
}
|
|
187
|
+
detectConfigFile(filePath) {
|
|
188
|
+
return CONFIG_FILE_PATTERNS.some(p => p.test(filePath));
|
|
189
|
+
}
|
|
190
|
+
// -----------------------------------------------------------------------
|
|
191
|
+
// Private helpers — type definition heuristics
|
|
192
|
+
// -----------------------------------------------------------------------
|
|
193
|
+
/**
|
|
194
|
+
* Check via AST whether the file consists primarily of type declarations.
|
|
195
|
+
* A file is considered primarily types if >= 80% of its top-level
|
|
196
|
+
* statements are interface/type declarations.
|
|
197
|
+
*/
|
|
198
|
+
isAstPrimarilyTypes(tree) {
|
|
199
|
+
const root = tree.rootNode;
|
|
200
|
+
let totalStatements = 0;
|
|
201
|
+
let typeStatements = 0;
|
|
202
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
203
|
+
const child = root.child(i);
|
|
204
|
+
if (!child)
|
|
205
|
+
continue;
|
|
206
|
+
const type = child.type;
|
|
207
|
+
// Skip import/export wrapper nodes — look inside them
|
|
208
|
+
if (type === 'export_statement') {
|
|
209
|
+
const inner = child.namedChildren[0];
|
|
210
|
+
if (inner) {
|
|
211
|
+
totalStatements++;
|
|
212
|
+
if (this.isTypeNode(inner.type)) {
|
|
213
|
+
typeStatements++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Skip import statements — they don't count
|
|
219
|
+
if (type === 'import_statement')
|
|
220
|
+
continue;
|
|
221
|
+
totalStatements++;
|
|
222
|
+
if (this.isTypeNode(type)) {
|
|
223
|
+
typeStatements++;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (totalStatements === 0)
|
|
227
|
+
return false;
|
|
228
|
+
return typeStatements / totalStatements >= 0.8;
|
|
229
|
+
}
|
|
230
|
+
isTypeNode(nodeType) {
|
|
231
|
+
return (nodeType === 'interface_declaration' ||
|
|
232
|
+
nodeType === 'type_alias_declaration');
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Content-based fallback: count top-level interface/type declaration blocks
|
|
236
|
+
* vs total top-level statements (excluding imports and blanks).
|
|
237
|
+
*
|
|
238
|
+
* Uses a simple state machine: lines starting with `interface` or `type`
|
|
239
|
+
* keywords begin a type block. Lines that are clearly function/class/const
|
|
240
|
+
* declarations begin a non-type block. Lines inside a block (e.g., interface
|
|
241
|
+
* members) are attributed to whatever block they belong to.
|
|
242
|
+
*/
|
|
243
|
+
isContentPrimarilyTypes(content) {
|
|
244
|
+
const lines = content.split('\n');
|
|
245
|
+
let typeBlocks = 0;
|
|
246
|
+
let nonTypeBlocks = 0;
|
|
247
|
+
const typeStart = /^\s*(export\s+)?(interface|type)\s+\w+/;
|
|
248
|
+
const nonTypeStart = /^\s*(export\s+)?(function|class|const|let|var|enum|async\s+function)\s+\w+/;
|
|
249
|
+
const importPattern = /^\s*import\s/;
|
|
250
|
+
const blankOrComment = /^\s*(\/\/|\/\*|\*|$)/;
|
|
251
|
+
const closingBrace = /^\s*\};\s*$/;
|
|
252
|
+
for (const line of lines) {
|
|
253
|
+
if (blankOrComment.test(line))
|
|
254
|
+
continue;
|
|
255
|
+
if (importPattern.test(line))
|
|
256
|
+
continue;
|
|
257
|
+
if (closingBrace.test(line))
|
|
258
|
+
continue;
|
|
259
|
+
if (typeStart.test(line)) {
|
|
260
|
+
typeBlocks++;
|
|
261
|
+
}
|
|
262
|
+
else if (nonTypeStart.test(line)) {
|
|
263
|
+
nonTypeBlocks++;
|
|
264
|
+
}
|
|
265
|
+
// Lines inside a block (members, etc.) are ignored for counting
|
|
266
|
+
}
|
|
267
|
+
const totalBlocks = typeBlocks + nonTypeBlocks;
|
|
268
|
+
if (totalBlocks === 0)
|
|
269
|
+
return false;
|
|
270
|
+
return typeBlocks / totalBlocks >= 0.8;
|
|
271
|
+
}
|
|
272
|
+
// -----------------------------------------------------------------------
|
|
273
|
+
// Private helpers — AST traversal
|
|
274
|
+
// -----------------------------------------------------------------------
|
|
275
|
+
/**
|
|
276
|
+
* Find the deepest (most specific) AST node whose range contains the
|
|
277
|
+
* target row. This gives us a starting point for ancestor walking.
|
|
278
|
+
*/
|
|
279
|
+
findDeepestNodeAtLine(node, targetRow) {
|
|
280
|
+
// If this node doesn't span the target row, skip it
|
|
281
|
+
if (node.startPosition.row > targetRow || node.endPosition.row < targetRow) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
// Try to find a more specific child
|
|
285
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
286
|
+
const child = node.child(i);
|
|
287
|
+
if (!child)
|
|
288
|
+
continue;
|
|
289
|
+
const deeper = this.findDeepestNodeAtLine(child, targetRow);
|
|
290
|
+
if (deeper) {
|
|
291
|
+
return deeper;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// No child matched — this node is the deepest
|
|
295
|
+
return node;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
exports.ScopeAnalyzer = ScopeAnalyzer;
|
|
299
|
+
exports.default = ScopeAnalyzer;
|
|
300
|
+
//# sourceMappingURL=scope-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-analyzer.js","sourceRoot":"","sources":["../src/scope-analyzer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runhalo/engine",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Halo rule engine —
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Halo rule engine — child online safety compliance detection. 130 rules across 10 packs covering COPPA, UK AADC, EU DSA, EU AI Act, and more.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"coppa",
|
|
18
18
|
"privacy",
|
|
19
19
|
"child-safety",
|
|
20
|
+
"compliance",
|
|
21
|
+
"aadc",
|
|
22
|
+
"dsa",
|
|
23
|
+
"online-safety",
|
|
20
24
|
"static-analysis",
|
|
21
25
|
"tree-sitter",
|
|
22
26
|
"ast",
|
|
@@ -33,6 +37,9 @@
|
|
|
33
37
|
},
|
|
34
38
|
"author": "Mindful Media <hello@mindfulmedia.org> (https://mindfulmedia.org)",
|
|
35
39
|
"license": "Apache-2.0",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
36
43
|
"dependencies": {
|
|
37
44
|
"glob": "^10.3.10",
|
|
38
45
|
"js-yaml": "^3.14.2",
|
package/rules/coppa-tier-1.yaml
CHANGED
|
@@ -13,7 +13,7 @@ rules:
|
|
|
13
13
|
name: Unverified Social Login Providers
|
|
14
14
|
coppaSection: "§ 312.5 (Parental Consent) & COPPA 2.0 § 312.11 (Mixed Audience)"
|
|
15
15
|
severity: critical
|
|
16
|
-
penaltyRange: "$
|
|
16
|
+
penaltyRange: "$53,088 per violation"
|
|
17
17
|
languages:
|
|
18
18
|
- typescript
|
|
19
19
|
- javascript
|
|
@@ -46,7 +46,7 @@ rules:
|
|
|
46
46
|
name: Sensitive Data in GET Requests
|
|
47
47
|
coppaSection: "§ 312.2 (Definitions - Personal Information)"
|
|
48
48
|
severity: high
|
|
49
|
-
penaltyRange: "$
|
|
49
|
+
penaltyRange: "$53,088 per violation"
|
|
50
50
|
languages:
|
|
51
51
|
- typescript
|
|
52
52
|
- javascript
|
|
@@ -80,7 +80,7 @@ rules:
|
|
|
80
80
|
name: Restricted Third-Party Ad Trackers
|
|
81
81
|
coppaSection: "§ 312.2 (Definitions - Support for Internal Operations) - COPPA 2.0 Focus"
|
|
82
82
|
severity: critical
|
|
83
|
-
penaltyRange: "$
|
|
83
|
+
penaltyRange: "$53,088 per violation"
|
|
84
84
|
languages:
|
|
85
85
|
- typescript
|
|
86
86
|
- javascript
|
|
@@ -123,7 +123,7 @@ rules:
|
|
|
123
123
|
name: Precise Geolocation Without Parental Consent
|
|
124
124
|
coppaSection: "§ 312.2 (Personal Information)"
|
|
125
125
|
severity: high
|
|
126
|
-
penaltyRange: "$
|
|
126
|
+
penaltyRange: "$53,088 per violation"
|
|
127
127
|
languages:
|
|
128
128
|
- typescript
|
|
129
129
|
- javascript
|
|
@@ -159,18 +159,23 @@ rules:
|
|
|
159
159
|
|
|
160
160
|
# ============================================
|
|
161
161
|
# 5. coppa-retention-005: Missing Data Retention Policy
|
|
162
|
+
# Updated: 2026-03-12 — COPPA 2025 final rule explicitly
|
|
163
|
+
# prohibits indefinite retention (§ 312.10)
|
|
162
164
|
# ============================================
|
|
163
165
|
- metadata:
|
|
164
166
|
id: coppa-retention-005
|
|
165
167
|
name: Missing Data Retention Policy in Database Schemas
|
|
166
|
-
coppaSection: "§ 312.10 (Data Retention and Deletion) - COPPA
|
|
168
|
+
coppaSection: "§ 312.10 (Data Retention and Deletion) - COPPA 2025 Final Rule"
|
|
167
169
|
severity: medium
|
|
168
|
-
penaltyRange: "
|
|
170
|
+
penaltyRange: "$53,088 per violation (indefinite retention prohibition)"
|
|
169
171
|
languages:
|
|
170
172
|
- typescript
|
|
171
173
|
- javascript
|
|
172
174
|
- python
|
|
173
175
|
- sql
|
|
176
|
+
- go
|
|
177
|
+
- java
|
|
178
|
+
- kotlin
|
|
174
179
|
falsePositiveRisk: high
|
|
175
180
|
patterns:
|
|
176
181
|
- regex:
|
|
@@ -186,17 +191,19 @@ rules:
|
|
|
186
191
|
pattern: "class\\s+\\w+.*extends\\s+Model(?!.*deletedAt)"
|
|
187
192
|
flags: "g"
|
|
188
193
|
autoFix:
|
|
189
|
-
description: "Add TTL index
|
|
194
|
+
description: "Add explicit retention period, TTL index, and deletion mechanism per COPPA 2025 § 312.10"
|
|
190
195
|
replacement: |
|
|
191
|
-
// Mongoose - Add TTL index
|
|
196
|
+
// Mongoose - Add TTL index with explicit retention period
|
|
192
197
|
new Schema({
|
|
193
198
|
createdAt: { type: Date, expires: '365d' },
|
|
199
|
+
retentionDays: { type: Number, default: 365 },
|
|
194
200
|
email: String
|
|
195
201
|
});
|
|
196
|
-
|
|
197
|
-
// Sequelize - Add deletedAt
|
|
202
|
+
|
|
203
|
+
// Sequelize - Add deletedAt with retention config
|
|
198
204
|
sequelize.define('User', {
|
|
199
205
|
deletedAt: { type: DataTypes.DATE },
|
|
206
|
+
retentionDays: { type: DataTypes.INTEGER, defaultValue: 365 },
|
|
200
207
|
// ... other fields
|
|
201
208
|
}, {
|
|
202
209
|
paranoid: true
|