@wardnmesh/sdk-node 0.2.1
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/LICENSE +47 -0
- package/README.md +178 -0
- package/bin/setup.js +119 -0
- package/dist/agent-guard.d.ts +28 -0
- package/dist/agent-guard.js +206 -0
- package/dist/config/security-limits.d.ts +42 -0
- package/dist/config/security-limits.js +52 -0
- package/dist/detectors/base.d.ts +22 -0
- package/dist/detectors/base.js +51 -0
- package/dist/detectors/pattern.d.ts +7 -0
- package/dist/detectors/pattern.js +76 -0
- package/dist/detectors/semantic-detector.d.ts +16 -0
- package/dist/detectors/semantic-detector.js +96 -0
- package/dist/detectors/sequence.d.ts +11 -0
- package/dist/detectors/sequence.js +182 -0
- package/dist/detectors/state.d.ts +8 -0
- package/dist/detectors/state.js +86 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +31 -0
- package/dist/integrations/vercel.d.ts +7 -0
- package/dist/integrations/vercel.js +34 -0
- package/dist/middleware/express.d.ts +36 -0
- package/dist/middleware/express.js +54 -0
- package/dist/middleware/nextjs.d.ts +3 -0
- package/dist/middleware/nextjs.js +40 -0
- package/dist/state/session-manager.d.ts +13 -0
- package/dist/state/session-manager.js +22 -0
- package/dist/telemetry/reporter.d.ts +32 -0
- package/dist/telemetry/reporter.js +86 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.js +14 -0
- package/dist/update-checker.d.ts +21 -0
- package/dist/update-checker.js +218 -0
- package/dist/utils/logger.d.ts +40 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/rule-validator.d.ts +15 -0
- package/dist/utils/rule-validator.js +143 -0
- package/dist/utils/safe-regex.d.ts +53 -0
- package/dist/utils/safe-regex.js +220 -0
- package/dist/wardn.d.ts +67 -0
- package/dist/wardn.js +443 -0
- package/package.json +47 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Update Checker - Check for new SDK versions on GitHub Releases
|
|
4
|
+
* Caches results to avoid excessive API calls
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.checkForUpdates = checkForUpdates;
|
|
41
|
+
exports.notifyIfUpdateAvailable = notifyIfUpdateAvailable;
|
|
42
|
+
const logger_1 = require("./utils/logger");
|
|
43
|
+
const security_limits_1 = require("./config/security-limits");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const GITHUB_RELEASES_API = "https://api.github.com/repos/PCIRCLE-AI/wardnmesh/releases/latest";
|
|
48
|
+
const CACHE_DIR = path.join(os.homedir(), ".wardnmesh", "cache");
|
|
49
|
+
const VERSION_CACHE_FILE = path.join(CACHE_DIR, "version-check.json");
|
|
50
|
+
/**
|
|
51
|
+
* Get current SDK version from package.json
|
|
52
|
+
*/
|
|
53
|
+
function getCurrentVersion() {
|
|
54
|
+
try {
|
|
55
|
+
const packageJsonPath = path.join(__dirname, "../package.json");
|
|
56
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
57
|
+
return packageJson.version;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
logger_1.logger.warn("Failed to read SDK version from package.json");
|
|
61
|
+
return "0.0.0";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Load cached version check result
|
|
66
|
+
*/
|
|
67
|
+
function loadCachedVersionCheck() {
|
|
68
|
+
try {
|
|
69
|
+
if (fs.existsSync(VERSION_CACHE_FILE)) {
|
|
70
|
+
const content = fs.readFileSync(VERSION_CACHE_FILE, "utf-8");
|
|
71
|
+
const cache = JSON.parse(content);
|
|
72
|
+
// Check if cache is still valid (TTL: 6 hours)
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const age = now - cache.checkedAt;
|
|
75
|
+
if (age < security_limits_1.SECURITY_LIMITS.UPDATE_CHECK_CACHE_TTL_MS) {
|
|
76
|
+
return cache;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
logger_1.logger.warn("Failed to load version check cache:", error);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Save version check result to cache
|
|
87
|
+
*/
|
|
88
|
+
function saveVersionCheckCache(cache) {
|
|
89
|
+
try {
|
|
90
|
+
// Ensure cache directory exists
|
|
91
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
92
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
fs.writeFileSync(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger_1.logger.warn("Failed to save version check cache:", error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fetch latest version from GitHub Releases API
|
|
102
|
+
*/
|
|
103
|
+
async function fetchLatestVersion() {
|
|
104
|
+
try {
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timeoutId = setTimeout(() => controller.abort(), security_limits_1.SECURITY_LIMITS.FETCH_TIMEOUT_MS);
|
|
107
|
+
const response = await fetch(GITHUB_RELEASES_API, {
|
|
108
|
+
headers: {
|
|
109
|
+
Accept: "application/vnd.github+json",
|
|
110
|
+
"User-Agent": "@wardnmesh/sdk-node",
|
|
111
|
+
},
|
|
112
|
+
signal: controller.signal,
|
|
113
|
+
});
|
|
114
|
+
clearTimeout(timeoutId);
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
logger_1.logger.warn(`GitHub API returned status ${response.status}: ${response.statusText}`);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
return {
|
|
121
|
+
version: data.tag_name?.replace(/^v/, "") || "0.0.0",
|
|
122
|
+
url: data.html_url || "",
|
|
123
|
+
publishedAt: data.published_at || new Date().toISOString(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
logger_1.logger.warn("Failed to fetch latest version from GitHub:", error);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Compare two semantic versions
|
|
133
|
+
* Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
|
134
|
+
*/
|
|
135
|
+
function compareVersions(v1, v2) {
|
|
136
|
+
const parts1 = v1.split(".").map(Number);
|
|
137
|
+
const parts2 = v2.split(".").map(Number);
|
|
138
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
139
|
+
const num1 = parts1[i] || 0;
|
|
140
|
+
const num2 = parts2[i] || 0;
|
|
141
|
+
if (num1 > num2)
|
|
142
|
+
return 1;
|
|
143
|
+
if (num1 < num2)
|
|
144
|
+
return -1;
|
|
145
|
+
}
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check for SDK updates (uses cache if available)
|
|
150
|
+
* @param forceCheck - Force check even if cache is valid
|
|
151
|
+
* @returns Version information including update availability
|
|
152
|
+
*/
|
|
153
|
+
async function checkForUpdates(forceCheck = false) {
|
|
154
|
+
const currentVersion = getCurrentVersion();
|
|
155
|
+
// Try loading from cache first
|
|
156
|
+
if (!forceCheck) {
|
|
157
|
+
const cached = loadCachedVersionCheck();
|
|
158
|
+
if (cached) {
|
|
159
|
+
const updateAvailable = compareVersions(cached.latestVersion, currentVersion) > 0;
|
|
160
|
+
return {
|
|
161
|
+
currentVersion,
|
|
162
|
+
latestVersion: cached.latestVersion,
|
|
163
|
+
updateAvailable,
|
|
164
|
+
releaseUrl: cached.releaseUrl,
|
|
165
|
+
publishedAt: cached.publishedAt,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Fetch latest version from GitHub
|
|
170
|
+
const latest = await fetchLatestVersion();
|
|
171
|
+
if (!latest) {
|
|
172
|
+
// Fallback: no update available (API failure)
|
|
173
|
+
return {
|
|
174
|
+
currentVersion,
|
|
175
|
+
latestVersion: currentVersion,
|
|
176
|
+
updateAvailable: false,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// Save to cache
|
|
180
|
+
saveVersionCheckCache({
|
|
181
|
+
latestVersion: latest.version,
|
|
182
|
+
releaseUrl: latest.url,
|
|
183
|
+
publishedAt: latest.publishedAt,
|
|
184
|
+
checkedAt: Date.now(),
|
|
185
|
+
});
|
|
186
|
+
const updateAvailable = compareVersions(latest.version, currentVersion) > 0;
|
|
187
|
+
return {
|
|
188
|
+
currentVersion,
|
|
189
|
+
latestVersion: latest.version,
|
|
190
|
+
updateAvailable,
|
|
191
|
+
releaseUrl: latest.url,
|
|
192
|
+
publishedAt: latest.publishedAt,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Log update notification if update is available
|
|
197
|
+
*/
|
|
198
|
+
async function notifyIfUpdateAvailable() {
|
|
199
|
+
try {
|
|
200
|
+
const versionInfo = await checkForUpdates();
|
|
201
|
+
if (versionInfo.updateAvailable) {
|
|
202
|
+
logger_1.logger.info(`\n` +
|
|
203
|
+
`┌─────────────────────────────────────────────────────────────┐\n` +
|
|
204
|
+
`│ 🎉 New version of @wardnmesh/sdk-node is available! │\n` +
|
|
205
|
+
`│ │\n` +
|
|
206
|
+
`│ Current: ${versionInfo.currentVersion.padEnd(47)} │\n` +
|
|
207
|
+
`│ Latest: ${versionInfo.latestVersion.padEnd(47)} │\n` +
|
|
208
|
+
`│ │\n` +
|
|
209
|
+
`│ Run: npm install @wardnmesh/sdk-node@latest │\n` +
|
|
210
|
+
`│ Release: ${(versionInfo.releaseUrl || "").padEnd(42)} │\n` +
|
|
211
|
+
`└─────────────────────────────────────────────────────────────┘\n`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
// Silently fail - don't disrupt SDK functionality
|
|
216
|
+
logger_1.logger.warn("Failed to check for updates:", error);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility - Centralized logging for WardnMesh SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent logging with configurable log levels.
|
|
5
|
+
* Can be easily extended to integrate with external logging services.
|
|
6
|
+
*/
|
|
7
|
+
export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
|
|
8
|
+
declare class Logger {
|
|
9
|
+
private level;
|
|
10
|
+
private prefix;
|
|
11
|
+
constructor(prefix?: string, level?: LogLevel);
|
|
12
|
+
private shouldLog;
|
|
13
|
+
private formatMessage;
|
|
14
|
+
/**
|
|
15
|
+
* Set log level dynamically
|
|
16
|
+
*/
|
|
17
|
+
setLevel(level: LogLevel): void;
|
|
18
|
+
/**
|
|
19
|
+
* Get current log level
|
|
20
|
+
*/
|
|
21
|
+
getLevel(): LogLevel;
|
|
22
|
+
/**
|
|
23
|
+
* Debug level - detailed information for debugging
|
|
24
|
+
*/
|
|
25
|
+
debug(message: string, ...args: unknown[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* Info level - general operational information
|
|
28
|
+
*/
|
|
29
|
+
info(message: string, ...args: unknown[]): void;
|
|
30
|
+
/**
|
|
31
|
+
* Warn level - potentially harmful situations
|
|
32
|
+
*/
|
|
33
|
+
warn(message: string, ...args: unknown[]): void;
|
|
34
|
+
/**
|
|
35
|
+
* Error level - error events
|
|
36
|
+
*/
|
|
37
|
+
error(message: string, ...args: unknown[]): void;
|
|
38
|
+
}
|
|
39
|
+
export declare const logger: Logger;
|
|
40
|
+
export { Logger };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Logger Utility - Centralized logging for WardnMesh SDK
|
|
4
|
+
*
|
|
5
|
+
* Provides consistent logging with configurable log levels.
|
|
6
|
+
* Can be easily extended to integrate with external logging services.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.Logger = exports.logger = void 0;
|
|
10
|
+
const LOG_LEVELS = {
|
|
11
|
+
debug: 0,
|
|
12
|
+
info: 1,
|
|
13
|
+
warn: 2,
|
|
14
|
+
error: 3,
|
|
15
|
+
silent: 4,
|
|
16
|
+
};
|
|
17
|
+
class Logger {
|
|
18
|
+
constructor(prefix = "[Wardn]", level) {
|
|
19
|
+
this.prefix = prefix;
|
|
20
|
+
// Default to 'info' in production, 'debug' in development
|
|
21
|
+
this.level =
|
|
22
|
+
level ||
|
|
23
|
+
process.env.WARDN_LOG_LEVEL ||
|
|
24
|
+
(process.env.NODE_ENV === "development" ? "debug" : "info");
|
|
25
|
+
}
|
|
26
|
+
shouldLog(level) {
|
|
27
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
28
|
+
}
|
|
29
|
+
formatMessage(message) {
|
|
30
|
+
return `${this.prefix} ${message}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Set log level dynamically
|
|
34
|
+
*/
|
|
35
|
+
setLevel(level) {
|
|
36
|
+
this.level = level;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get current log level
|
|
40
|
+
*/
|
|
41
|
+
getLevel() {
|
|
42
|
+
return this.level;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Debug level - detailed information for debugging
|
|
46
|
+
*/
|
|
47
|
+
debug(message, ...args) {
|
|
48
|
+
if (this.shouldLog("debug")) {
|
|
49
|
+
console.debug(this.formatMessage(message), ...args);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Info level - general operational information
|
|
54
|
+
*/
|
|
55
|
+
info(message, ...args) {
|
|
56
|
+
if (this.shouldLog("info")) {
|
|
57
|
+
console.log(this.formatMessage(message), ...args);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Warn level - potentially harmful situations
|
|
62
|
+
*/
|
|
63
|
+
warn(message, ...args) {
|
|
64
|
+
if (this.shouldLog("warn")) {
|
|
65
|
+
console.warn(this.formatMessage(message), ...args);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Error level - error events
|
|
70
|
+
*/
|
|
71
|
+
error(message, ...args) {
|
|
72
|
+
if (this.shouldLog("error")) {
|
|
73
|
+
console.error(this.formatMessage(message), ...args);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.Logger = Logger;
|
|
78
|
+
// Singleton instance for the SDK
|
|
79
|
+
exports.logger = new Logger("[Wardn]");
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Validator - Validates remote rules against expected schema
|
|
3
|
+
*/
|
|
4
|
+
export interface ValidationResult {
|
|
5
|
+
valid: boolean;
|
|
6
|
+
errors: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Validate an array of rules from remote source
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateRules(rules: unknown): ValidationResult;
|
|
12
|
+
/**
|
|
13
|
+
* Validate remote rules URL
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateRemoteUrl(url: string): ValidationResult;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Rule Validator - Validates remote rules against expected schema
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateRules = validateRules;
|
|
7
|
+
exports.validateRemoteUrl = validateRemoteUrl;
|
|
8
|
+
const VALID_DETECTOR_TYPES = [
|
|
9
|
+
"sequence",
|
|
10
|
+
"state",
|
|
11
|
+
"pattern",
|
|
12
|
+
"content_analysis",
|
|
13
|
+
"semantic",
|
|
14
|
+
];
|
|
15
|
+
const VALID_SEVERITIES = ["critical", "major", "minor"];
|
|
16
|
+
const VALID_CATEGORIES = [
|
|
17
|
+
"workflow",
|
|
18
|
+
"quality",
|
|
19
|
+
"safety",
|
|
20
|
+
"network_boundary",
|
|
21
|
+
"supply_chain",
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Validate a single rule object
|
|
25
|
+
*/
|
|
26
|
+
function validateRule(rule, index) {
|
|
27
|
+
const errors = [];
|
|
28
|
+
const prefix = `Rule[${index}]`;
|
|
29
|
+
if (!rule || typeof rule !== "object") {
|
|
30
|
+
return [`${prefix}: Must be an object`];
|
|
31
|
+
}
|
|
32
|
+
const r = rule;
|
|
33
|
+
// Required string fields
|
|
34
|
+
if (typeof r.id !== "string" || !r.id) {
|
|
35
|
+
errors.push(`${prefix}: Missing or invalid 'id' field`);
|
|
36
|
+
}
|
|
37
|
+
if (typeof r.name !== "string" || !r.name) {
|
|
38
|
+
errors.push(`${prefix}: Missing or invalid 'name' field`);
|
|
39
|
+
}
|
|
40
|
+
if (typeof r.description !== "string") {
|
|
41
|
+
errors.push(`${prefix}: Missing or invalid 'description' field`);
|
|
42
|
+
}
|
|
43
|
+
// Category validation
|
|
44
|
+
if (!VALID_CATEGORIES.includes(r.category)) {
|
|
45
|
+
errors.push(`${prefix}: Invalid category '${r.category}'. Must be one of: ${VALID_CATEGORIES.join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
// Severity validation
|
|
48
|
+
if (!VALID_SEVERITIES.includes(r.severity)) {
|
|
49
|
+
errors.push(`${prefix}: Invalid severity '${r.severity}'. Must be one of: ${VALID_SEVERITIES.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
// Detector validation
|
|
52
|
+
if (!r.detector || typeof r.detector !== "object") {
|
|
53
|
+
errors.push(`${prefix}: Missing or invalid 'detector' field`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const detector = r.detector;
|
|
57
|
+
if (!VALID_DETECTOR_TYPES.includes(detector.type)) {
|
|
58
|
+
errors.push(`${prefix}: Invalid detector type '${detector.type}'. Must be one of: ${VALID_DETECTOR_TYPES.join(", ")}`);
|
|
59
|
+
}
|
|
60
|
+
if (!detector.config || typeof detector.config !== "object") {
|
|
61
|
+
errors.push(`${prefix}: Missing or invalid 'detector.config' field`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Escalation validation
|
|
65
|
+
if (!r.escalation || typeof r.escalation !== "object") {
|
|
66
|
+
errors.push(`${prefix}: Missing or invalid 'escalation' field`);
|
|
67
|
+
}
|
|
68
|
+
return errors;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate an array of rules from remote source
|
|
72
|
+
*/
|
|
73
|
+
function validateRules(rules) {
|
|
74
|
+
const errors = [];
|
|
75
|
+
if (!Array.isArray(rules)) {
|
|
76
|
+
return {
|
|
77
|
+
valid: false,
|
|
78
|
+
errors: ["Rules must be an array"],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (rules.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
valid: false,
|
|
84
|
+
errors: ["Rules array cannot be empty"],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Limit maximum rules to prevent DoS
|
|
88
|
+
const MAX_RULES = 1000;
|
|
89
|
+
if (rules.length > MAX_RULES) {
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
errors: [`Too many rules (${rules.length}). Maximum allowed: ${MAX_RULES}`],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Validate each rule
|
|
96
|
+
for (let i = 0; i < rules.length; i++) {
|
|
97
|
+
const ruleErrors = validateRule(rules[i], i);
|
|
98
|
+
errors.push(...ruleErrors);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
valid: errors.length === 0,
|
|
102
|
+
errors,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Validate remote rules URL
|
|
107
|
+
*/
|
|
108
|
+
function validateRemoteUrl(url) {
|
|
109
|
+
const errors = [];
|
|
110
|
+
try {
|
|
111
|
+
const parsed = new URL(url);
|
|
112
|
+
// Enforce HTTPS in production
|
|
113
|
+
if (process.env.NODE_ENV === "production" &&
|
|
114
|
+
parsed.protocol !== "https:") {
|
|
115
|
+
errors.push("Remote rules URL must use HTTPS in production");
|
|
116
|
+
}
|
|
117
|
+
// Block localhost in production
|
|
118
|
+
if (process.env.NODE_ENV === "production" &&
|
|
119
|
+
(parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1")) {
|
|
120
|
+
errors.push("Remote rules URL cannot use localhost in production");
|
|
121
|
+
}
|
|
122
|
+
// Block private IP ranges (basic SSRF protection)
|
|
123
|
+
const privatePatterns = [
|
|
124
|
+
/^10\./,
|
|
125
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
126
|
+
/^192\.168\./,
|
|
127
|
+
/^169\.254\./, // Link-local
|
|
128
|
+
];
|
|
129
|
+
for (const pattern of privatePatterns) {
|
|
130
|
+
if (pattern.test(parsed.hostname)) {
|
|
131
|
+
errors.push("Remote rules URL cannot use private IP addresses");
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
errors.push("Invalid URL format");
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
valid: errors.length === 0,
|
|
141
|
+
errors,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe Regex Utilities - ReDoS Prevention
|
|
3
|
+
*
|
|
4
|
+
* Provides regex validation and safe execution with timeout protection.
|
|
5
|
+
*/
|
|
6
|
+
export interface RegexValidationResult {
|
|
7
|
+
valid: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PCREConversionResult {
|
|
11
|
+
pattern: string;
|
|
12
|
+
flags: string;
|
|
13
|
+
converted: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Convert PCRE inline modifiers to JavaScript flags
|
|
18
|
+
* Handles: (?i), (?m), (?s), (?x), and combinations like (?im)
|
|
19
|
+
*/
|
|
20
|
+
export declare function convertPCREModifiers(pattern: string): PCREConversionResult;
|
|
21
|
+
/**
|
|
22
|
+
* Validate a regex pattern for potential ReDoS vulnerabilities
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateRegexPattern(pattern: string): RegexValidationResult;
|
|
25
|
+
/**
|
|
26
|
+
* Pattern error metrics for monitoring
|
|
27
|
+
*/
|
|
28
|
+
export interface PatternErrorMetrics {
|
|
29
|
+
pcreConversionFailures: number;
|
|
30
|
+
validationFailures: number;
|
|
31
|
+
regexCompilationFailures: number;
|
|
32
|
+
totalPatternsProcessed: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get current pattern error metrics
|
|
36
|
+
*/
|
|
37
|
+
export declare function getPatternErrorMetrics(): Readonly<PatternErrorMetrics>;
|
|
38
|
+
/**
|
|
39
|
+
* Reset pattern error metrics (useful for testing)
|
|
40
|
+
*/
|
|
41
|
+
export declare function resetPatternErrorMetrics(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Get a cached regex instance or create a new one
|
|
44
|
+
*/
|
|
45
|
+
export declare function getCachedRegex(pattern: string, flags?: string): RegExp | null;
|
|
46
|
+
/**
|
|
47
|
+
* Execute regex with content length limit
|
|
48
|
+
*/
|
|
49
|
+
export declare function safeRegexExec(regex: RegExp, content: string, maxLength?: number): RegExpExecArray | null;
|
|
50
|
+
/**
|
|
51
|
+
* Safe regex test with content length limit
|
|
52
|
+
*/
|
|
53
|
+
export declare function safeRegexTest(regex: RegExp, content: string, maxLength?: number): boolean;
|