agentic-qe 3.2.2 → 3.2.3
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/.claude/helpers/github-safe.js +10 -10
- package/package.json +2 -1
- package/v3/dist/cli/bundle.js +314 -14
- package/v3/dist/coordination/cross-domain-router.d.ts.map +1 -1
- package/v3/dist/coordination/cross-domain-router.js +2 -0
- package/v3/dist/coordination/cross-domain-router.js.map +1 -1
- package/v3/dist/domains/security-compliance/services/semgrep-integration.d.ts.map +1 -1
- package/v3/dist/domains/security-compliance/services/semgrep-integration.js +16 -12
- package/v3/dist/domains/security-compliance/services/semgrep-integration.js.map +1 -1
- package/v3/dist/domains/test-execution/services/e2e-runner.d.ts +1 -0
- package/v3/dist/domains/test-execution/services/e2e-runner.d.ts.map +1 -1
- package/v3/dist/domains/test-execution/services/e2e-runner.js +9 -12
- package/v3/dist/domains/test-execution/services/e2e-runner.js.map +1 -1
- package/v3/dist/integrations/agentic-flow/reasoning-bank/experience-replay.d.ts +9 -3
- package/v3/dist/integrations/agentic-flow/reasoning-bank/experience-replay.d.ts.map +1 -1
- package/v3/dist/integrations/agentic-flow/reasoning-bank/experience-replay.js +103 -26
- package/v3/dist/integrations/agentic-flow/reasoning-bank/experience-replay.js.map +1 -1
- package/v3/dist/integrations/embeddings/base/EmbeddingGenerator.d.ts.map +1 -1
- package/v3/dist/integrations/embeddings/base/EmbeddingGenerator.js +1 -0
- package/v3/dist/integrations/embeddings/base/EmbeddingGenerator.js.map +1 -1
- package/v3/dist/integrations/embeddings/base/types.d.ts +1 -1
- package/v3/dist/integrations/embeddings/base/types.d.ts.map +1 -1
- package/v3/dist/integrations/embeddings/cache/EmbeddingCache.d.ts.map +1 -1
- package/v3/dist/integrations/embeddings/cache/EmbeddingCache.js +1 -0
- package/v3/dist/integrations/embeddings/cache/EmbeddingCache.js.map +1 -1
- package/v3/dist/learning/token-tracker.d.ts.map +1 -1
- package/v3/dist/learning/token-tracker.js +5 -2
- package/v3/dist/learning/token-tracker.js.map +1 -1
- package/v3/dist/mcp/bundle.js +383 -25
- package/v3/dist/mcp/tools/contract-testing/validate.d.ts +1 -1
- package/v3/dist/mcp/tools/contract-testing/validate.d.ts.map +1 -1
- package/v3/dist/mcp/tools/contract-testing/validate.js +66 -0
- package/v3/dist/mcp/tools/contract-testing/validate.js.map +1 -1
- package/v3/dist/shared/utils/safe-expression-evaluator.d.ts +61 -0
- package/v3/dist/shared/utils/safe-expression-evaluator.d.ts.map +1 -0
- package/v3/dist/shared/utils/safe-expression-evaluator.js +365 -0
- package/v3/dist/shared/utils/safe-expression-evaluator.js.map +1 -0
- package/v3/dist/test-scheduling/executors/vitest-executor.d.ts.map +1 -1
- package/v3/dist/test-scheduling/executors/vitest-executor.js +7 -2
- package/v3/dist/test-scheduling/executors/vitest-executor.js.map +1 -1
- package/v3/dist/workflows/browser/workflow-loader.d.ts +1 -0
- package/v3/dist/workflows/browser/workflow-loader.d.ts.map +1 -1
- package/v3/dist/workflows/browser/workflow-loader.js +11 -16
- package/v3/dist/workflows/browser/workflow-loader.js.map +1 -1
- package/v3/package.json +1 -1
package/v3/dist/mcp/bundle.js
CHANGED
|
@@ -21320,6 +21320,310 @@ var TestPrioritizerService = class {
|
|
|
21320
21320
|
}
|
|
21321
21321
|
};
|
|
21322
21322
|
|
|
21323
|
+
// src/shared/utils/safe-expression-evaluator.ts
|
|
21324
|
+
var PRECEDENCE = {
|
|
21325
|
+
"||": 1,
|
|
21326
|
+
"&&": 2,
|
|
21327
|
+
"===": 3,
|
|
21328
|
+
"!==": 3,
|
|
21329
|
+
"==": 3,
|
|
21330
|
+
"!=": 3,
|
|
21331
|
+
"<": 4,
|
|
21332
|
+
">": 4,
|
|
21333
|
+
"<=": 4,
|
|
21334
|
+
">=": 4,
|
|
21335
|
+
"+": 5,
|
|
21336
|
+
"-": 5,
|
|
21337
|
+
"*": 6,
|
|
21338
|
+
"/": 6,
|
|
21339
|
+
"%": 6
|
|
21340
|
+
};
|
|
21341
|
+
var BINARY_OPERATORS = new Set(Object.keys(PRECEDENCE));
|
|
21342
|
+
var UNARY_OPERATORS = /* @__PURE__ */ new Set(["!", "-", "+"]);
|
|
21343
|
+
function tokenize(expr) {
|
|
21344
|
+
const tokens = [];
|
|
21345
|
+
let i = 0;
|
|
21346
|
+
while (i < expr.length) {
|
|
21347
|
+
const char = expr[i];
|
|
21348
|
+
if (/\s/.test(char)) {
|
|
21349
|
+
i++;
|
|
21350
|
+
continue;
|
|
21351
|
+
}
|
|
21352
|
+
if (/\d/.test(char) || char === "." && /\d/.test(expr[i + 1])) {
|
|
21353
|
+
let num = "";
|
|
21354
|
+
while (i < expr.length && /[\d.]/.test(expr[i])) {
|
|
21355
|
+
num += expr[i++];
|
|
21356
|
+
}
|
|
21357
|
+
tokens.push({ type: "NUMBER", value: parseFloat(num), raw: num });
|
|
21358
|
+
continue;
|
|
21359
|
+
}
|
|
21360
|
+
if (char === '"' || char === "'") {
|
|
21361
|
+
const quote = char;
|
|
21362
|
+
let str = "";
|
|
21363
|
+
i++;
|
|
21364
|
+
while (i < expr.length && expr[i] !== quote) {
|
|
21365
|
+
if (expr[i] === "\\" && i + 1 < expr.length) {
|
|
21366
|
+
i++;
|
|
21367
|
+
const escaped = expr[i];
|
|
21368
|
+
switch (escaped) {
|
|
21369
|
+
case "n":
|
|
21370
|
+
str += "\n";
|
|
21371
|
+
break;
|
|
21372
|
+
case "t":
|
|
21373
|
+
str += " ";
|
|
21374
|
+
break;
|
|
21375
|
+
case "r":
|
|
21376
|
+
str += "\r";
|
|
21377
|
+
break;
|
|
21378
|
+
default:
|
|
21379
|
+
str += escaped;
|
|
21380
|
+
}
|
|
21381
|
+
} else {
|
|
21382
|
+
str += expr[i];
|
|
21383
|
+
}
|
|
21384
|
+
i++;
|
|
21385
|
+
}
|
|
21386
|
+
i++;
|
|
21387
|
+
tokens.push({ type: "STRING", value: str, raw: `${quote}${str}${quote}` });
|
|
21388
|
+
continue;
|
|
21389
|
+
}
|
|
21390
|
+
if (/[a-zA-Z_$]/.test(char)) {
|
|
21391
|
+
let ident = "";
|
|
21392
|
+
while (i < expr.length && /[a-zA-Z0-9_$]/.test(expr[i])) {
|
|
21393
|
+
ident += expr[i++];
|
|
21394
|
+
}
|
|
21395
|
+
if (ident === "true") {
|
|
21396
|
+
tokens.push({ type: "BOOLEAN", value: true, raw: ident });
|
|
21397
|
+
} else if (ident === "false") {
|
|
21398
|
+
tokens.push({ type: "BOOLEAN", value: false, raw: ident });
|
|
21399
|
+
} else if (ident === "null") {
|
|
21400
|
+
tokens.push({ type: "NULL", value: null, raw: ident });
|
|
21401
|
+
} else if (ident === "undefined") {
|
|
21402
|
+
tokens.push({ type: "UNDEFINED", value: void 0, raw: ident });
|
|
21403
|
+
} else {
|
|
21404
|
+
tokens.push({ type: "IDENTIFIER", value: ident, raw: ident });
|
|
21405
|
+
}
|
|
21406
|
+
continue;
|
|
21407
|
+
}
|
|
21408
|
+
const twoChar = expr.slice(i, i + 2);
|
|
21409
|
+
const threeChar = expr.slice(i, i + 3);
|
|
21410
|
+
if (threeChar === "===" || threeChar === "!==") {
|
|
21411
|
+
tokens.push({ type: "OPERATOR", value: threeChar, raw: threeChar });
|
|
21412
|
+
i += 3;
|
|
21413
|
+
continue;
|
|
21414
|
+
}
|
|
21415
|
+
if (twoChar === "==" || twoChar === "!=" || twoChar === "<=" || twoChar === ">=" || twoChar === "&&" || twoChar === "||") {
|
|
21416
|
+
tokens.push({ type: "OPERATOR", value: twoChar, raw: twoChar });
|
|
21417
|
+
i += 2;
|
|
21418
|
+
continue;
|
|
21419
|
+
}
|
|
21420
|
+
if (char === "(") {
|
|
21421
|
+
tokens.push({ type: "LPAREN", value: "(", raw: "(" });
|
|
21422
|
+
i++;
|
|
21423
|
+
continue;
|
|
21424
|
+
}
|
|
21425
|
+
if (char === ")") {
|
|
21426
|
+
tokens.push({ type: "RPAREN", value: ")", raw: ")" });
|
|
21427
|
+
i++;
|
|
21428
|
+
continue;
|
|
21429
|
+
}
|
|
21430
|
+
if (char === ".") {
|
|
21431
|
+
tokens.push({ type: "DOT", value: ".", raw: "." });
|
|
21432
|
+
i++;
|
|
21433
|
+
continue;
|
|
21434
|
+
}
|
|
21435
|
+
if ("+-*/%<>!".includes(char)) {
|
|
21436
|
+
tokens.push({ type: "OPERATOR", value: char, raw: char });
|
|
21437
|
+
i++;
|
|
21438
|
+
continue;
|
|
21439
|
+
}
|
|
21440
|
+
throw new Error(`Unexpected character at position ${i}: ${char}`);
|
|
21441
|
+
}
|
|
21442
|
+
tokens.push({ type: "EOF", value: "", raw: "" });
|
|
21443
|
+
return tokens;
|
|
21444
|
+
}
|
|
21445
|
+
var Parser = class {
|
|
21446
|
+
tokens;
|
|
21447
|
+
pos = 0;
|
|
21448
|
+
context;
|
|
21449
|
+
constructor(tokens, context) {
|
|
21450
|
+
this.tokens = tokens;
|
|
21451
|
+
this.context = context;
|
|
21452
|
+
}
|
|
21453
|
+
current() {
|
|
21454
|
+
return this.tokens[this.pos];
|
|
21455
|
+
}
|
|
21456
|
+
advance() {
|
|
21457
|
+
return this.tokens[this.pos++];
|
|
21458
|
+
}
|
|
21459
|
+
expect(type) {
|
|
21460
|
+
const token = this.current();
|
|
21461
|
+
if (token.type !== type) {
|
|
21462
|
+
throw new Error(`Expected ${type}, got ${token.type}`);
|
|
21463
|
+
}
|
|
21464
|
+
return this.advance();
|
|
21465
|
+
}
|
|
21466
|
+
parse() {
|
|
21467
|
+
const result = this.parseExpression(0);
|
|
21468
|
+
if (this.current().type !== "EOF") {
|
|
21469
|
+
throw new Error(`Unexpected token: ${this.current().raw}`);
|
|
21470
|
+
}
|
|
21471
|
+
return result;
|
|
21472
|
+
}
|
|
21473
|
+
parseExpression(minPrecedence) {
|
|
21474
|
+
let left = this.parseUnary();
|
|
21475
|
+
while (true) {
|
|
21476
|
+
const token = this.current();
|
|
21477
|
+
if (token.type !== "OPERATOR" || !BINARY_OPERATORS.has(token.value)) {
|
|
21478
|
+
break;
|
|
21479
|
+
}
|
|
21480
|
+
const precedence = PRECEDENCE[token.value];
|
|
21481
|
+
if (precedence < minPrecedence) {
|
|
21482
|
+
break;
|
|
21483
|
+
}
|
|
21484
|
+
const operator = this.advance().value;
|
|
21485
|
+
const right = this.parseExpression(precedence + 1);
|
|
21486
|
+
left = this.applyBinaryOperator(operator, left, right);
|
|
21487
|
+
}
|
|
21488
|
+
return left;
|
|
21489
|
+
}
|
|
21490
|
+
parseUnary() {
|
|
21491
|
+
const token = this.current();
|
|
21492
|
+
if (token.type === "OPERATOR" && UNARY_OPERATORS.has(token.value)) {
|
|
21493
|
+
const operator = this.advance().value;
|
|
21494
|
+
const operand = this.parseUnary();
|
|
21495
|
+
return this.applyUnaryOperator(operator, operand);
|
|
21496
|
+
}
|
|
21497
|
+
return this.parsePrimary();
|
|
21498
|
+
}
|
|
21499
|
+
parsePrimary() {
|
|
21500
|
+
const token = this.current();
|
|
21501
|
+
switch (token.type) {
|
|
21502
|
+
case "NUMBER":
|
|
21503
|
+
case "STRING":
|
|
21504
|
+
case "BOOLEAN":
|
|
21505
|
+
case "NULL":
|
|
21506
|
+
case "UNDEFINED":
|
|
21507
|
+
this.advance();
|
|
21508
|
+
return token.value;
|
|
21509
|
+
case "IDENTIFIER":
|
|
21510
|
+
return this.parseIdentifier();
|
|
21511
|
+
case "LPAREN":
|
|
21512
|
+
this.advance();
|
|
21513
|
+
const result = this.parseExpression(0);
|
|
21514
|
+
this.expect("RPAREN");
|
|
21515
|
+
return result;
|
|
21516
|
+
default:
|
|
21517
|
+
throw new Error(`Unexpected token: ${token.raw}`);
|
|
21518
|
+
}
|
|
21519
|
+
}
|
|
21520
|
+
parseIdentifier() {
|
|
21521
|
+
let value = this.context;
|
|
21522
|
+
let name = this.advance().value;
|
|
21523
|
+
if (typeof value === "object" && value !== null && name in value) {
|
|
21524
|
+
value = value[name];
|
|
21525
|
+
} else {
|
|
21526
|
+
value = void 0;
|
|
21527
|
+
}
|
|
21528
|
+
while (this.current().type === "DOT") {
|
|
21529
|
+
this.advance();
|
|
21530
|
+
const prop = this.expect("IDENTIFIER").value;
|
|
21531
|
+
if (value !== null && value !== void 0 && typeof value === "object") {
|
|
21532
|
+
value = value[prop];
|
|
21533
|
+
} else {
|
|
21534
|
+
value = void 0;
|
|
21535
|
+
}
|
|
21536
|
+
}
|
|
21537
|
+
return value;
|
|
21538
|
+
}
|
|
21539
|
+
applyBinaryOperator(op, left, right) {
|
|
21540
|
+
switch (op) {
|
|
21541
|
+
case "===":
|
|
21542
|
+
return left === right;
|
|
21543
|
+
case "!==":
|
|
21544
|
+
return left !== right;
|
|
21545
|
+
case "==":
|
|
21546
|
+
return left == right;
|
|
21547
|
+
case "!=":
|
|
21548
|
+
return left != right;
|
|
21549
|
+
case "<":
|
|
21550
|
+
return left < right;
|
|
21551
|
+
case ">":
|
|
21552
|
+
return left > right;
|
|
21553
|
+
case "<=":
|
|
21554
|
+
return left <= right;
|
|
21555
|
+
case ">=":
|
|
21556
|
+
return left >= right;
|
|
21557
|
+
case "&&":
|
|
21558
|
+
return left && right;
|
|
21559
|
+
case "||":
|
|
21560
|
+
return left || right;
|
|
21561
|
+
case "+":
|
|
21562
|
+
return left + right;
|
|
21563
|
+
case "-":
|
|
21564
|
+
return left - right;
|
|
21565
|
+
case "*":
|
|
21566
|
+
return left * right;
|
|
21567
|
+
case "/":
|
|
21568
|
+
return left / right;
|
|
21569
|
+
case "%":
|
|
21570
|
+
return left % right;
|
|
21571
|
+
default:
|
|
21572
|
+
throw new Error(`Unknown operator: ${op}`);
|
|
21573
|
+
}
|
|
21574
|
+
}
|
|
21575
|
+
applyUnaryOperator(op, operand) {
|
|
21576
|
+
switch (op) {
|
|
21577
|
+
case "!":
|
|
21578
|
+
return !operand;
|
|
21579
|
+
case "-":
|
|
21580
|
+
return -operand;
|
|
21581
|
+
case "+":
|
|
21582
|
+
return +operand;
|
|
21583
|
+
default:
|
|
21584
|
+
throw new Error(`Unknown unary operator: ${op}`);
|
|
21585
|
+
}
|
|
21586
|
+
}
|
|
21587
|
+
};
|
|
21588
|
+
function safeEvaluate(expression, context = {}) {
|
|
21589
|
+
if (!expression || typeof expression !== "string") {
|
|
21590
|
+
throw new Error("Expression must be a non-empty string");
|
|
21591
|
+
}
|
|
21592
|
+
const dangerousPatterns = [
|
|
21593
|
+
/\beval\b/i,
|
|
21594
|
+
/\bFunction\b/,
|
|
21595
|
+
/\bconstructor\b/,
|
|
21596
|
+
/\b__proto__\b/,
|
|
21597
|
+
/\bprototype\b/,
|
|
21598
|
+
/\bimport\b/,
|
|
21599
|
+
/\brequire\b/,
|
|
21600
|
+
/\bprocess\b/,
|
|
21601
|
+
/\bglobal\b/,
|
|
21602
|
+
/\bwindow\b/,
|
|
21603
|
+
/\bdocument\b/,
|
|
21604
|
+
/\[\s*['"`]/,
|
|
21605
|
+
// Computed property access with string
|
|
21606
|
+
/\[.*\]/
|
|
21607
|
+
// Any computed property access
|
|
21608
|
+
];
|
|
21609
|
+
for (const pattern of dangerousPatterns) {
|
|
21610
|
+
if (pattern.test(expression)) {
|
|
21611
|
+
throw new Error(`Expression contains potentially dangerous pattern: ${expression}`);
|
|
21612
|
+
}
|
|
21613
|
+
}
|
|
21614
|
+
const tokens = tokenize(expression.trim());
|
|
21615
|
+
const parser = new Parser(tokens, context);
|
|
21616
|
+
return parser.parse();
|
|
21617
|
+
}
|
|
21618
|
+
function safeEvaluateBoolean(expression, context = {}, defaultValue = false) {
|
|
21619
|
+
try {
|
|
21620
|
+
return Boolean(safeEvaluate(expression, context));
|
|
21621
|
+
} catch (error) {
|
|
21622
|
+
console.warn(`[SafeEvaluator] Failed to evaluate expression: ${expression}`, error);
|
|
21623
|
+
return defaultValue;
|
|
21624
|
+
}
|
|
21625
|
+
}
|
|
21626
|
+
|
|
21323
21627
|
// src/integrations/browser/types.ts
|
|
21324
21628
|
var BrowserError = class _BrowserError extends Error {
|
|
21325
21629
|
constructor(message, code, tool, cause) {
|
|
@@ -24688,18 +24992,13 @@ var E2ETestRunnerService = class {
|
|
|
24688
24992
|
}
|
|
24689
24993
|
/**
|
|
24690
24994
|
* Evaluate conditional expression
|
|
24995
|
+
* Uses safe expression evaluator to prevent code injection (CVE fix)
|
|
24691
24996
|
*/
|
|
24692
24997
|
evaluateCondition(condition, context) {
|
|
24693
|
-
|
|
24694
|
-
|
|
24695
|
-
|
|
24696
|
-
|
|
24697
|
-
};
|
|
24698
|
-
const fn = new Function(...Object.keys(evalContext), `return ${condition}`);
|
|
24699
|
-
return Boolean(fn(...Object.values(evalContext)));
|
|
24700
|
-
} catch {
|
|
24701
|
-
return true;
|
|
24702
|
-
}
|
|
24998
|
+
const evalContext = {
|
|
24999
|
+
...context.variables
|
|
25000
|
+
};
|
|
25001
|
+
return safeEvaluateBoolean(condition, evalContext, true);
|
|
24703
25002
|
}
|
|
24704
25003
|
/**
|
|
24705
25004
|
* Get browser context options from test case
|
|
@@ -75036,7 +75335,7 @@ var CrossDomainEventRouter = class {
|
|
|
75036
75335
|
*/
|
|
75037
75336
|
matchEventType(eventType, pattern) {
|
|
75038
75337
|
if (pattern === "*") return true;
|
|
75039
|
-
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
|
|
75338
|
+
const regexPattern = pattern.replace(/\\/g, "\\\\").replace(/\./g, "\\.").replace(/\*/g, ".*");
|
|
75040
75339
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
75041
75340
|
return regex.test(eventType);
|
|
75042
75341
|
}
|
|
@@ -77415,6 +77714,9 @@ init_unified_memory();
|
|
|
77415
77714
|
init_real_embeddings();
|
|
77416
77715
|
import { v4 as uuidv455 } from "uuid";
|
|
77417
77716
|
|
|
77717
|
+
// src/integrations/embeddings/index/HNSWIndex.ts
|
|
77718
|
+
import { HierarchicalNSW } from "hnswlib-node";
|
|
77719
|
+
|
|
77418
77720
|
// src/integrations/agentic-flow/reasoning-bank/pattern-evolution.ts
|
|
77419
77721
|
init_unified_memory();
|
|
77420
77722
|
init_real_embeddings();
|
|
@@ -81218,6 +81520,7 @@ function createPatternStore(memory, config) {
|
|
|
81218
81520
|
}
|
|
81219
81521
|
|
|
81220
81522
|
// src/learning/token-tracker.ts
|
|
81523
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
81221
81524
|
var DEFAULT_COST_CONFIG = {
|
|
81222
81525
|
costPerInputToken: 3e-3 / 1e3,
|
|
81223
81526
|
// $3 per 1M input tokens
|
|
@@ -81247,7 +81550,7 @@ var TokenMetricsCollectorImpl = class {
|
|
|
81247
81550
|
autoSaveTimer = null;
|
|
81248
81551
|
isDirty = false;
|
|
81249
81552
|
constructor() {
|
|
81250
|
-
this.sessionId = `session-${Date.now()}-${
|
|
81553
|
+
this.sessionId = `session-${Date.now()}-${randomUUID2().substring(0, 8)}`;
|
|
81251
81554
|
this.sessionStartTime = Date.now();
|
|
81252
81555
|
this.costConfig = DEFAULT_COST_CONFIG;
|
|
81253
81556
|
}
|
|
@@ -81537,7 +81840,7 @@ var TokenMetricsCollectorImpl = class {
|
|
|
81537
81840
|
this.taskMetrics = [];
|
|
81538
81841
|
this.agentMetrics.clear();
|
|
81539
81842
|
this.domainMetrics.clear();
|
|
81540
|
-
this.sessionId = `session-${Date.now()}-${
|
|
81843
|
+
this.sessionId = `session-${Date.now()}-${randomUUID2().substring(0, 8)}`;
|
|
81541
81844
|
this.sessionStartTime = Date.now();
|
|
81542
81845
|
this.cacheHits = 0;
|
|
81543
81846
|
this.earlyExits = 0;
|
|
@@ -85454,6 +85757,34 @@ function generateRecommendations3(vulnerabilities, summary) {
|
|
|
85454
85757
|
}
|
|
85455
85758
|
|
|
85456
85759
|
// src/mcp/tools/contract-testing/validate.ts
|
|
85760
|
+
function validateHttpUrl(urlString) {
|
|
85761
|
+
try {
|
|
85762
|
+
const url = new URL(urlString);
|
|
85763
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
85764
|
+
return { valid: false, error: `Invalid protocol: ${url.protocol}. Only http/https allowed.` };
|
|
85765
|
+
}
|
|
85766
|
+
const hostname = url.hostname.toLowerCase();
|
|
85767
|
+
const blockedPatterns = [
|
|
85768
|
+
/^localhost$/i,
|
|
85769
|
+
/^127\./,
|
|
85770
|
+
/^10\./,
|
|
85771
|
+
/^172\.(1[6-9]|2[0-9]|3[01])\./,
|
|
85772
|
+
/^192\.168\./,
|
|
85773
|
+
/^0\.0\.0\.0$/,
|
|
85774
|
+
/^\[::1\]$/,
|
|
85775
|
+
/^169\.254\./
|
|
85776
|
+
// Link-local
|
|
85777
|
+
];
|
|
85778
|
+
for (const pattern of blockedPatterns) {
|
|
85779
|
+
if (pattern.test(hostname)) {
|
|
85780
|
+
return { valid: false, error: `Blocked hostname: ${hostname}. Cannot access internal/private addresses.` };
|
|
85781
|
+
}
|
|
85782
|
+
}
|
|
85783
|
+
return { valid: true };
|
|
85784
|
+
} catch {
|
|
85785
|
+
return { valid: false, error: `Invalid URL format: ${urlString}` };
|
|
85786
|
+
}
|
|
85787
|
+
}
|
|
85457
85788
|
var ContractValidateTool = class extends MCPToolBase {
|
|
85458
85789
|
config = {
|
|
85459
85790
|
name: "qe/contracts/validate",
|
|
@@ -85739,10 +86070,37 @@ var ContractValidateTool = class extends MCPToolBase {
|
|
|
85739
86070
|
async verifyAgainstProvider(providerUrl, consumerName, contractContent, format, contractValidator) {
|
|
85740
86071
|
const failures = [];
|
|
85741
86072
|
const warnings = [];
|
|
86073
|
+
const providerValidation = validateHttpUrl(providerUrl);
|
|
86074
|
+
if (!providerValidation.valid) {
|
|
86075
|
+
return {
|
|
86076
|
+
provider: providerUrl,
|
|
86077
|
+
consumer: consumerName,
|
|
86078
|
+
passed: false,
|
|
86079
|
+
failures: [{
|
|
86080
|
+
endpoint: providerUrl,
|
|
86081
|
+
type: "validation-error",
|
|
86082
|
+
expected: "Valid HTTP/HTTPS URL",
|
|
86083
|
+
actual: providerValidation.error || "Invalid URL",
|
|
86084
|
+
message: `Provider URL validation failed: ${providerValidation.error}`
|
|
86085
|
+
}],
|
|
86086
|
+
warnings: []
|
|
86087
|
+
};
|
|
86088
|
+
}
|
|
85742
86089
|
const contract = this.buildContractFromContent(contractContent, format, consumerName);
|
|
85743
86090
|
for (const endpoint of contract.endpoints) {
|
|
85744
86091
|
try {
|
|
85745
86092
|
const url = `${providerUrl}${endpoint.path}`;
|
|
86093
|
+
const urlValidation = validateHttpUrl(url);
|
|
86094
|
+
if (!urlValidation.valid) {
|
|
86095
|
+
failures.push({
|
|
86096
|
+
endpoint: `${endpoint.method} ${endpoint.path}`,
|
|
86097
|
+
type: "validation-error",
|
|
86098
|
+
expected: "Valid URL",
|
|
86099
|
+
actual: urlValidation.error || "Invalid URL",
|
|
86100
|
+
message: `URL validation failed: ${urlValidation.error}`
|
|
86101
|
+
});
|
|
86102
|
+
continue;
|
|
86103
|
+
}
|
|
85746
86104
|
const response = await fetch(url, {
|
|
85747
86105
|
method: endpoint.method === "GET" ? "GET" : "OPTIONS",
|
|
85748
86106
|
headers: {
|
|
@@ -89348,7 +89706,7 @@ var QE_GOALS = [
|
|
|
89348
89706
|
|
|
89349
89707
|
// src/planning/goap-planner.ts
|
|
89350
89708
|
init_unified_persistence();
|
|
89351
|
-
import { randomUUID as
|
|
89709
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
89352
89710
|
var GOAPPlanner = class {
|
|
89353
89711
|
db = null;
|
|
89354
89712
|
persistence = null;
|
|
@@ -89403,7 +89761,7 @@ var GOAPPlanner = class {
|
|
|
89403
89761
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
89404
89762
|
`);
|
|
89405
89763
|
for (const action of allActions) {
|
|
89406
|
-
const id = `action-${Date.now()}-${
|
|
89764
|
+
const id = `action-${Date.now()}-${randomUUID3().slice(0, 8)}`;
|
|
89407
89765
|
insertAction.run(
|
|
89408
89766
|
id,
|
|
89409
89767
|
action.name,
|
|
@@ -89421,7 +89779,7 @@ var GOAPPlanner = class {
|
|
|
89421
89779
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
89422
89780
|
`);
|
|
89423
89781
|
for (const goal of QE_GOALS) {
|
|
89424
|
-
const id = `goal-${Date.now()}-${
|
|
89782
|
+
const id = `goal-${Date.now()}-${randomUUID3().slice(0, 8)}`;
|
|
89425
89783
|
insertGoal.run(
|
|
89426
89784
|
id,
|
|
89427
89785
|
goal.name,
|
|
@@ -89453,7 +89811,7 @@ var GOAPPlanner = class {
|
|
|
89453
89811
|
this.recordPlanReuse(reusedPlan.id, true);
|
|
89454
89812
|
return {
|
|
89455
89813
|
...reusedPlan,
|
|
89456
|
-
id: `plan-${Date.now()}-${
|
|
89814
|
+
id: `plan-${Date.now()}-${randomUUID3().slice(0, 8)}`,
|
|
89457
89815
|
initialState: this.cloneState(currentState),
|
|
89458
89816
|
reusedFrom: reusedPlan.id,
|
|
89459
89817
|
status: "pending"
|
|
@@ -89471,7 +89829,7 @@ var GOAPPlanner = class {
|
|
|
89471
89829
|
return null;
|
|
89472
89830
|
}
|
|
89473
89831
|
const plan = {
|
|
89474
|
-
id: `plan-${Date.now()}-${
|
|
89832
|
+
id: `plan-${Date.now()}-${randomUUID3().slice(0, 8)}`,
|
|
89475
89833
|
initialState: this.cloneState(currentState),
|
|
89476
89834
|
goalState: goal,
|
|
89477
89835
|
actions: actionSequence,
|
|
@@ -89852,7 +90210,7 @@ var GOAPPlanner = class {
|
|
|
89852
90210
|
*/
|
|
89853
90211
|
async addAction(action) {
|
|
89854
90212
|
await this.initialize();
|
|
89855
|
-
const id = `action-${Date.now()}-${
|
|
90213
|
+
const id = `action-${Date.now()}-${randomUUID3().slice(0, 8)}`;
|
|
89856
90214
|
this.ensureDb().prepare(
|
|
89857
90215
|
`
|
|
89858
90216
|
INSERT INTO goap_actions (
|
|
@@ -90014,7 +90372,7 @@ var GOAPPlanner = class {
|
|
|
90014
90372
|
) VALUES (?, ?, ?, ?, ?, ?)
|
|
90015
90373
|
`
|
|
90016
90374
|
).run(
|
|
90017
|
-
`sig-${Date.now()}-${
|
|
90375
|
+
`sig-${Date.now()}-${randomUUID3().slice(0, 8)}`,
|
|
90018
90376
|
plan.id,
|
|
90019
90377
|
goalHash,
|
|
90020
90378
|
JSON.stringify(stateVector),
|
|
@@ -90098,7 +90456,7 @@ var GOAPPlanner = class {
|
|
|
90098
90456
|
*/
|
|
90099
90457
|
async addGoal(goal) {
|
|
90100
90458
|
await this.initialize();
|
|
90101
|
-
const id = `goal-${Date.now()}-${
|
|
90459
|
+
const id = `goal-${Date.now()}-${randomUUID3().slice(0, 8)}`;
|
|
90102
90460
|
this.ensureDb().prepare(
|
|
90103
90461
|
`
|
|
90104
90462
|
INSERT INTO goap_goals (id, name, description, conditions, priority, qe_domain)
|
|
@@ -90188,7 +90546,7 @@ function getSharedGOAPPlanner() {
|
|
|
90188
90546
|
// src/planning/plan-executor.ts
|
|
90189
90547
|
init_unified_memory();
|
|
90190
90548
|
import Database3 from "better-sqlite3";
|
|
90191
|
-
import { randomUUID as
|
|
90549
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
90192
90550
|
var DEFAULT_CONFIG53 = {
|
|
90193
90551
|
maxRetries: 2,
|
|
90194
90552
|
stepTimeoutMs: 6e4,
|
|
@@ -90325,7 +90683,7 @@ var PlanExecutor = class {
|
|
|
90325
90683
|
async executeWithCallbacks(plan, onStepStart, onStepComplete, initialState) {
|
|
90326
90684
|
await this.initialize();
|
|
90327
90685
|
const startTime = Date.now();
|
|
90328
|
-
const executionId = `exec-${Date.now()}-${
|
|
90686
|
+
const executionId = `exec-${Date.now()}-${randomUUID4().slice(0, 8)}`;
|
|
90329
90687
|
this.currentExecution = { planId: plan.id, cancelled: false };
|
|
90330
90688
|
const result = {
|
|
90331
90689
|
planId: plan.id,
|
|
@@ -90337,7 +90695,7 @@ var PlanExecutor = class {
|
|
|
90337
90695
|
};
|
|
90338
90696
|
let currentState = initialState ? this.cloneState(initialState) : this.cloneState(plan.initialState);
|
|
90339
90697
|
const steps = plan.actions.map((action, index) => ({
|
|
90340
|
-
id: `step-${Date.now()}-${
|
|
90698
|
+
id: `step-${Date.now()}-${randomUUID4().slice(0, 8)}`,
|
|
90341
90699
|
planId: plan.id,
|
|
90342
90700
|
action,
|
|
90343
90701
|
stepOrder: index,
|
|
@@ -90861,7 +91219,7 @@ var MockAgentSpawner = class {
|
|
|
90861
91219
|
async spawn(agentType, task) {
|
|
90862
91220
|
await new Promise((resolve5) => setTimeout(resolve5, this.executionDelay));
|
|
90863
91221
|
const success = Math.random() < this.successRate;
|
|
90864
|
-
const agentId = `mock-agent-${
|
|
91222
|
+
const agentId = `mock-agent-${randomUUID4().slice(0, 8)}`;
|
|
90865
91223
|
if (success) {
|
|
90866
91224
|
return {
|
|
90867
91225
|
agentId,
|
|
@@ -51,7 +51,7 @@ export interface VerificationResult {
|
|
|
51
51
|
}
|
|
52
52
|
export interface ContractFailure {
|
|
53
53
|
endpoint: string;
|
|
54
|
-
type: 'schema-mismatch' | 'status-code-mismatch' | 'missing-endpoint' | 'response-body-mismatch';
|
|
54
|
+
type: 'schema-mismatch' | 'status-code-mismatch' | 'missing-endpoint' | 'response-body-mismatch' | 'validation-error';
|
|
55
55
|
expected: string;
|
|
56
56
|
actual: string;
|
|
57
57
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tools/contract-testing/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAyC,MAAM,YAAY,CAAC;AAC/G,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAiB5C,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;IACrD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,aAAa,EAAE,mBAAmB,CAAC;IACnC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,GAAG,eAAe,GAAG,aAAa,GAAG,sBAAsB,GAAG,oBAAoB,CAAC;IAC3G,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IAClC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,iBAAiB,GAAG,sBAAsB,GAAG,kBAAkB,GAAG,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tools/contract-testing/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,43 @@ import { Version } from '../../../shared/value-objects/index.js';
|
|
|
12
12
|
import { ContractValidatorService } from '../../../domains/contract-testing/services/contract-validator.js';
|
|
13
13
|
import { ApiCompatibilityService } from '../../../domains/contract-testing/services/api-compatibility.js';
|
|
14
14
|
// ============================================================================
|
|
15
|
+
// Security Helpers
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Validate URL to prevent SSRF attacks
|
|
19
|
+
* Only allows HTTP/HTTPS protocols and rejects potentially malicious patterns
|
|
20
|
+
*/
|
|
21
|
+
function validateHttpUrl(urlString) {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(urlString);
|
|
24
|
+
// Only allow HTTP and HTTPS protocols (prevents javascript:, file:, etc.)
|
|
25
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
26
|
+
return { valid: false, error: `Invalid protocol: ${url.protocol}. Only http/https allowed.` };
|
|
27
|
+
}
|
|
28
|
+
// Block localhost and private IP ranges (SSRF protection)
|
|
29
|
+
const hostname = url.hostname.toLowerCase();
|
|
30
|
+
const blockedPatterns = [
|
|
31
|
+
/^localhost$/i,
|
|
32
|
+
/^127\./,
|
|
33
|
+
/^10\./,
|
|
34
|
+
/^172\.(1[6-9]|2[0-9]|3[01])\./,
|
|
35
|
+
/^192\.168\./,
|
|
36
|
+
/^0\.0\.0\.0$/,
|
|
37
|
+
/^\[::1\]$/,
|
|
38
|
+
/^169\.254\./, // Link-local
|
|
39
|
+
];
|
|
40
|
+
for (const pattern of blockedPatterns) {
|
|
41
|
+
if (pattern.test(hostname)) {
|
|
42
|
+
return { valid: false, error: `Blocked hostname: ${hostname}. Cannot access internal/private addresses.` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { valid: true };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { valid: false, error: `Invalid URL format: ${urlString}` };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ============================================================================
|
|
15
52
|
// Tool Implementation
|
|
16
53
|
// ============================================================================
|
|
17
54
|
export class ContractValidateTool extends MCPToolBase {
|
|
@@ -292,6 +329,23 @@ export class ContractValidateTool extends MCPToolBase {
|
|
|
292
329
|
async verifyAgainstProvider(providerUrl, consumerName, contractContent, format, contractValidator) {
|
|
293
330
|
const failures = [];
|
|
294
331
|
const warnings = [];
|
|
332
|
+
// Validate provider URL first (SSRF protection)
|
|
333
|
+
const providerValidation = validateHttpUrl(providerUrl);
|
|
334
|
+
if (!providerValidation.valid) {
|
|
335
|
+
return {
|
|
336
|
+
provider: providerUrl,
|
|
337
|
+
consumer: consumerName,
|
|
338
|
+
passed: false,
|
|
339
|
+
failures: [{
|
|
340
|
+
endpoint: providerUrl,
|
|
341
|
+
type: 'validation-error',
|
|
342
|
+
expected: 'Valid HTTP/HTTPS URL',
|
|
343
|
+
actual: providerValidation.error || 'Invalid URL',
|
|
344
|
+
message: `Provider URL validation failed: ${providerValidation.error}`,
|
|
345
|
+
}],
|
|
346
|
+
warnings: [],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
295
349
|
// Build contract and verify
|
|
296
350
|
const contract = this.buildContractFromContent(contractContent, format, consumerName);
|
|
297
351
|
// Validate each endpoint against the provider
|
|
@@ -299,6 +353,18 @@ export class ContractValidateTool extends MCPToolBase {
|
|
|
299
353
|
try {
|
|
300
354
|
// Construct the full URL
|
|
301
355
|
const url = `${providerUrl}${endpoint.path}`;
|
|
356
|
+
// Validate constructed URL (prevents path traversal attacks)
|
|
357
|
+
const urlValidation = validateHttpUrl(url);
|
|
358
|
+
if (!urlValidation.valid) {
|
|
359
|
+
failures.push({
|
|
360
|
+
endpoint: `${endpoint.method} ${endpoint.path}`,
|
|
361
|
+
type: 'validation-error',
|
|
362
|
+
expected: 'Valid URL',
|
|
363
|
+
actual: urlValidation.error || 'Invalid URL',
|
|
364
|
+
message: `URL validation failed: ${urlValidation.error}`,
|
|
365
|
+
});
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
302
368
|
// Make a simple request to verify the endpoint exists
|
|
303
369
|
const response = await fetch(url, {
|
|
304
370
|
method: endpoint.method === 'GET' ? 'GET' : 'OPTIONS',
|