@aspect-guard/core 0.5.1 → 0.5.2

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/index.d.ts CHANGED
@@ -265,6 +265,77 @@ declare class RuleRegistry {
265
265
  }
266
266
  declare const ruleRegistry: RuleRegistry;
267
267
 
268
+ /**
269
+ * Shared pattern matching utilities for detection rules.
270
+ * Reduces code duplication across rules and improves maintainability.
271
+ */
272
+
273
+ /**
274
+ * Pattern definition for matching in file content
275
+ */
276
+ interface MatchPattern {
277
+ /** Name of the pattern for evidence reporting */
278
+ name: string;
279
+ /** Regex pattern (should have 'g' flag for multiple matches) */
280
+ pattern: RegExp;
281
+ /** If specified, use this capture group for the matched value */
282
+ matchGroup?: number;
283
+ }
284
+ /**
285
+ * Options for file filtering
286
+ */
287
+ interface FileFilterOptions {
288
+ /** File extensions to include (e.g., ['.js', '.ts']) */
289
+ extensions?: string[];
290
+ /** Regex patterns to exclude files */
291
+ excludePatterns?: RegExp[];
292
+ }
293
+ /**
294
+ * Options for pattern matching
295
+ */
296
+ interface MatchOptions {
297
+ /** Maximum length for snippet in evidence */
298
+ maxSnippetLength?: number;
299
+ /** Function to validate matched value (return false to skip) */
300
+ validate?: (value: string, content: string, matchIndex: number) => boolean;
301
+ }
302
+ /** Default code file extensions */
303
+ declare const CODE_EXTENSIONS: string[];
304
+ /** Simple JS/TS extensions */
305
+ declare const JS_TS_EXTENSIONS: string[];
306
+ /**
307
+ * Check if a file should be processed based on extension
308
+ */
309
+ declare function hasExtension(filePath: string, extensions: string[]): boolean;
310
+ /**
311
+ * Check if a file matches any exclusion pattern
312
+ */
313
+ declare function isExcluded(filePath: string, patterns: RegExp[]): boolean;
314
+ /**
315
+ * Get line number from content index (1-based)
316
+ */
317
+ declare function getLineNumber(content: string, index: number): number;
318
+ /**
319
+ * Get line content at a specific line number (1-based)
320
+ */
321
+ declare function getLineContent(lines: string[], lineNumber: number): string;
322
+ /**
323
+ * Check if an index position is inside a comment
324
+ */
325
+ declare function isInComment(content: string, matchIndex: number): boolean;
326
+ /**
327
+ * Match all patterns in a single file and generate evidence
328
+ */
329
+ declare function matchPatternsInFile(filePath: string, content: string, patterns: MatchPattern[], options?: MatchOptions): Evidence[];
330
+ /**
331
+ * Match patterns across all files with filtering
332
+ */
333
+ declare function matchPatternsInFiles(files: Map<string, string>, patterns: MatchPattern[], filterOptions?: FileFilterOptions, matchOptions?: MatchOptions): Evidence[];
334
+ /**
335
+ * Check if content within a context window matches a pattern
336
+ */
337
+ declare function hasPatternInContext(content: string, matchIndex: number, matchLength: number, contextPattern: RegExp, contextWindow?: number): boolean;
338
+
268
339
  declare function registerBuiltInRules(): void;
269
340
 
