@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.
Files changed (98) hide show
  1. package/README.md +177 -2
  2. package/blueprints/molecule-capsule/m-tab-button/m-tab-button.controller.ts +8 -11
  3. package/cli/audit/ast-visitors.js +73 -0
  4. package/cli/audit/extended-text-patterns.js +218 -0
  5. package/cli/audit/extended-visitors.js +200 -171
  6. package/cli/audit/reporter-utils.js +13 -22
  7. package/cli/audit/rules-registry.js +25 -0
  8. package/cli/audit/rules.spec.js +205 -0
  9. package/cli/audit.js +1 -0
  10. package/cli/build/detector.js +40 -23
  11. package/cli/build/detector.spec.js +81 -0
  12. package/cli/build.d.ts +2 -1
  13. package/cli/build.js +37 -12
  14. package/cli/columnar.d.ts +14 -0
  15. package/cli/columnar.js +52 -0
  16. package/cli/columnar.spec.js +49 -0
  17. package/cli/errors/catcher.js +80 -0
  18. package/cli/errors/errors.spec.js +163 -0
  19. package/cli/errors/formatter.js +94 -0
  20. package/cli/errors/index.js +52 -0
  21. package/cli/errors/publisher.js +76 -0
  22. package/cli/errors/sanitizer.js +50 -0
  23. package/cli/errors/storage.js +39 -0
  24. package/cli/errors/types.d.ts +42 -0
  25. package/cli/help.js +29 -4
  26. package/cli/index.js +84 -9
  27. package/cli/installer-templates.js +1 -1
  28. package/cli/installer.d.ts +2 -0
  29. package/cli/installer.js +51 -22
  30. package/cli/installer.spec.js +72 -0
  31. package/cli/mcp/antigravity.js +78 -0
  32. package/cli/mcp/index.js +2 -1
  33. package/cli/mcp/installer.js +34 -9
  34. package/cli/mcp/installer.spec.js +24 -1
  35. package/cli/mcp/manifests.js +399 -0
  36. package/cli/mcp/prompts.js +28 -0
  37. package/cli/mcp/resources.js +94 -90
  38. package/cli/mcp/server.js +55 -6
  39. package/cli/mcp/server.spec.js +315 -10
  40. package/cli/mcp/tools-search.js +237 -0
  41. package/cli/mcp/tools-team.js +145 -0
  42. package/cli/mcp/tools.js +216 -191
  43. package/cli/mcp/types.d.ts +37 -0
  44. package/cli/navigator-actions.js +23 -10
  45. package/cli/navigator-banner-helpers.js +33 -0
  46. package/cli/navigator-banner.js +19 -38
  47. package/cli/navigator-banner.spec.js +68 -0
  48. package/cli/navigator-guide.js +73 -0
  49. package/cli/navigator-menu.js +20 -0
  50. package/cli/navigator.js +1 -0
  51. package/cli/patcher.d.ts +73 -0
  52. package/cli/patcher.js +249 -4
  53. package/cli/patcher.spec.js +148 -0
  54. package/cli/reader.js +65 -5
  55. package/cli/scaffold.js +7 -1
  56. package/cli/search-commands.js +10 -8
  57. package/cli/search-queries.js +8 -3
  58. package/cli/search-schema.js +5 -0
  59. package/cli/search.d.ts +16 -0
  60. package/cli/search.js +103 -11
  61. package/cli/silence-warnings.js +17 -0
  62. package/cli/team/index.js +30 -0
  63. package/cli/team/team-adversarial-locks.spec.js +105 -0
  64. package/cli/team/team-adversarial-telemetry.spec.js +93 -0
  65. package/cli/team/team-commands.js +199 -0
  66. package/cli/team/team-db-agents.js +79 -0
  67. package/cli/team/team-db-feed.js +84 -0
  68. package/cli/team/team-db-lock-promotion.js +91 -0
  69. package/cli/team/team-db-locks.js +94 -0
  70. package/cli/team/team-db-task-helpers.js +94 -0
  71. package/cli/team/team-db-tasks.js +90 -0
  72. package/cli/team/team-db.js +93 -0
  73. package/cli/team/team-format.js +80 -0
  74. package/cli/team/team-lock-concurrency.spec.js +182 -0
  75. package/cli/team/team-schema.js +74 -0
  76. package/cli/team/team-telemetry.js +104 -0
  77. package/cli/team/team-triage.js +97 -0
  78. package/cli/team/team.spec.js +344 -0
  79. package/cli/ui-actions-helpers.js +66 -0
  80. package/cli/ui-actions.js +85 -0
  81. package/cli/ui-attention.js +74 -0
  82. package/cli/ui-client-api.js +49 -0
  83. package/cli/ui-client-cert.js +27 -0
  84. package/cli/ui-client-script.js +83 -0
  85. package/cli/ui-db-studio.js +79 -0
  86. package/cli/ui-handlers.js +95 -0
  87. package/cli/ui-html.js +35 -0
  88. package/cli/ui-server-routes.js +63 -0
  89. package/cli/ui-server.js +75 -0
  90. package/cli/ui-styles.js +34 -0
  91. package/cli/ui-template-views.js +63 -0
  92. package/cli/ui-template.js +91 -0
  93. package/cli/ui.spec.js +305 -0
  94. package/cli/verify.js +367 -0
  95. package/cli/verify.spec.js +98 -0
  96. package/package.json +11 -14
  97. package/scripts/pre-commit.sh +1 -1
  98. 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 { toResultSync } from './rules-helpers.js';
