@chemx/starter-kit 26.9.17-309 → 26.9.20-228
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/README.md +177 -2
- package/blueprints/molecule-capsule/m-tab-button/m-tab-button.controller.ts +8 -11
- package/cli/audit/ast-visitors.js +73 -0
- package/cli/audit/extended-text-patterns.js +218 -0
- package/cli/audit/extended-visitors.js +200 -171
- package/cli/audit/reporter-utils.js +13 -22
- package/cli/audit/rules-registry.js +25 -0
- package/cli/audit/rules.spec.js +205 -0
- package/cli/audit.js +1 -0
- package/cli/build/detector.js +40 -23
- package/cli/build/detector.spec.js +81 -0
- package/cli/build.d.ts +2 -1
- package/cli/build.js +37 -12
- package/cli/columnar.d.ts +14 -0
- package/cli/columnar.js +52 -0
- package/cli/columnar.spec.js +49 -0
- package/cli/errors/catcher.js +80 -0
- package/cli/errors/errors.spec.js +163 -0
- package/cli/errors/formatter.js +94 -0
- package/cli/errors/index.js +52 -0
- package/cli/errors/publisher.js +76 -0
- package/cli/errors/sanitizer.js +50 -0
- package/cli/errors/storage.js +39 -0
- package/cli/errors/types.d.ts +42 -0
- package/cli/help.js +29 -4
- package/cli/index.js +84 -9
- package/cli/installer-templates.js +1 -1
- package/cli/installer.d.ts +2 -0
- package/cli/installer.js +51 -22
- package/cli/installer.spec.js +72 -0
- package/cli/mcp/antigravity.js +78 -0
- package/cli/mcp/index.js +2 -1
- package/cli/mcp/installer.js +34 -9
- package/cli/mcp/installer.spec.js +24 -1
- package/cli/mcp/manifests.js +399 -0
- package/cli/mcp/prompts.js +28 -0
- package/cli/mcp/resources.js +94 -90
- package/cli/mcp/server.js +55 -6
- package/cli/mcp/server.spec.js +315 -10
- package/cli/mcp/tools-search.js +237 -0
- package/cli/mcp/tools-team.js +145 -0
- package/cli/mcp/tools.js +216 -191
- package/cli/mcp/types.d.ts +37 -0
- package/cli/navigator-actions.js +23 -10
- package/cli/navigator-banner-helpers.js +33 -0
- package/cli/navigator-banner.js +19 -38
- package/cli/navigator-banner.spec.js +68 -0
- package/cli/navigator-guide.js +73 -0
- package/cli/navigator-menu.js +20 -0
- package/cli/navigator.js +1 -0
- package/cli/patcher.d.ts +73 -0
- package/cli/patcher.js +249 -4
- package/cli/patcher.spec.js +148 -0
- package/cli/reader.js +65 -5
- package/cli/scaffold.js +7 -1
- package/cli/search-commands.js +10 -8
- package/cli/search-queries.js +8 -3
- package/cli/search-schema.js +5 -0
- package/cli/search.d.ts +16 -0
- package/cli/search.js +103 -11
- package/cli/silence-warnings.js +17 -0
- package/cli/team/index.js +30 -0
- package/cli/team/team-adversarial-locks.spec.js +105 -0
- package/cli/team/team-adversarial-telemetry.spec.js +93 -0
- package/cli/team/team-commands.js +199 -0
- package/cli/team/team-db-agents.js +79 -0
- package/cli/team/team-db-feed.js +84 -0
- package/cli/team/team-db-lock-promotion.js +91 -0
- package/cli/team/team-db-locks.js +94 -0
- package/cli/team/team-db-task-helpers.js +94 -0
- package/cli/team/team-db-tasks.js +90 -0
- package/cli/team/team-db.js +93 -0
- package/cli/team/team-format.js +80 -0
- package/cli/team/team-lock-concurrency.spec.js +182 -0
- package/cli/team/team-schema.js +74 -0
- package/cli/team/team-telemetry.js +104 -0
- package/cli/team/team-triage.js +97 -0
- package/cli/team/team.spec.js +344 -0
- package/cli/ui-actions-helpers.js +66 -0
- package/cli/ui-actions.js +85 -0
- package/cli/ui-attention.js +74 -0
- package/cli/ui-client-api.js +49 -0
- package/cli/ui-client-cert.js +27 -0
- package/cli/ui-client-script.js +83 -0
- package/cli/ui-db-studio.js +79 -0
- package/cli/ui-handlers.js +95 -0
- package/cli/ui-html.js +35 -0
- package/cli/ui-server-routes.js +63 -0
- package/cli/ui-server.js +75 -0
- package/cli/ui-styles.js +34 -0
- package/cli/ui-template-views.js +63 -0
- package/cli/ui-template.js +91 -0
- package/cli/ui.spec.js +305 -0
- package/cli/verify.js +367 -0
- package/cli/verify.spec.js +98 -0
- package/package.json +11 -14
- package/scripts/pre-commit.sh +1 -1
- package/cli/mcp.js +0 -326
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import * as t from '@babel/types';
|
|
4
2
|
import { RULE_REGISTRY } from './rules-registry.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
isTemplateExtension,
|
|
9
|
-
isCommentLine
|
|
10
|
-
} from './rules-predicates.js';
|
|
3
|
+
import { checkExtendedTextPatterns } from './extended-text-patterns.js';
|
|
4
|
+
|
|
5
|
+
export { checkExtendedTextPatterns };
|
|
11
6
|
|
|
12
7
|
const BARE_BOOLEANS = new Set([
|
|
13
8
|
'loading', 'valid', 'active', 'visible', 'open', 'disabled',
|
|
@@ -20,176 +15,55 @@ const BARE_HANDLERS = new Set([
|
|
|
20
15
|
'reset', 'confirm', 'cancel', 'refresh', 'sync', 'send', 'pay'
|
|
21
16
|
]);
|
|
22
17
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/assert\s*\.\s*(?:strictEqual|equal|deepEqual)\s*\(\s*(true|false|1|0)\s*,\s*\1\s*\)/,
|
|
30
|
-
/assert\s*\.\s*ok\s*\(\s*(?:true|1)\s*\)/
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
const IMG_WITHOUT_ALT_PATTERN = new RegExp(['<', 'img\\b', '(?![^>]*\\balt\\s*=)', '[^>]*>'].join(''), 'i');
|
|
34
|
-
const CLICKABLE_CONTAINER_PATTERN = new RegExp(['<', '(div|span)\\b', '(?![^>]*\\brole\\s*=\\s*[\'"](?:button|link|tab)[\'"])', '[^>]*\\b(?:@click|v-on:click)\\s*='].join(''), 'i');
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Pillar 10: Check for co-located test files for molecule capsules.
|
|
38
|
-
*/
|
|
39
|
-
const checkMoleculeCoLocatedTest = (filePath, relativePath, violations) => {
|
|
40
|
-
const baseName = path.basename(filePath);
|
|
41
|
-
const ext = path.extname(filePath);
|
|
42
|
-
|
|
43
|
-
// Stage 1: Atomic Concept Declarations
|
|
44
|
-
const isMoleculePath = relativePath.includes('molecules') || baseName.startsWith('m-');
|
|
45
|
-
const isComponentExt = isComponentExtension(ext);
|
|
46
|
-
const isTestOrSpecFile = baseName.includes('.spec.') || baseName.includes('.test.');
|
|
47
|
-
|
|
48
|
-
// Stage 2: Unified Decision Variable & Early Guard Clause
|
|
49
|
-
const isCandidateComponent = isMoleculePath && isComponentExt;
|
|
50
|
-
const isMoleculeComponent = isCandidateComponent && !isTestOrSpecFile;
|
|
51
|
-
if (!isMoleculeComponent) return;
|
|
52
|
-
|
|
53
|
-
const dir = path.dirname(filePath);
|
|
54
|
-
const [siblings, dirError] = toResultSync(() => (fs.existsSync(dir) ? fs.readdirSync(dir) : []));
|
|
55
|
-
|
|
56
|
-
// Stage 1: Atomic Guard Check
|
|
57
|
-
const hasDirError = Boolean(dirError);
|
|
58
|
-
const hasSiblings = Boolean(siblings);
|
|
59
|
-
const canInspectSiblings = !hasDirError && hasSiblings;
|
|
60
|
-
if (!canInspectSiblings) return;
|
|
61
|
-
|
|
62
|
-
const isTestSibling = (fileName) => /\.(test|spec)\.[jt]sx?$/.test(fileName);
|
|
63
|
-
const hasCoLocatedTest = siblings.some(isTestSibling);
|
|
64
|
-
if (hasCoLocatedTest) return;
|
|
65
|
-
|
|
66
|
-
const meta = RULE_REGISTRY.TEST_MISSING_COLOCATED;
|
|
67
|
-
violations.push({
|
|
68
|
-
filePath: relativePath,
|
|
69
|
-
line: 1,
|
|
70
|
-
column: 1,
|
|
71
|
-
hazard: 'Molecule capsule missing co-located test file (*.spec.ts or *.test.ts)',
|
|
72
|
-
rule: 'TEST_MISSING_COLOCATED',
|
|
73
|
-
severity: meta.severity,
|
|
74
|
-
pillar: meta.pillar,
|
|
75
|
-
directive: meta.directive
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export const checkExtendedTextPatterns = (content, lines, relativePath, filePath, violations) => {
|
|
80
|
-
checkMoleculeCoLocatedTest(filePath, relativePath, violations);
|
|
81
|
-
|
|
82
|
-
const isTestFile = /\.(test|spec)\.[jt]sx?$/.test(filePath);
|
|
83
|
-
const ext = path.extname(filePath);
|
|
84
|
-
const isTemplateFile = isTemplateExtension(ext);
|
|
85
|
-
|
|
86
|
-
lines.forEach((lineText, idx) => {
|
|
87
|
-
const lineNum = idx + 1;
|
|
88
|
-
const trimmed = lineText.trim();
|
|
89
|
-
const isComment = isCommentLine(trimmed);
|
|
90
|
-
|
|
91
|
-
// Pillar 9: Hardcoded Secret Detection
|
|
92
|
-
const hasEnvReference = trimmed.includes('process.env') || trimmed.includes('import.meta.env');
|
|
93
|
-
const canScanSecrets = !isComment && !hasEnvReference;
|
|
94
|
-
|
|
95
|
-
if (canScanSecrets) {
|
|
96
|
-
for (const pat of SECRET_PATTERNS) {
|
|
97
|
-
const matchesSecretPattern = pat.test(lineText);
|
|
98
|
-
const hasPlaceholder = lineText.includes('dummy') || lineText.includes('placeholder');
|
|
99
|
-
const isHardcodedSecret = matchesSecretPattern && !hasPlaceholder;
|
|
18
|
+
const SENSITIVE_KEYWORDS = new Set([
|
|
19
|
+
'password', 'passwd', 'secret', 'token', 'apikey', 'api_key',
|
|
20
|
+
'authtoken', 'auth_token', 'accesstoken', 'access_token',
|
|
21
|
+
'refreshtoken', 'refresh_token', 'privatekey', 'private_key',
|
|
22
|
+
'creditcard', 'credit_card', 'ssn'
|
|
23
|
+
]);
|
|
100
24
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
column: 1,
|
|
107
|
-
hazard: 'High-entropy API key or secret token detected in source code',
|
|
108
|
-
rule: 'SECURITY_HARDCODED_SECRET',
|
|
109
|
-
severity: meta.severity,
|
|
110
|
-
pillar: meta.pillar,
|
|
111
|
-
directive: meta.directive
|
|
112
|
-
});
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
25
|
+
const isSensitiveName = (name = '') => {
|
|
26
|
+
const normalized = String(name).toLowerCase();
|
|
27
|
+
if (SENSITIVE_KEYWORDS.has(normalized)) return true;
|
|
28
|
+
return /^(pass(word)?|token|secret|api[_-]?key)$/i.test(normalized);
|
|
29
|
+
};
|
|
117
30
|
|
|
118
|
-
|
|
119
|
-
const hasVHtml = isTemplateFile && /\bv-html\s*=\s*["']/.test(lineText);
|
|
120
|
-
if (hasVHtml) {
|
|
121
|
-
const hasSanitizer = /(?:sanitize|DOMPurify|escapeHtml|filterXSS)/i.test(lineText);
|
|
122
|
-
const isUnsanitizedVHtml = !hasSanitizer;
|
|
31
|
+
const JAVASCRIPT_URL_REGEX = /^\s*javascript:/i;
|
|
123
32
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
rule: 'SECURITY_RAW_HTML_INJECTION',
|
|
132
|
-
severity: meta.severity,
|
|
133
|
-
pillar: meta.pillar,
|
|
134
|
-
directive: meta.directive
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
33
|
+
const resolvePropertyKeyName = (key) => {
|
|
34
|
+
const isIdentifier = t.isIdentifier(key);
|
|
35
|
+
if (isIdentifier) return key.name;
|
|
36
|
+
const isStringLiteral = t.isStringLiteral(key);
|
|
37
|
+
if (isStringLiteral) return key.value;
|
|
38
|
+
return '';
|
|
39
|
+
};
|
|
138
40
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
hazard: 'Clickable generic container detected without native button/link semantics',
|
|
148
|
-
rule: 'A11Y_CLICKABLE_NON_SEMANTIC',
|
|
149
|
-
severity: meta.severity,
|
|
150
|
-
pillar: meta.pillar,
|
|
151
|
-
directive: meta.directive
|
|
152
|
-
});
|
|
41
|
+
const isSensitiveExpression = (node) => {
|
|
42
|
+
if (!node) return false;
|
|
43
|
+
if (t.isIdentifier(node)) {
|
|
44
|
+
return isSensitiveName(node.name);
|
|
45
|
+
}
|
|
46
|
+
if (t.isMemberExpression(node)) {
|
|
47
|
+
if (t.isIdentifier(node.property)) {
|
|
48
|
+
return isSensitiveName(node.property.name);
|
|
153
49
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const hasMissingAltImg = isTemplateFile && IMG_WITHOUT_ALT_PATTERN.test(lineText);
|
|
157
|
-
if (hasMissingAltImg) {
|
|
158
|
-
const meta = RULE_REGISTRY.A11Y_IMAGE_MISSING_ALT;
|
|
159
|
-
violations.push({
|
|
160
|
-
filePath: relativePath,
|
|
161
|
-
line: lineNum,
|
|
162
|
-
column: 1,
|
|
163
|
-
hazard: 'Image element missing alt text attribute',
|
|
164
|
-
rule: 'A11Y_IMAGE_MISSING_ALT',
|
|
165
|
-
severity: meta.severity,
|
|
166
|
-
pillar: meta.pillar,
|
|
167
|
-
directive: meta.directive
|
|
168
|
-
});
|
|
50
|
+
if (t.isStringLiteral(node.property)) {
|
|
51
|
+
return isSensitiveName(node.property.value);
|
|
169
52
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (isFakeGreenMatch) {
|
|
177
|
-
const meta = RULE_REGISTRY.TEST_FAKE_GREEN;
|
|
178
|
-
violations.push({
|
|
179
|
-
filePath: relativePath,
|
|
180
|
-
line: lineNum,
|
|
181
|
-
column: 1,
|
|
182
|
-
hazard: 'Fake green test assertion detected (asserting literal against itself)',
|
|
183
|
-
rule: 'TEST_FAKE_GREEN',
|
|
184
|
-
severity: meta.severity,
|
|
185
|
-
pillar: meta.pillar,
|
|
186
|
-
directive: meta.directive
|
|
187
|
-
});
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
53
|
+
}
|
|
54
|
+
if (t.isObjectExpression(node)) {
|
|
55
|
+
return node.properties.some((prop) => {
|
|
56
|
+
if (t.isObjectProperty(prop)) {
|
|
57
|
+
const keyName = resolvePropertyKeyName(prop.key);
|
|
58
|
+
return isSensitiveName(keyName);
|
|
190
59
|
}
|
|
191
|
-
|
|
192
|
-
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (t.isTemplateLiteral(node)) {
|
|
64
|
+
return node.expressions.some(isSensitiveExpression);
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
193
67
|
};
|
|
194
68
|
|
|
195
69
|
export const createExtendedVisitors = ({ relativePath, violations }) => {
|
|
@@ -244,6 +118,49 @@ export const createExtendedVisitors = ({ relativePath, violations }) => {
|
|
|
244
118
|
});
|
|
245
119
|
}
|
|
246
120
|
}
|
|
121
|
+
|
|
122
|
+
// Pillar 9: External link target="_blank" without rel="noopener noreferrer"
|
|
123
|
+
const isAnchorElement = tagName === 'a';
|
|
124
|
+
if (isAnchorElement) {
|
|
125
|
+
const targetAttr = astPath.node.attributes.find((attr) => t.isJSXAttribute(attr) && attr.name?.name === 'target');
|
|
126
|
+
let isTargetBlank = false;
|
|
127
|
+
if (targetAttr) {
|
|
128
|
+
if (t.isStringLiteral(targetAttr.value)) {
|
|
129
|
+
isTargetBlank = targetAttr.value.value.trim().toLowerCase() === '_blank';
|
|
130
|
+
} else if (t.isJSXExpressionContainer(targetAttr.value) && t.isStringLiteral(targetAttr.value.expression)) {
|
|
131
|
+
isTargetBlank = targetAttr.value.expression.value.trim().toLowerCase() === '_blank';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (isTargetBlank) {
|
|
136
|
+
const relAttr = astPath.node.attributes.find((attr) => t.isJSXAttribute(attr) && attr.name?.name === 'rel');
|
|
137
|
+
let hasRelProtection = false;
|
|
138
|
+
if (relAttr) {
|
|
139
|
+
if (t.isStringLiteral(relAttr.value)) {
|
|
140
|
+
const val = relAttr.value.value.toLowerCase();
|
|
141
|
+
hasRelProtection = val.includes('noopener') || val.includes('noreferrer');
|
|
142
|
+
} else if (t.isJSXExpressionContainer(relAttr.value) && t.isStringLiteral(relAttr.value.expression)) {
|
|
143
|
+
const val = relAttr.value.expression.value.toLowerCase();
|
|
144
|
+
hasRelProtection = val.includes('noopener') || val.includes('noreferrer');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!hasRelProtection) {
|
|
149
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
150
|
+
const meta = RULE_REGISTRY.SECURITY_REVERSE_TABNABBING;
|
|
151
|
+
violations.push({
|
|
152
|
+
filePath: relativePath,
|
|
153
|
+
line,
|
|
154
|
+
column: astPath.node.loc?.start.column || 1,
|
|
155
|
+
hazard: 'External link with target="_blank" missing rel="noopener noreferrer"',
|
|
156
|
+
rule: 'SECURITY_REVERSE_TABNABBING',
|
|
157
|
+
severity: meta.severity,
|
|
158
|
+
pillar: meta.pillar,
|
|
159
|
+
directive: meta.directive
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
247
164
|
},
|
|
248
165
|
|
|
249
166
|
JSXAttribute(astPath) {
|
|
@@ -314,6 +231,118 @@ export const createExtendedVisitors = ({ relativePath, violations }) => {
|
|
|
314
231
|
}
|
|
315
232
|
}
|
|
316
233
|
}
|
|
234
|
+
|
|
235
|
+
// Pillar 9: javascript: pseudo-protocol URL in JSX
|
|
236
|
+
const isUrlAttribute = ['href', 'src', 'action', 'formAction'].includes(attrName);
|
|
237
|
+
if (isUrlAttribute) {
|
|
238
|
+
let isJsUrl = false;
|
|
239
|
+
const val = astPath.node.value;
|
|
240
|
+
if (t.isStringLiteral(val)) {
|
|
241
|
+
isJsUrl = JAVASCRIPT_URL_REGEX.test(val.value);
|
|
242
|
+
} else if (t.isJSXExpressionContainer(val)) {
|
|
243
|
+
const expr = val.expression;
|
|
244
|
+
if (t.isStringLiteral(expr)) {
|
|
245
|
+
isJsUrl = JAVASCRIPT_URL_REGEX.test(expr.value);
|
|
246
|
+
} else if (t.isTemplateLiteral(expr) && expr.quasis.length > 0) {
|
|
247
|
+
isJsUrl = JAVASCRIPT_URL_REGEX.test(expr.quasis[0].value.raw);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (isJsUrl) {
|
|
252
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
253
|
+
const meta = RULE_REGISTRY.SECURITY_JAVASCRIPT_URL;
|
|
254
|
+
violations.push({
|
|
255
|
+
filePath: relativePath,
|
|
256
|
+
line,
|
|
257
|
+
column: astPath.node.loc?.start.column || 1,
|
|
258
|
+
hazard: `javascript: pseudo-protocol detected in ${attrName} attribute`,
|
|
259
|
+
rule: 'SECURITY_JAVASCRIPT_URL',
|
|
260
|
+
severity: meta.severity,
|
|
261
|
+
pillar: meta.pillar,
|
|
262
|
+
directive: meta.directive
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
CallExpression(astPath) {
|
|
269
|
+
const callee = astPath.node.callee;
|
|
270
|
+
|
|
271
|
+
// Pillar 9: Dynamic code execution via eval()
|
|
272
|
+
if (t.isIdentifier(callee) && callee.name === 'eval') {
|
|
273
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
274
|
+
const meta = RULE_REGISTRY.SECURITY_DYNAMIC_CODE_EXECUTION;
|
|
275
|
+
violations.push({
|
|
276
|
+
filePath: relativePath,
|
|
277
|
+
line,
|
|
278
|
+
column: astPath.node.loc?.start.column || 1,
|
|
279
|
+
hazard: 'Dynamic code execution via eval() detected',
|
|
280
|
+
rule: 'SECURITY_DYNAMIC_CODE_EXECUTION',
|
|
281
|
+
severity: meta.severity,
|
|
282
|
+
pillar: meta.pillar,
|
|
283
|
+
directive: meta.directive
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Pillar 9: String-based code execution in setTimeout/setInterval
|
|
288
|
+
if (t.isIdentifier(callee) && (callee.name === 'setTimeout' || callee.name === 'setInterval')) {
|
|
289
|
+
const firstArg = astPath.node.arguments[0];
|
|
290
|
+
const isStringExecution = t.isStringLiteral(firstArg) || t.isTemplateLiteral(firstArg);
|
|
291
|
+
if (isStringExecution) {
|
|
292
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
293
|
+
const meta = RULE_REGISTRY.SECURITY_DYNAMIC_CODE_EXECUTION;
|
|
294
|
+
violations.push({
|
|
295
|
+
filePath: relativePath,
|
|
296
|
+
line,
|
|
297
|
+
column: astPath.node.loc?.start.column || 1,
|
|
298
|
+
hazard: `String-based dynamic code execution in ${callee.name} detected`,
|
|
299
|
+
rule: 'SECURITY_DYNAMIC_CODE_EXECUTION',
|
|
300
|
+
severity: meta.severity,
|
|
301
|
+
pillar: meta.pillar,
|
|
302
|
+
directive: meta.directive
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Pillar 9: Sensitive credential/token logging in console
|
|
308
|
+
const isConsoleCall = t.isMemberExpression(callee) && t.isIdentifier(callee.object) && callee.object.name === 'console';
|
|
309
|
+
if (isConsoleCall) {
|
|
310
|
+
const args = astPath.node.arguments || [];
|
|
311
|
+
const hasSensitiveArg = args.some(isSensitiveExpression);
|
|
312
|
+
if (hasSensitiveArg) {
|
|
313
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
314
|
+
const meta = RULE_REGISTRY.SECURITY_SENSITIVE_LOGGING;
|
|
315
|
+
violations.push({
|
|
316
|
+
filePath: relativePath,
|
|
317
|
+
line,
|
|
318
|
+
column: astPath.node.loc?.start.column || 1,
|
|
319
|
+
hazard: 'Sensitive variable (credential, token, or password) logged to console',
|
|
320
|
+
rule: 'SECURITY_SENSITIVE_LOGGING',
|
|
321
|
+
severity: meta.severity,
|
|
322
|
+
pillar: meta.pillar,
|
|
323
|
+
directive: meta.directive
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
NewExpression(astPath) {
|
|
330
|
+
// Pillar 9: Dynamic code execution via new Function()
|
|
331
|
+
const callee = astPath.node.callee;
|
|
332
|
+
if (t.isIdentifier(callee) && callee.name === 'Function') {
|
|
333
|
+
const line = astPath.node.loc?.start.line || 1;
|
|
334
|
+
const meta = RULE_REGISTRY.SECURITY_DYNAMIC_CODE_EXECUTION;
|
|
335
|
+
violations.push({
|
|
336
|
+
filePath: relativePath,
|
|
337
|
+
line,
|
|
338
|
+
column: astPath.node.loc?.start.column || 1,
|
|
339
|
+
hazard: 'Dynamic code execution via new Function() constructor detected',
|
|
340
|
+
rule: 'SECURITY_DYNAMIC_CODE_EXECUTION',
|
|
341
|
+
severity: meta.severity,
|
|
342
|
+
pillar: meta.pillar,
|
|
343
|
+
directive: meta.directive
|
|
344
|
+
});
|
|
345
|
+
}
|
|
317
346
|
},
|
|
318
347
|
|
|
319
348
|
VariableDeclarator(astPath) {
|
|
@@ -36,31 +36,22 @@ export const groupViolationsBySeverity = (violations) => {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export const getSeverityBadge = (severity) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
case 'LOW':
|
|
47
|
-
return `${YELLOW}[LOW]${RESET}`;
|
|
48
|
-
default:
|
|
49
|
-
return `[${severity}]`;
|
|
50
|
-
}
|
|
39
|
+
const SEVERITY_BADGES = {
|
|
40
|
+
CRITICAL: `${RED}[CRITICAL]${RESET}`,
|
|
41
|
+
HIGH: `${ORANGE}[HIGH]${RESET}`,
|
|
42
|
+
MEDIUM: `${ORANGE}[MEDIUM]${RESET}`,
|
|
43
|
+
LOW: `${YELLOW}[LOW]${RESET}`
|
|
44
|
+
};
|
|
45
|
+
return SEVERITY_BADGES[severity] || `[${severity}]`;
|
|
51
46
|
};
|
|
52
47
|
|
|
53
48
|
export const getStatusBadge = (status) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return `${RED}✕ FAIL${RESET}`;
|
|
61
|
-
default:
|
|
62
|
-
return status;
|
|
63
|
-
}
|
|
49
|
+
const STATUS_BADGES = {
|
|
50
|
+
PASSED: `${GREEN}✔ PASS${RESET}`,
|
|
51
|
+
WARN: `${YELLOW}⚠ WARN${RESET}`,
|
|
52
|
+
FAILED: `${RED}✕ FAIL${RESET}`
|
|
53
|
+
};
|
|
54
|
+
return STATUS_BADGES[status] || status;
|
|
64
55
|
};
|
|
65
56
|
|
|
66
57
|
export const resolveIndividualPillarGrade = (pillarData) => {
|
|
@@ -36,6 +36,26 @@ export const RULE_REGISTRY = {
|
|
|
36
36
|
severity: 'CRITICAL',
|
|
37
37
|
directive: 'Extract hardcoded API keys and secrets into environment variables or secrets manager'
|
|
38
38
|
},
|
|
39
|
+
SECURITY_REVERSE_TABNABBING: {
|
|
40
|
+
pillar: PILLARS.PILLAR_9,
|
|
41
|
+
severity: 'MEDIUM',
|
|
42
|
+
directive: 'Add rel="noopener noreferrer" to external links with target="_blank" to prevent tab hijacking'
|
|
43
|
+
},
|
|
44
|
+
SECURITY_JAVASCRIPT_URL: {
|
|
45
|
+
pillar: PILLARS.PILLAR_9,
|
|
46
|
+
severity: 'CRITICAL',
|
|
47
|
+
directive: 'Do not use javascript: pseudo-protocol in links or action attributes; use event handlers'
|
|
48
|
+
},
|
|
49
|
+
SECURITY_DYNAMIC_CODE_EXECUTION: {
|
|
50
|
+
pillar: PILLARS.PILLAR_9,
|
|
51
|
+
severity: 'CRITICAL',
|
|
52
|
+
directive: 'Avoid eval(), new Function(), and string-based setTimeout/setInterval code execution'
|
|
53
|
+
},
|
|
54
|
+
SECURITY_SENSITIVE_LOGGING: {
|
|
55
|
+
pillar: PILLARS.PILLAR_9,
|
|
56
|
+
severity: 'HIGH',
|
|
57
|
+
directive: 'Never log sensitive credentials, tokens, or passwords to console diagnostics'
|
|
58
|
+
},
|
|
39
59
|
|
|
40
60
|
// Pillar 10: Testing Discipline
|
|
41
61
|
TEST_FAKE_GREEN: {
|
|
@@ -86,6 +106,11 @@ export const RULE_REGISTRY = {
|
|
|
86
106
|
severity: 'CRITICAL',
|
|
87
107
|
directive: 'Extract display states into computed descriptor objects or early returns'
|
|
88
108
|
},
|
|
109
|
+
CONTROL_FLOW_DISPATCH_SWITCH: {
|
|
110
|
+
pillar: PILLARS.PILLAR_2,
|
|
111
|
+
severity: 'MEDIUM',
|
|
112
|
+
directive: 'Replace repetitive dispatch switch with an O(1) keyed dictionary or method map (Directive 3.E)'
|
|
113
|
+
},
|
|
89
114
|
HOOK_SATURATION: {
|
|
90
115
|
pillar: PILLARS.PILLAR_3,
|
|
91
116
|
severity: 'HIGH',
|