270
341
  /**
@@ -752,6 +823,6 @@ declare function generateBaseline(_extensionPaths: string[], _outputPath?: strin
752
823
  errors: string[];
753
824
  }>;
754
825
 
755
- declare const VERSION = "0.5.1";
826
+ declare const VERSION = "0.5.2";
756
827
 
757
- export { ALL_POPULAR_EXTENSIONS, type AdjustFindingsOptions, type AuditReport, type BundleDetectionResult, DETECTION_RULES, type DetectedIDE, type DetectionRule, type Evidence, type ExtensionCategory, ExtensionGuardScanner, type ExtensionHash, type ExtensionInfo, type ExtensionManifest, type Finding, type FindingCategory, type FullScanReport, type HashDatabase, IDE_PATHS, type InspectOptions, type IntegrityInfo, type IntegrityResult, type IntegrityStatus, JsonReporter, MEGA_POPULAR_EXTENSIONS, MarkdownReporter, POPULAR_EXTENSIONS, type PolicyAction, type PolicyConfig, PolicyEngine, type PolicyRules, type PolicyViolation, type PopularExtension, type Reporter, type ReporterOptions, type RiskLevel, RuleEngine, type RuleEngineOptions, SEVERITY_ORDER, SarifReporter, type ScanOptions, type ScanResult, type ScanSummary, type Severity, TRUSTED_EXTENSION_IDS, TRUSTED_PUBLISHERS, VERIFIED_PUBLISHERS, VERSION, addHash, adjustFindings, categorizeExtension, clearHashCache, collectFiles, compareSeverity, computeExtensionHashes, createHashRecord, detectBundle, detectIDEPaths, expandPath, generateBaseline, getDefaultDatabasePath, getHash, getIDEExtensionPath, getPopularityTier, getSupportedIDEs, isAtLeastSeverity, isBundleOutputPath, isIDEInstalled, isMegaPopular, isPopular, isTrustedExtension, isTrustedPublisher, isVerifiedPublisher, loadHashDatabase, loadPolicyConfig, readExtension, readExtensionsFromDirectory, registerBuiltInRules, ruleRegistry, saveHashDatabase, sha256, shouldCollectFile, shouldReduceSeverityForBundle, verifyIntegrity };
828
+ export { ALL_POPULAR_EXTENSIONS, type AdjustFindingsOptions, type AuditReport, type BundleDetectionResult, CODE_EXTENSIONS, DETECTION_RULES, type DetectedIDE, type DetectionRule, type Evidence, type ExtensionCategory, ExtensionGuardScanner, type ExtensionHash, type ExtensionInfo, type ExtensionManifest, type FileFilterOptions, type Finding, type FindingCategory, type FullScanReport, type HashDatabase, IDE_PATHS, type InspectOptions, type IntegrityInfo, type IntegrityResult, type IntegrityStatus, JS_TS_EXTENSIONS, JsonReporter, MEGA_POPULAR_EXTENSIONS, MarkdownReporter, type MatchOptions, type MatchPattern, POPULAR_EXTENSIONS, type PolicyAction, type PolicyConfig, PolicyEngine, type PolicyRules, type PolicyViolation, type PopularExtension, type Reporter, type ReporterOptions, type RiskLevel, RuleEngine, type RuleEngineOptions, SEVERITY_ORDER, SarifReporter, type ScanOptions, type ScanResult, type ScanSummary, type Severity, TRUSTED_EXTENSION_IDS, TRUSTED_PUBLISHERS, VERIFIED_PUBLISHERS, VERSION, addHash, adjustFindings, categorizeExtension, clearHashCache, collectFiles, compareSeverity, computeExtensionHashes, createHashRecord, detectBundle, detectIDEPaths, expandPath, generateBaseline, getDefaultDatabasePath, getHash, getIDEExtensionPath, getLineContent, getLineNumber, getPopularityTier, getSupportedIDEs, hasExtension, hasPatternInContext, isAtLeastSeverity, isBundleOutputPath, isExcluded, isIDEInstalled, isInComment, isMegaPopular, isPopular, isTrustedExtension, isTrustedPublisher, isVerifiedPublisher, loadHashDatabase, loadPolicyConfig, matchPatternsInFile, matchPatternsInFiles, readExtension, readExtensionsFromDirectory, registerBuiltInRules, ruleRegistry, saveHashDatabase, sha256, shouldCollectFile, shouldReduceSeverityForBundle, verifyIntegrity };
package/dist/index.js CHANGED
@@ -1110,8 +1110,89 @@ var critDataExfiltration = {
1110
1110
  }
1111
1111
  };
1112
1112
 
1113
+ // src/rules/pattern-matcher.ts
1114
+ var CODE_EXTENSIONS = [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"];
1115
+ var JS_TS_EXTENSIONS = [".js", ".ts"];
1116
+ function hasExtension(filePath, extensions) {
1117
+ return extensions.some((ext) => filePath.endsWith(ext));
1118
+ }
1119
+ function isExcluded(filePath, patterns) {
1120
+ return patterns.some((pattern) => pattern.test(filePath));
1121
+ }
1122
+ function getLineNumber(content, index) {
1123
+ return content.slice(0, index).split("\n").length;
1124
+ }
1125
+ function getLineContent(lines, lineNumber) {
1126
+ return lines[lineNumber - 1]?.trim() ?? "";
1127
+ }
1128
+ function isInComment(content, matchIndex) {
1129
+ const lineStart = content.lastIndexOf("\n", matchIndex) + 1;
1130
+ const lineContent = content.slice(lineStart, matchIndex);
1131
+ if (lineContent.includes("//")) {
1132
+ return true;
1133
+ }
1134
+ const beforeMatch = content.slice(0, matchIndex);
1135
+ const lastBlockStart = beforeMatch.lastIndexOf("/*");
1136
+ const lastBlockEnd = beforeMatch.lastIndexOf("*/");
1137
+ return lastBlockStart > lastBlockEnd;
1138
+ }
1139
+ function matchPatternsInFile(filePath, content, patterns, options = {}) {
1140
+ const evidences = [];
1141
+ const lines = content.split("\n");
1142
+ const maxSnippetLength = options.maxSnippetLength ?? 100;
1143
+ for (const { name, pattern, matchGroup } of patterns) {
1144
+ pattern.lastIndex = 0;
1145
+ let match;
1146
+ while ((match = pattern.exec(content)) !== null) {
1147
+ const matchIndex = match.index;
1148
+ const matchedValue = matchGroup !== void 0 ? match[matchGroup] : match[0];
1149
+ if (!matchedValue) {
1150
+ continue;
1151
+ }
1152
+ if (options.validate && !options.validate(matchedValue, content, matchIndex)) {
1153
+ continue;
1154
+ }
1155
+ const lineNumber = getLineNumber(content, matchIndex);
1156
+ const lineContent = getLineContent(lines, lineNumber);
1157
+ evidences.push({
1158
+ filePath,
1159
+ lineNumber,
1160
+ lineContent: lineContent.length > maxSnippetLength ? lineContent.slice(0, maxSnippetLength) + "..." : lineContent,
1161
+ matchedPattern: name,
1162
+ snippet: matchedValue.length > maxSnippetLength ? matchedValue.slice(0, maxSnippetLength) + "..." : matchedValue
1163
+ });
1164
+ }
1165
+ }
1166
+ return evidences;
1167
+ }
1168
+ function matchPatternsInFiles(files, patterns, filterOptions = {}, matchOptions = {}) {
1169
+ const evidences = [];
1170
+ const extensions = filterOptions.extensions ?? JS_TS_EXTENSIONS;
1171
+ const excludePatterns = filterOptions.excludePatterns ?? [];
1172
+ for (const [filePath, content] of files) {
1173
+ if (!hasExtension(filePath, extensions)) {
1174
+ continue;
1175
+ }
1176
+ if (excludePatterns.length > 0 && isExcluded(filePath, excludePatterns)) {
1177
+ continue;
1178
+ }
1179
+ if (!content || content.trim().length === 0) {
1180
+ continue;
1181
+ }
1182
+ const fileEvidences = matchPatternsInFile(filePath, content, patterns, matchOptions);
1183
+ evidences.push(...fileEvidences);
1184
+ }
1185
+ return evidences;
1186
+ }
1187
+ function hasPatternInContext(content, matchIndex, matchLength, contextPattern, contextWindow = 200) {
1188
+ const startIndex = Math.max(0, matchIndex - contextWindow);
1189
+ const endIndex = Math.min(content.length, matchIndex + matchLength + contextWindow);
1190
+ const context = content.slice(startIndex, endIndex);
1191
+ return contextPattern.test(context);
1192
+ }
1193
+
1113
1194
  // src/rules/built-in/crit-remote-execution.ts