6
- import {
7
- isComponentExtension,
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 SECRET_PATTERNS = [
24
- /(['"])(?:sk-[a-zA-Z0-9_-]{24,}|ghp_[a-zA-Z0-9]{30,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z-_]{35})\1/
25
- ];
26
-
27
- const FAKE_GREEN_PATTERNS = [
28
- /expect\s*\(\s*(true|false|1|0)\s*\)\s*\.\s*to(?:Be|Equal|StrictEqual)\s*\(\s*\1\s*\)/,
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
- if (isHardcodedSecret) {
102
- const meta = RULE_REGISTRY.SECURITY_HARDCODED_SECRET;
103
- violations.push({
104
- filePath: relativePath,
105
- line: lineNum,
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
- // Pillar 9: Unsanitized v-html injection (Vue / HTML templates)
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
- if (isUnsanitizedVHtml) {
125
- const meta = RULE_REGISTRY.SECURITY_RAW_HTML_INJECTION;
126
- violations.push({
127
- filePath: relativePath,
128
- line: lineNum,
129
- column: 1,
130
- hazard: 'Unsanitized HTML injection via v-html detected without DOMPurify',
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
- // Pillar 8: Non-semantic clickable containers in templates (@click on div/span)
140
- const hasClickableContainer = isTemplateFile && CLICKABLE_CONTAINER_PATTERN.test(lineText);
141
- if (hasClickableContainer) {
142
- const meta = RULE_REGISTRY.A11Y_CLICKABLE_NON_SEMANTIC;
143
- violations.push({
144
- filePath: relativePath,
145
- line: lineNum,
146
- column: 1,
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
- // Pillar 8: Missing alt attribute on img tags
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
- // Pillar 10: Fake green tests (only in spec/test files)
172
- const canCheckFakeGreen = isTestFile && !isComment;
173
- if (canCheckFakeGreen) {
174
- for (const pat of FAKE_GREEN_PATTERNS) {
175
- const isFakeGreenMatch = pat.test(lineText);
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
- switch (severity) {
40
- case 'CRITICAL':
41
- return `${RED}[CRITICAL]${RESET}`;
42
- case 'HIGH':
43
- return `${ORANGE}[HIGH]${RESET}`;
44
- case 'MEDIUM':
45
- return `${ORANGE}[MEDIUM]${RESET}`;
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
- switch (status) {
55
- case 'PASSED':
56
- return `${GREEN}✔ PASS${RESET}`;
57
- case 'WARN':
58
- return `${YELLOW}⚠ WARN${RESET}`;
59
- case 'FAILED':
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',