1114
- var DANGEROUS_PATTERNS = [
1195
+ var PATTERNS = [
1115
1196
  { name: "eval", pattern: /\beval\s*\(/g },
1116
1197
  { name: "Function-constructor", pattern: /new\s+Function\s*\(/g },
1117
1198
  {
@@ -1124,9 +1205,9 @@ var DANGEROUS_PATTERNS = [
1124
1205
  },
1125
1206
  { name: "child_process-spawn-shell", pattern: /\.spawn\s*\([^)]*\{[^}]*shell\s*:\s*true/g },
1126
1207
  { name: "vm-runInContext", pattern: /vm\.run(?:InContext|InNewContext|InThisContext)\s*\(/g },
1127
- { name: "vm-Script", pattern: /new\s+vm\.Script\s*\(/g }
1208
+ { name: "vm-Script", pattern: /new\s+vm\.Script\s*\(/g },
1209
+ { name: "dynamic-require", pattern: /require\s*\(\s*(?:[^'"`\s)]|`[^`]*\$\{)/g }
1128
1210
  ];
1129
- var DYNAMIC_REQUIRE = /require\s*\(\s*(?:[^'"`\s)]|`[^`]*\$\{)/g;
1130
1211
  var critRemoteExecution = {
1131
1212
  id: "EG-CRIT-002",
1132
1213
  name: "Remote Code Execution",
@@ -1136,40 +1217,7 @@ var critRemoteExecution = {
1136
1217
  mitreAttackId: "T1059",
1137
1218
  enabled: true,
1138
1219
  detect(files, _manifest) {
1139
- const evidences = [];
1140
- for (const [filePath, content] of files) {
1141
- if (!filePath.endsWith(".js") && !filePath.endsWith(".ts")) {
1142
- continue;
1143
- }
1144
- const lines = content.split("\n");
1145
- for (const { name, pattern } of DANGEROUS_PATTERNS) {
1146
- pattern.lastIndex = 0;
1147
- let match2;
1148
- while ((match2 = pattern.exec(content)) !== null) {
1149
- const lineNumber = content.slice(0, match2.index).split("\n").length;
1150
- evidences.push({
1151
- filePath,
1152
- lineNumber,
1153
- lineContent: lines[lineNumber - 1]?.trim(),
1154
- matchedPattern: name,
1155
- snippet: match2[0]
1156
- });
1157
- }
1158
- }
1159
- DYNAMIC_REQUIRE.lastIndex = 0;
1160
- let match;
1161
- while ((match = DYNAMIC_REQUIRE.exec(content)) !== null) {
1162
- const lineNumber = content.slice(0, match.index).split("\n").length;
1163
- evidences.push({
1164
- filePath,
1165
- lineNumber,
1166
- lineContent: lines[lineNumber - 1]?.trim(),
1167
- matchedPattern: "dynamic-require",
1168
- snippet: match[0]
1169
- });
1170
- }
1171
- }
1172
- return evidences;
1220
+ return matchPatternsInFiles(files, PATTERNS);
1173
1221
  }
1174
1222
  };
1175
1223
 
@@ -1199,41 +1247,40 @@ var critCredentialAccess = {
1199
1247
  mitreAttackId: "T1552.004",
1200
1248
  enabled: true,
1201
1249
  detect(files, _manifest) {
1202
- const evidences = [];
1203
- for (const [filePath, content] of files) {
1204
- if (!filePath.endsWith(".js") && !filePath.endsWith(".ts")) {
1205
- continue;
1206
- }
1207
- const lines = content.split("\n");
1208
- for (const { name, pattern } of SENSITIVE_PATHS) {
1209
- pattern.lastIndex = 0;
1210
- let match;
1211
- while ((match = pattern.exec(content)) !== null) {
1212
- const startIndex = Math.max(0, match.index - 200);
1213
- const endIndex = Math.min(content.length, match.index + match[0].length + 200);
1214
- const context = content.slice(startIndex, endIndex);
1215
- if (FILE_READ_CONTEXT.test(context)) {
1216
- const lineNumber = content.slice(0, match.index).split("\n").length;
1217
- evidences.push({
1218
- filePath,
1219
- lineNumber,
1220
- lineContent: lines[lineNumber - 1]?.trim(),
1221
- matchedPattern: name,
1222
- snippet: match[0]
1223
- });
1224
- }
1225
- }
1250
+ return matchPatternsInFiles(
1251
+ files,
1252
+ SENSITIVE_PATHS,
1253
+ {},
1254
+ {
1255
+ validate: (value, content, matchIndex) => hasPatternInContext(content, matchIndex, value.length, FILE_READ_CONTEXT, 200)
1226
1256
  }
1227
- }
1228
- return evidences;
1257
+ );
1229
1258
  }
1230
1259
  };
1231
1260
 
1232
1261
  // src/rules/built-in/high-suspicious-network.ts
1233
- var HTTP_TO_IP = /(?:fetch|axios(?:\.(?:get|post|put|delete|request))?|https?\.(?:get|post|request)|XMLHttpRequest)\s*\([^)]*['"`]https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g;
1234
- var DYNAMIC_URL = /(?:fetch|axios|https?\.request)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*\w)/g;
1235
- var WEBSOCKET_TO_IP = /new\s+WebSocket\s*\(\s*['"`]wss?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g;
1236
- var UNUSUAL_PORTS = /['"`]https?:\/\/[^'"`:]+:(?!443|80|8080|3000|8443|5000)[0-9]{2,5}/g;
1262
+ var PATTERNS2 = [
1263
+ // HTTP requests to IP addresses instead of domains
1264
+ {
1265
+ name: "http-to-ip",
1266
+ pattern: /(?:fetch|axios(?:\.(?:get|post|put|delete|request))?|https?\.(?:get|post|request)|XMLHttpRequest)\s*\([^)]*['"`]https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g
1267
+ },
1268
+ // Dynamic URL construction (template literals or concatenation)
1269
+ {
1270
+ name: "dynamic-url",
1271
+ pattern: /(?:fetch|axios|https?\.request)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*\w)/g
1272
+ },
1273
+ // WebSocket connections to IP addresses
1274
+ {
1275
+ name: "websocket-to-ip",
1276
+ pattern: /new\s+WebSocket\s*\(\s*['"`]wss?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g
1277
+ },
1278
+ // Unusual ports
1279
+ {
1280
+ name: "unusual-port",
1281
+ pattern: /['"`]https?:\/\/[^'"`:]+:(?!443|80|8080|3000|8443|5000)[0-9]{2,5}/g
1282
+ }
1283
+ ];
1237
1284
  var highSuspiciousNetwork = {
1238
1285
  id: "EG-HIGH-002",
1239
1286
  name: "Suspicious Network Activity",
@@ -1243,34 +1290,7 @@ var highSuspiciousNetwork = {
1243
1290
  mitreAttackId: "T1071",
1244
1291
  enabled: true,
1245
1292
  detect(files, _manifest) {
1246
- const evidences = [];
1247
- for (const [filePath, content] of files) {
1248
- if (!filePath.endsWith(".js") && !filePath.endsWith(".ts")) {
1249
- continue;
1250
- }
1251
- const lines = content.split("\n");
1252
- const patterns = [
1253
- { pattern: HTTP_TO_IP, name: "http-to-ip" },
1254
- { pattern: DYNAMIC_URL, name: "dynamic-url" },
1255
- { pattern: WEBSOCKET_TO_IP, name: "websocket-to-ip" },
1256
- { pattern: UNUSUAL_PORTS, name: "unusual-port" }
1257
- ];
1258
- for (const { pattern, name } of patterns) {
1259
- pattern.lastIndex = 0;
1260
- let match;
1261
- while ((match = pattern.exec(content)) !== null) {
1262
- const lineNumber = content.slice(0, match.index).split("\n").length;
1263
- evidences.push({
1264
- filePath,
1265
- lineNumber,
1266
- lineContent: lines[lineNumber - 1]?.trim(),
1267
- matchedPattern: name,
1268
- snippet: match[0].slice(0, 100)
1269
- });
1270
- }
1271
- }
1272
- }
1273
- return evidences;
1293
+ return matchPatternsInFiles(files, PATTERNS2);
1274
1294
  }
1275
1295
  };
1276
1296
 
@@ -1496,23 +1516,6 @@ function calculateSecretEntropy(str) {
1496
1516
  }
1497
1517
  return entropy;
1498
1518
  }
1499
- function isInComment(content, matchIndex) {
1500
- const lineStart = content.lastIndexOf("\n", matchIndex) + 1;
1501
- const lineContent = content.slice(lineStart, matchIndex);
1502
- if (lineContent.includes("//")) {
1503
- return true;
1504
- }
1505
- const beforeMatch = content.slice(0, matchIndex);
1506
- const lastBlockStart = beforeMatch.lastIndexOf("/*");
1507
- const lastBlockEnd = beforeMatch.lastIndexOf("*/");
1508
- if (lastBlockStart > lastBlockEnd) {
1509
- return true;
1510
- }
1511
- return false;
1512
- }
1513
- function getLineNumber(content, index) {
1514
- return content.slice(0, index).split("\n").length;
1515
- }
1516
1519
  var highHardcodedSecret = {
1517
1520
  id: "EG-HIGH-006",
1518
1521
  name: "Hardcoded Secrets",
@@ -3016,12 +3019,14 @@ var PolicyEngine = class {
3016
3019
  };
3017
3020
 
3018
3021
  // src/index.ts
3019
- var VERSION = "0.5.1";
3022
+ var VERSION = "0.5.2";
3020
3023
  export {
3021
3024
  ALL_POPULAR_EXTENSIONS,
3025
+ CODE_EXTENSIONS,
3022
3026
  DETECTION_RULES,
3023
3027
  ExtensionGuardScanner,
3024
3028
  IDE_PATHS,
3029
+ JS_TS_EXTENSIONS,
3025
3030
  JsonReporter,
3026
3031
  MEGA_POPULAR_EXTENSIONS,
3027
3032
  MarkdownReporter,
@@ -3049,11 +3054,17 @@ export {
3049
3054
  getDefaultDatabasePath,
3050
3055
  getHash,
3051
3056
  getIDEExtensionPath,
3057
+ getLineContent,
3058
+ getLineNumber,
3052
3059
  getPopularityTier,
3053
3060
  getSupportedIDEs,
3061
+ hasExtension,
3062
+ hasPatternInContext,
3054
3063
  isAtLeastSeverity,
3055
3064
  isBundleOutputPath,
3065
+ isExcluded,
3056
3066
  isIDEInstalled,
3067
+ isInComment,
3057
3068
  isMegaPopular,
3058
3069
  isPopular,
3059
3070
  isTrustedExtension,
@@ -3061,6 +3072,8 @@ export {
3061
3072
  isVerifiedPublisher,
3062
3073
  loadHashDatabase,
3063
3074
  loadPolicyConfig,
3075
+ matchPatternsInFile,
3076
+ matchPatternsInFiles,
3064
3077
  readExtension,
3065
3078
  readExtensionsFromDirectory,
3066
3079
  registerBuiltInRules,