@frontmcp/plugin-codecall 1.8.0 → 1.8.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/codecall.plugin.d.ts.map +1 -1
- package/esm/index.mjs +535 -199
- package/esm/package.json +5 -5
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +559 -221
- package/package.json +5 -5
- package/security/codecall-tool-policy.d.ts +67 -15
- package/security/codecall-tool-policy.d.ts.map +1 -1
- package/security/index.d.ts +0 -1
- package/security/index.d.ts.map +1 -1
- package/services/enclave.service.d.ts +7 -0
- package/services/enclave.service.d.ts.map +1 -1
- package/services/index.d.ts +0 -1
- package/services/index.d.ts.map +1 -1
- package/services/tool-search.service.d.ts +7 -15
- package/services/tool-search.service.d.ts.map +1 -1
- package/tools/describe.tool.d.ts.map +1 -1
- package/tools/execute.tool.d.ts.map +1 -1
- package/tools/invoke.tool.d.ts.map +1 -1
- package/tools/search-knowledge.tool.d.ts.map +1 -1
- package/tools/search-skills.tool.d.ts.map +1 -1
- package/tools/search.tool.d.ts.map +1 -1
- package/security/tool-access-control.service.d.ts +0 -115
- package/security/tool-access-control.service.d.ts.map +0 -1
- package/services/error-enrichment.service.d.ts +0 -95
- package/services/error-enrichment.service.d.ts.map +0 -1
package/esm/index.mjs
CHANGED
|
@@ -372,9 +372,434 @@ function presetDefaults(preset) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
// plugins/plugin-codecall/src/services/audit-logger.service.ts
|
|
376
|
+
import { Provider as Provider2, ProviderScope as ProviderScope2 } from "@frontmcp/sdk";
|
|
377
|
+
var AUDIT_EVENT_TYPES = {
|
|
378
|
+
/** Script execution started */
|
|
379
|
+
EXECUTION_START: "codecall:execution:start",
|
|
380
|
+
/** Script execution completed successfully */
|
|
381
|
+
EXECUTION_SUCCESS: "codecall:execution:success",
|
|
382
|
+
/** Script execution failed */
|
|
383
|
+
EXECUTION_FAILURE: "codecall:execution:failure",
|
|
384
|
+
/** Script execution timed out */
|
|
385
|
+
EXECUTION_TIMEOUT: "codecall:execution:timeout",
|
|
386
|
+
/** Tool call initiated from script */
|
|
387
|
+
TOOL_CALL_START: "codecall:tool:call:start",
|
|
388
|
+
/** Tool call completed successfully */
|
|
389
|
+
TOOL_CALL_SUCCESS: "codecall:tool:call:success",
|
|
390
|
+
/** Tool call failed */
|
|
391
|
+
TOOL_CALL_FAILURE: "codecall:tool:call:failure",
|
|
392
|
+
/** Self-reference attack blocked */
|
|
393
|
+
SECURITY_SELF_REFERENCE: "codecall:security:self-reference",
|
|
394
|
+
/** Tool access denied */
|
|
395
|
+
SECURITY_ACCESS_DENIED: "codecall:security:access-denied",
|
|
396
|
+
/** AST validation failed (blocked code pattern) */
|
|
397
|
+
SECURITY_AST_BLOCKED: "codecall:security:ast-blocked",
|
|
398
|
+
/** Search performed */
|
|
399
|
+
SEARCH_PERFORMED: "codecall:search:performed",
|
|
400
|
+
/** Tool described */
|
|
401
|
+
DESCRIBE_PERFORMED: "codecall:describe:performed",
|
|
402
|
+
/** Direct invoke performed */
|
|
403
|
+
INVOKE_PERFORMED: "codecall:invoke:performed"
|
|
404
|
+
};
|
|
405
|
+
var AuditLoggerService = class {
|
|
406
|
+
listeners = /* @__PURE__ */ new Set();
|
|
407
|
+
executionCounter = 0;
|
|
408
|
+
/**
|
|
409
|
+
* Subscribe to audit events.
|
|
410
|
+
*
|
|
411
|
+
* @param listener - Function to call when events occur
|
|
412
|
+
* @returns Unsubscribe function
|
|
413
|
+
*/
|
|
414
|
+
subscribe(listener) {
|
|
415
|
+
this.listeners.add(listener);
|
|
416
|
+
return () => {
|
|
417
|
+
this.listeners.delete(listener);
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Generate a unique execution ID.
|
|
422
|
+
*/
|
|
423
|
+
generateExecutionId() {
|
|
424
|
+
const timestamp = Date.now().toString(36);
|
|
425
|
+
const counter = (++this.executionCounter).toString(36).padStart(4, "0");
|
|
426
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
427
|
+
return `exec_${timestamp}_${counter}_${random}`;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Log execution start.
|
|
431
|
+
*/
|
|
432
|
+
logExecutionStart(executionId, script) {
|
|
433
|
+
this.emit({
|
|
434
|
+
type: AUDIT_EVENT_TYPES.EXECUTION_START,
|
|
435
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
436
|
+
executionId,
|
|
437
|
+
data: {
|
|
438
|
+
scriptHash: this.hashScript(script),
|
|
439
|
+
scriptLength: script.length
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Log execution success.
|
|
445
|
+
*/
|
|
446
|
+
logExecutionSuccess(executionId, script, durationMs, toolCallCount) {
|
|
447
|
+
this.emit({
|
|
448
|
+
type: AUDIT_EVENT_TYPES.EXECUTION_SUCCESS,
|
|
449
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
450
|
+
executionId,
|
|
451
|
+
durationMs,
|
|
452
|
+
data: {
|
|
453
|
+
scriptHash: this.hashScript(script),
|
|
454
|
+
scriptLength: script.length,
|
|
455
|
+
toolCallCount
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Log execution failure.
|
|
461
|
+
*/
|
|
462
|
+
logExecutionFailure(executionId, script, durationMs, error) {
|
|
463
|
+
this.emit({
|
|
464
|
+
type: AUDIT_EVENT_TYPES.EXECUTION_FAILURE,
|
|
465
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
466
|
+
executionId,
|
|
467
|
+
durationMs,
|
|
468
|
+
data: {
|
|
469
|
+
scriptHash: this.hashScript(script),
|
|
470
|
+
scriptLength: script.length,
|
|
471
|
+
error: this.sanitizeError(error)
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Log execution timeout.
|
|
477
|
+
*/
|
|
478
|
+
logExecutionTimeout(executionId, script, durationMs) {
|
|
479
|
+
this.emit({
|
|
480
|
+
type: AUDIT_EVENT_TYPES.EXECUTION_TIMEOUT,
|
|
481
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
482
|
+
executionId,
|
|
483
|
+
durationMs,
|
|
484
|
+
data: {
|
|
485
|
+
scriptHash: this.hashScript(script),
|
|
486
|
+
scriptLength: script.length
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Log tool call start.
|
|
492
|
+
*/
|
|
493
|
+
logToolCallStart(executionId, toolName, callDepth) {
|
|
494
|
+
this.emit({
|
|
495
|
+
type: AUDIT_EVENT_TYPES.TOOL_CALL_START,
|
|
496
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
497
|
+
executionId,
|
|
498
|
+
data: {
|
|
499
|
+
toolName,
|
|
500
|
+
callDepth
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Log tool call success.
|
|
506
|
+
*/
|
|
507
|
+
logToolCallSuccess(executionId, toolName, callDepth, durationMs) {
|
|
508
|
+
this.emit({
|
|
509
|
+
type: AUDIT_EVENT_TYPES.TOOL_CALL_SUCCESS,
|
|
510
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
511
|
+
executionId,
|
|
512
|
+
durationMs,
|
|
513
|
+
data: {
|
|
514
|
+
toolName,
|
|
515
|
+
callDepth
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Log tool call failure.
|
|
521
|
+
*/
|
|
522
|
+
logToolCallFailure(executionId, toolName, callDepth, durationMs, errorCode) {
|
|
523
|
+
this.emit({
|
|
524
|
+
type: AUDIT_EVENT_TYPES.TOOL_CALL_FAILURE,
|
|
525
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
526
|
+
executionId,
|
|
527
|
+
durationMs,
|
|
528
|
+
data: {
|
|
529
|
+
toolName,
|
|
530
|
+
callDepth,
|
|
531
|
+
errorCode
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Log security event: self-reference blocked.
|
|
537
|
+
*/
|
|
538
|
+
logSecuritySelfReference(executionId, toolName) {
|
|
539
|
+
this.emit({
|
|
540
|
+
type: AUDIT_EVENT_TYPES.SECURITY_SELF_REFERENCE,
|
|
541
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
542
|
+
executionId,
|
|
543
|
+
data: {
|
|
544
|
+
blocked: toolName,
|
|
545
|
+
reason: "Self-reference attack: attempted to call CodeCall tool from within AgentScript"
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Log security event: access denied.
|
|
551
|
+
*/
|
|
552
|
+
logSecurityAccessDenied(executionId, toolName, reason) {
|
|
553
|
+
this.emit({
|
|
554
|
+
type: AUDIT_EVENT_TYPES.SECURITY_ACCESS_DENIED,
|
|
555
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
556
|
+
executionId,
|
|
557
|
+
data: {
|
|
558
|
+
blocked: toolName,
|
|
559
|
+
reason: this.sanitizeError(reason)
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Log security event: AST validation blocked.
|
|
565
|
+
*/
|
|
566
|
+
logSecurityAstBlocked(executionId, pattern, reason) {
|
|
567
|
+
this.emit({
|
|
568
|
+
type: AUDIT_EVENT_TYPES.SECURITY_AST_BLOCKED,
|
|
569
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
570
|
+
executionId,
|
|
571
|
+
data: {
|
|
572
|
+
blocked: pattern,
|
|
573
|
+
reason: this.sanitizeError(reason)
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Log search operation.
|
|
579
|
+
*/
|
|
580
|
+
logSearch(executionId, query, resultCount, durationMs) {
|
|
581
|
+
this.emit({
|
|
582
|
+
type: AUDIT_EVENT_TYPES.SEARCH_PERFORMED,
|
|
583
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
584
|
+
executionId,
|
|
585
|
+
durationMs,
|
|
586
|
+
data: {
|
|
587
|
+
queryLength: query.length,
|
|
588
|
+
resultCount
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Log describe operation.
|
|
594
|
+
*/
|
|
595
|
+
logDescribe(executionId, toolNames, durationMs) {
|
|
596
|
+
this.emit({
|
|
597
|
+
type: AUDIT_EVENT_TYPES.DESCRIBE_PERFORMED,
|
|
598
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
599
|
+
executionId,
|
|
600
|
+
durationMs,
|
|
601
|
+
data: {
|
|
602
|
+
toolCount: toolNames.length,
|
|
603
|
+
toolNames: toolNames.slice(0, 10)
|
|
604
|
+
// Limit to first 10 for audit
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Log invoke operation.
|
|
610
|
+
*/
|
|
611
|
+
logInvoke(executionId, toolName, success, durationMs) {
|
|
612
|
+
this.emit({
|
|
613
|
+
type: AUDIT_EVENT_TYPES.INVOKE_PERFORMED,
|
|
614
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
615
|
+
executionId,
|
|
616
|
+
durationMs,
|
|
617
|
+
data: {
|
|
618
|
+
toolName,
|
|
619
|
+
success
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Emit an audit event to all listeners.
|
|
625
|
+
*/
|
|
626
|
+
emit(event) {
|
|
627
|
+
const frozenEvent = Object.freeze({ ...event, data: Object.freeze({ ...event.data }) });
|
|
628
|
+
for (const listener of this.listeners) {
|
|
629
|
+
try {
|
|
630
|
+
listener(frozenEvent);
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Create a simple hash of the script for identification.
|
|
637
|
+
* Uses a fast, non-cryptographic hash for performance.
|
|
638
|
+
*/
|
|
639
|
+
hashScript(script) {
|
|
640
|
+
let hash = 0;
|
|
641
|
+
for (let i = 0; i < script.length; i++) {
|
|
642
|
+
const char = script.charCodeAt(i);
|
|
643
|
+
hash = (hash << 5) - hash + char;
|
|
644
|
+
hash = hash & hash;
|
|
645
|
+
}
|
|
646
|
+
return `sh_${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Sanitize error messages to remove sensitive information.
|
|
650
|
+
*/
|
|
651
|
+
sanitizeError(error) {
|
|
652
|
+
if (!error) return "Unknown error";
|
|
653
|
+
let sanitized = error.replace(/(?:\/[\w.-]+)+|(?:[A-Za-z]:\\[\w\\.-]+)+/g, "[path]");
|
|
654
|
+
sanitized = sanitized.replace(/:\d+:\d+/g, "");
|
|
655
|
+
sanitized = sanitized.replace(/\n\s*at .*/g, "");
|
|
656
|
+
if (sanitized.length > 200) {
|
|
657
|
+
sanitized = sanitized.substring(0, 200) + "...";
|
|
658
|
+
}
|
|
659
|
+
return sanitized.trim();
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
AuditLoggerService = __decorateClass([
|
|
663
|
+
Provider2({
|
|
664
|
+
name: "codecall:audit-logger",
|
|
665
|
+
scope: ProviderScope2.GLOBAL
|
|
666
|
+
})
|
|
667
|
+
], AuditLoggerService);
|
|
668
|
+
|
|
375
669
|
// plugins/plugin-codecall/src/services/tool-search.service.ts
|
|
376
670
|
import { TFIDFVectoria, VectoriaDB } from "vectoriadb";
|
|
377
671
|
|
|
672
|
+
// plugins/plugin-codecall/src/security/codecall-tool-policy.ts
|
|
673
|
+
var BLOCKED_NAMESPACE_PATTERNS = Object.freeze([/^system:/, /^internal:/, /^__/]);
|
|
674
|
+
var ALLOWED = { allowed: true };
|
|
675
|
+
function deny(reason) {
|
|
676
|
+
return { allowed: false, reason };
|
|
677
|
+
}
|
|
678
|
+
function codeCallAppIdOf(scope, entry) {
|
|
679
|
+
const lineage = scope?.tools?.lineageOf?.(entry) ?? [];
|
|
680
|
+
const owners = entry.owner ? [...lineage, entry.owner] : lineage;
|
|
681
|
+
return owners.find((owner) => owner.kind === "app")?.id;
|
|
682
|
+
}
|
|
683
|
+
function toCodeCallPolicyTool(entry, requestedName, scope) {
|
|
684
|
+
const { metadata } = entry;
|
|
685
|
+
return {
|
|
686
|
+
name: entry.name || entry.fullName,
|
|
687
|
+
fullName: entry.fullName,
|
|
688
|
+
aliases: requestedName ? [requestedName] : void 0,
|
|
689
|
+
appId: codeCallAppIdOf(scope, entry),
|
|
690
|
+
description: metadata?.description,
|
|
691
|
+
tags: metadata?.tags,
|
|
692
|
+
// `internal` tools refuse every external tools/call, CodeCall's included, so no surface may offer them.
|
|
693
|
+
hidden: metadata?.hideFromDiscovery === true || metadata?.visibility === "hidden" || metadata?.visibility === "internal",
|
|
694
|
+
codecall: metadata?.codecall
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function toToolFilterInfo(tool) {
|
|
698
|
+
return {
|
|
699
|
+
name: tool.name,
|
|
700
|
+
appId: tool.appId,
|
|
701
|
+
source: tool.codecall?.source,
|
|
702
|
+
description: tool.description,
|
|
703
|
+
tags: tool.codecall?.tags ?? tool.tags
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function ownNamesOf(tool) {
|
|
707
|
+
return [tool.name, tool.fullName].filter((candidate) => !!candidate);
|
|
708
|
+
}
|
|
709
|
+
function checkCodeCallToolPolicy(tool, config) {
|
|
710
|
+
const { name } = tool;
|
|
711
|
+
const spellings = [...ownNamesOf(tool), ...tool.aliases ?? []];
|
|
712
|
+
if (spellings.some((candidate) => candidate.startsWith("codecall:"))) {
|
|
713
|
+
return deny("CodeCall meta-tools are not callable from CodeCall");
|
|
714
|
+
}
|
|
715
|
+
for (const candidate of spellings) {
|
|
716
|
+
if (BLOCKED_NAMESPACE_PATTERNS.some((pattern) => pattern.test(candidate))) {
|
|
717
|
+
return deny(`Tool "${candidate}" is in a namespace CodeCall never calls`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (tool.hidden === true) {
|
|
721
|
+
return deny(`Tool "${name}" is hidden from discovery`);
|
|
722
|
+
}
|
|
723
|
+
const enabled = tool.codecall?.enabledInCodeCall;
|
|
724
|
+
switch (config.mode) {
|
|
725
|
+
case "codecall_only":
|
|
726
|
+
case "metadata_driven":
|
|
727
|
+
if (enabled === false) {
|
|
728
|
+
return deny(`Tool "${name}" sets enabledInCodeCall: false`);
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
case "codecall_opt_in":
|
|
732
|
+
if (enabled !== true) {
|
|
733
|
+
return deny(`Tool "${name}" has not opted in to CodeCall`);
|
|
734
|
+
}
|
|
735
|
+
break;
|
|
736
|
+
default:
|
|
737
|
+
return deny(`Unknown CodeCall mode: ${String(config.mode)}`);
|
|
738
|
+
}
|
|
739
|
+
if (config.includeTools && !config.includeTools(toToolFilterInfo(tool))) {
|
|
740
|
+
return deny(`Tool "${name}" is excluded by the includeTools filter`);
|
|
741
|
+
}
|
|
742
|
+
return ALLOWED;
|
|
743
|
+
}
|
|
744
|
+
function denyUnknownTool(name) {
|
|
745
|
+
return deny(`Tool "${name}" is not available through CodeCall`);
|
|
746
|
+
}
|
|
747
|
+
function resolveCodeCallTool(scope, name) {
|
|
748
|
+
const typedScope = scope;
|
|
749
|
+
const alias = /[-_]/.test(name) ? name.includes("_") ? name.replace(/_/g, "-") : name.replace(/-/g, "_") : void 0;
|
|
750
|
+
const candidates = alias ? [name, alias] : [name];
|
|
751
|
+
const matches = (entry) => candidates.includes(entry.fullName) || candidates.includes(entry.name);
|
|
752
|
+
const local = typedScope.tools?.getTools(true)?.find(matches);
|
|
753
|
+
if (local) return local;
|
|
754
|
+
const registries = typedScope.providers?.getRegistries?.("AppRegistry") ?? [];
|
|
755
|
+
for (const registry of registries) {
|
|
756
|
+
for (const app of registry.getApps()) {
|
|
757
|
+
if (!app.isRemote) continue;
|
|
758
|
+
const remote = app.tools.getTools(true).find(matches);
|
|
759
|
+
if (remote) return remote;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return void 0;
|
|
763
|
+
}
|
|
764
|
+
function checkDirectCallPolicy(tool, directCalls) {
|
|
765
|
+
if (!directCalls) return ALLOWED;
|
|
766
|
+
if (directCalls.enabled === false) {
|
|
767
|
+
return deny("Direct tool invocation is disabled");
|
|
768
|
+
}
|
|
769
|
+
const { allowedTools } = directCalls;
|
|
770
|
+
if (allowedTools && !ownNamesOf(tool).some((ownName) => allowedTools.includes(ownName))) {
|
|
771
|
+
return deny(`Tool "${tool.name}" is not in the direct-call allowlist`);
|
|
772
|
+
}
|
|
773
|
+
if (directCalls.filter && !directCalls.filter(toToolFilterInfo(tool))) {
|
|
774
|
+
return deny(`Tool "${tool.name}" is excluded by the directCalls filter`);
|
|
775
|
+
}
|
|
776
|
+
return ALLOWED;
|
|
777
|
+
}
|
|
778
|
+
function readCodeCallPolicyConfig(config) {
|
|
779
|
+
return {
|
|
780
|
+
// An absent mode means an unparsed/partial config, not a hostile one: fall back to the
|
|
781
|
+
// schema's own documented default rather than denying every call. An unrecognised mode
|
|
782
|
+
// still fails closed inside the policy's switch.
|
|
783
|
+
mode: config.get("mode") ?? "codecall_only",
|
|
784
|
+
includeTools: config.get("includeTools")
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function checkCodeCallToolAccess(scope, config, name, options = {}) {
|
|
788
|
+
const entry = resolveCodeCallTool(scope, name);
|
|
789
|
+
if (!entry) return denyUnknownTool(name);
|
|
790
|
+
const policyTool = toCodeCallPolicyTool(entry, name, scope);
|
|
791
|
+
const baseDecision = checkCodeCallToolPolicy(policyTool, readCodeCallPolicyConfig(config));
|
|
792
|
+
if (!baseDecision.allowed) return baseDecision;
|
|
793
|
+
if (options.directCall) {
|
|
794
|
+
const directDecision = checkDirectCallPolicy(
|
|
795
|
+
policyTool,
|
|
796
|
+
config.get("directCalls")
|
|
797
|
+
);
|
|
798
|
+
if (!directDecision.allowed) return directDecision;
|
|
799
|
+
}
|
|
800
|
+
return { allowed: true, entry };
|
|
801
|
+
}
|
|
802
|
+
|
|
378
803
|
// plugins/plugin-codecall/src/services/synonym-expansion.service.ts
|
|
379
804
|
var DEFAULT_SYNONYM_GROUPS = [
|
|
380
805
|
// ===========================================================================
|
|
@@ -1043,62 +1468,12 @@ var ToolSearchService = class _ToolSearchService {
|
|
|
1043
1468
|
this.initialized = true;
|
|
1044
1469
|
}
|
|
1045
1470
|
/**
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
* - Excludes tools with hideFromDiscovery: true (system tools like sendElicitationResult)
|
|
1050
|
-
* - Mode-based filtering (codecall_only, codecall_opt_in, metadata_driven)
|
|
1051
|
-
* - Per-tool metadata.codecall.enabledInCodeCall
|
|
1052
|
-
* - Custom includeTools filter function
|
|
1471
|
+
* Whether a tool belongs in the search index: exactly when CodeCall may execute it.
|
|
1472
|
+
* The decision is `codecall:execute`'s own, so search cannot show a tool execution refuses
|
|
1473
|
+
* or hide one it runs (GHSA-6w3j-82v5-6qrr).
|
|
1053
1474
|
*/
|
|
1054
1475
|
shouldIndexTool(tool) {
|
|
1055
|
-
|
|
1056
|
-
if (toolName.startsWith("codecall:")) {
|
|
1057
|
-
return false;
|
|
1058
|
-
}
|
|
1059
|
-
if (tool.metadata.hideFromDiscovery === true) {
|
|
1060
|
-
return false;
|
|
1061
|
-
}
|
|
1062
|
-
const codecallMeta = this.getCodeCallMetadata(tool);
|
|
1063
|
-
switch (this.config.mode) {
|
|
1064
|
-
case "codecall_only":
|
|
1065
|
-
if (codecallMeta?.enabledInCodeCall === false) {
|
|
1066
|
-
return false;
|
|
1067
|
-
}
|
|
1068
|
-
break;
|
|
1069
|
-
case "codecall_opt_in":
|
|
1070
|
-
if (codecallMeta?.enabledInCodeCall !== true) {
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
break;
|
|
1074
|
-
case "metadata_driven":
|
|
1075
|
-
if (codecallMeta?.enabledInCodeCall === false) {
|
|
1076
|
-
return false;
|
|
1077
|
-
}
|
|
1078
|
-
break;
|
|
1079
|
-
default:
|
|
1080
|
-
throw new Error(`Unknown CodeCall mode: ${this.config.mode}`);
|
|
1081
|
-
}
|
|
1082
|
-
if (this.config.includeTools) {
|
|
1083
|
-
const appId = this.extractAppId(tool);
|
|
1084
|
-
const filterInfo = {
|
|
1085
|
-
name: toolName,
|
|
1086
|
-
appId,
|
|
1087
|
-
source: codecallMeta?.source,
|
|
1088
|
-
description: tool.metadata.description,
|
|
1089
|
-
tags: codecallMeta?.tags || tool.metadata.tags
|
|
1090
|
-
};
|
|
1091
|
-
if (!this.config.includeTools(filterInfo)) {
|
|
1092
|
-
return false;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
return true;
|
|
1096
|
-
}
|
|
1097
|
-
/**
|
|
1098
|
-
* Extract CodeCall-specific metadata from a tool.
|
|
1099
|
-
*/
|
|
1100
|
-
getCodeCallMetadata(tool) {
|
|
1101
|
-
return tool.metadata?.codecall;
|
|
1476
|
+
return checkCodeCallToolPolicy(toCodeCallPolicyTool(tool, void 0, this.scope), this.config).allowed;
|
|
1102
1477
|
}
|
|
1103
1478
|
/**
|
|
1104
1479
|
* Initializes the search service by indexing all tools from the registry.
|
|
@@ -1188,11 +1563,7 @@ var ToolSearchService = class _ToolSearchService {
|
|
|
1188
1563
|
* Extracts app ID from tool's owner lineage
|
|
1189
1564
|
*/
|
|
1190
1565
|
extractAppId(tool) {
|
|
1191
|
-
|
|
1192
|
-
if (tool.owner.kind === "app") {
|
|
1193
|
-
return tool.owner.id;
|
|
1194
|
-
}
|
|
1195
|
-
return void 0;
|
|
1566
|
+
return codeCallAppIdOf(this.scope, tool);
|
|
1196
1567
|
}
|
|
1197
1568
|
/**
|
|
1198
1569
|
* Searches for tools matching the query
|
|
@@ -1279,7 +1650,7 @@ var ToolSearchService = class _ToolSearchService {
|
|
|
1279
1650
|
|
|
1280
1651
|
// plugins/plugin-codecall/src/services/enclave.service.ts
|
|
1281
1652
|
import { Enclave } from "@enclave-vm/core";
|
|
1282
|
-
import { Provider as
|
|
1653
|
+
import { Provider as Provider3, ProviderScope as ProviderScope3 } from "@frontmcp/sdk";
|
|
1283
1654
|
var ScriptTooLargeError = class extends Error {
|
|
1284
1655
|
code = "SCRIPT_TOO_LARGE";
|
|
1285
1656
|
scriptLength;
|
|
@@ -1398,15 +1769,23 @@ var EnclaveService = class {
|
|
|
1398
1769
|
const error = result.error;
|
|
1399
1770
|
const timedOut = error.message?.includes("timed out") || error.code === "TIMEOUT";
|
|
1400
1771
|
if (error.code === "VALIDATION_ERROR") {
|
|
1772
|
+
const issues = error.data?.issues ?? [];
|
|
1773
|
+
const blockedPatterns = [...new Set(issues.map((issue) => issue.code).filter((code) => !!code))];
|
|
1401
1774
|
return {
|
|
1402
1775
|
success: false,
|
|
1403
1776
|
error: {
|
|
1404
1777
|
message: error.message,
|
|
1405
1778
|
name: "ValidationError",
|
|
1406
|
-
code: error.code
|
|
1779
|
+
code: error.code,
|
|
1780
|
+
...blockedPatterns.length > 0 ? { blockedPatterns } : {}
|
|
1407
1781
|
},
|
|
1408
1782
|
logs,
|
|
1409
|
-
timedOut: false
|
|
1783
|
+
timedOut: false,
|
|
1784
|
+
stats: {
|
|
1785
|
+
duration: result.stats.duration,
|
|
1786
|
+
toolCallCount: result.stats.toolCallCount,
|
|
1787
|
+
iterationCount: result.stats.iterationCount
|
|
1788
|
+
}
|
|
1410
1789
|
};
|
|
1411
1790
|
}
|
|
1412
1791
|
const errorData = error.data;
|
|
@@ -1446,10 +1825,10 @@ var EnclaveService = class {
|
|
|
1446
1825
|
}
|
|
1447
1826
|
};
|
|
1448
1827
|
EnclaveService = __decorateClass([
|
|
1449
|
-
|
|
1828
|
+
Provider3({
|
|
1450
1829
|
name: "codecall:enclave",
|
|
1451
1830
|
description: "Executes AgentScript code in a secure enclave",
|
|
1452
|
-
scope:
|
|
1831
|
+
scope: ProviderScope3.GLOBAL
|
|
1453
1832
|
})
|
|
1454
1833
|
], EnclaveService);
|
|
1455
1834
|
|
|
@@ -1509,6 +1888,9 @@ var SearchTool = class extends ToolContext {
|
|
|
1509
1888
|
const { queries, appIds, excludeToolNames = [], topK = 5, minRelevanceScore = 0.3 } = input;
|
|
1510
1889
|
const searchService = this.get(ToolSearchService);
|
|
1511
1890
|
const warnings = [];
|
|
1891
|
+
const audit = this.tryGet(AuditLoggerService);
|
|
1892
|
+
const executionId = audit ? audit.generateExecutionId() : "";
|
|
1893
|
+
const startedAt = Date.now();
|
|
1512
1894
|
const nonExistentExcludedTools = excludeToolNames.filter((toolName) => !searchService.hasTool(toolName));
|
|
1513
1895
|
if (nonExistentExcludedTools.length > 0) {
|
|
1514
1896
|
warnings.push({
|
|
@@ -1564,6 +1946,7 @@ var SearchTool = class extends ToolContext {
|
|
|
1564
1946
|
message: `${lowRelevanceCount} result(s) filtered due to relevance below ${minRelevanceScore}`
|
|
1565
1947
|
});
|
|
1566
1948
|
}
|
|
1949
|
+
audit?.logSearch(executionId, queries.join(" "), tools.length, Date.now() - startedAt);
|
|
1567
1950
|
return {
|
|
1568
1951
|
tools,
|
|
1569
1952
|
warnings,
|
|
@@ -1594,7 +1977,7 @@ SearchTool = __decorateClass([
|
|
|
1594
1977
|
], SearchTool);
|
|
1595
1978
|
|
|
1596
1979
|
// plugins/plugin-codecall/src/tools/search-skills.tool.ts
|
|
1597
|
-
import { Tool as Tool2, ToolContext as ToolContext2 } from "@frontmcp/sdk";
|
|
1980
|
+
import { filterServableSkills, Tool as Tool2, ToolContext as ToolContext2 } from "@frontmcp/sdk";
|
|
1598
1981
|
|
|
1599
1982
|
// plugins/plugin-codecall/src/tools/search-skills.schema.ts
|
|
1600
1983
|
import { z as z3 } from "@frontmcp/lazy-zod";
|
|
@@ -1655,7 +2038,7 @@ var SearchSkillsTool = class extends ToolContext2 {
|
|
|
1655
2038
|
async execute(input) {
|
|
1656
2039
|
const { queries, tags, excludeSkillNames = [], topK = 5, minRelevanceScore = 0.1 } = input;
|
|
1657
2040
|
const skillRegistry = this.scope.skills;
|
|
1658
|
-
const executableSkills = skillRegistry.getExecutableSkills();
|
|
2041
|
+
const executableSkills = await filterServableSkills(this.scope, skillRegistry.getExecutableSkills());
|
|
1659
2042
|
const executableSet = new Set(executableSkills.map((s) => s.name));
|
|
1660
2043
|
const totalExecutableSkills = executableSkills.length;
|
|
1661
2044
|
const warnings = [];
|
|
@@ -1746,7 +2129,7 @@ SearchSkillsTool = __decorateClass([
|
|
|
1746
2129
|
], SearchSkillsTool);
|
|
1747
2130
|
|
|
1748
2131
|
// plugins/plugin-codecall/src/tools/search-knowledge.tool.ts
|
|
1749
|
-
import { Tool as Tool3, ToolContext as ToolContext3 } from "@frontmcp/sdk";
|
|
2132
|
+
import { filterServableSkills as filterServableSkills2, Tool as Tool3, ToolContext as ToolContext3 } from "@frontmcp/sdk";
|
|
1750
2133
|
|
|
1751
2134
|
// plugins/plugin-codecall/src/tools/search-knowledge.schema.ts
|
|
1752
2135
|
import { z as z4 } from "@frontmcp/lazy-zod";
|
|
@@ -1796,7 +2179,7 @@ var SearchKnowledgeTool = class extends ToolContext3 {
|
|
|
1796
2179
|
async execute(input) {
|
|
1797
2180
|
const { queries, tags, excludeSkillNames = [], topK = 5, minRelevanceScore = 0.1 } = input;
|
|
1798
2181
|
const skillRegistry = this.scope.skills;
|
|
1799
|
-
const knowledgeSkills = skillRegistry.getKnowledgeOnlySkills();
|
|
2182
|
+
const knowledgeSkills = await filterServableSkills2(this.scope, skillRegistry.getKnowledgeOnlySkills());
|
|
1800
2183
|
const knowledgeSet = new Set(knowledgeSkills.map((s) => s.name));
|
|
1801
2184
|
const totalKnowledgeSkills = knowledgeSkills.length;
|
|
1802
2185
|
const warnings = [];
|
|
@@ -1882,125 +2265,6 @@ SearchKnowledgeTool = __decorateClass([
|
|
|
1882
2265
|
import { toJSONSchema, z as z6, ZodType } from "@frontmcp/lazy-zod";
|
|
1883
2266
|
import { Tool as Tool4, ToolContext as ToolContext4 } from "@frontmcp/sdk";
|
|
1884
2267
|
|
|
1885
|
-
// plugins/plugin-codecall/src/security/codecall-tool-policy.ts
|
|
1886
|
-
var BLOCKED_NAMESPACE_PATTERNS = Object.freeze([/^system:/, /^internal:/, /^__/]);
|
|
1887
|
-
var ALLOWED = { allowed: true };
|
|
1888
|
-
function deny(reason) {
|
|
1889
|
-
return { allowed: false, reason };
|
|
1890
|
-
}
|
|
1891
|
-
function checkCodeCallToolPolicy(tool, config) {
|
|
1892
|
-
const { name } = tool;
|
|
1893
|
-
const names = [name, ...tool.aliases ?? []].filter((candidate) => !!candidate);
|
|
1894
|
-
if (names.some((candidate) => candidate.startsWith("codecall:"))) {
|
|
1895
|
-
return deny("CodeCall meta-tools are not callable from CodeCall");
|
|
1896
|
-
}
|
|
1897
|
-
for (const candidate of names) {
|
|
1898
|
-
if (BLOCKED_NAMESPACE_PATTERNS.some((pattern) => pattern.test(candidate))) {
|
|
1899
|
-
return deny(`Tool "${candidate}" is in a namespace CodeCall never calls`);
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
if (tool.hideFromDiscovery === true) {
|
|
1903
|
-
return deny(`Tool "${name}" is hidden from discovery`);
|
|
1904
|
-
}
|
|
1905
|
-
const enabled = tool.codecall?.enabledInCodeCall;
|
|
1906
|
-
switch (config.mode) {
|
|
1907
|
-
case "codecall_only":
|
|
1908
|
-
case "metadata_driven":
|
|
1909
|
-
if (enabled === false) {
|
|
1910
|
-
return deny(`Tool "${name}" sets enabledInCodeCall: false`);
|
|
1911
|
-
}
|
|
1912
|
-
break;
|
|
1913
|
-
case "codecall_opt_in":
|
|
1914
|
-
if (enabled !== true) {
|
|
1915
|
-
return deny(`Tool "${name}" has not opted in to CodeCall`);
|
|
1916
|
-
}
|
|
1917
|
-
break;
|
|
1918
|
-
default:
|
|
1919
|
-
return deny(`Unknown CodeCall mode: ${String(config.mode)}`);
|
|
1920
|
-
}
|
|
1921
|
-
if (config.includeTools) {
|
|
1922
|
-
const included = config.includeTools({
|
|
1923
|
-
name,
|
|
1924
|
-
appId: tool.appId,
|
|
1925
|
-
source: tool.codecall?.source,
|
|
1926
|
-
description: tool.description,
|
|
1927
|
-
tags: tool.codecall?.tags ?? tool.tags
|
|
1928
|
-
});
|
|
1929
|
-
if (!included) {
|
|
1930
|
-
return deny(`Tool "${name}" is excluded by the includeTools filter`);
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
return ALLOWED;
|
|
1934
|
-
}
|
|
1935
|
-
function denyUnknownTool(name) {
|
|
1936
|
-
return deny(`Tool "${name}" is not available through CodeCall`);
|
|
1937
|
-
}
|
|
1938
|
-
function resolveCodeCallTool(scope, name) {
|
|
1939
|
-
const typedScope = scope;
|
|
1940
|
-
const alias = /[-_]/.test(name) ? name.includes("_") ? name.replace(/_/g, "-") : name.replace(/-/g, "_") : void 0;
|
|
1941
|
-
const candidates = alias ? [name, alias] : [name];
|
|
1942
|
-
const matches = (entry) => candidates.includes(entry.fullName) || candidates.includes(entry.name);
|
|
1943
|
-
const local = typedScope.tools?.getTools(true)?.find(matches);
|
|
1944
|
-
if (local) return local;
|
|
1945
|
-
const registries = typedScope.providers?.getRegistries?.("AppRegistry") ?? [];
|
|
1946
|
-
for (const registry of registries) {
|
|
1947
|
-
for (const app of registry.getApps()) {
|
|
1948
|
-
if (!app.isRemote) continue;
|
|
1949
|
-
const remote = app.tools.getTools(true).find(matches);
|
|
1950
|
-
if (remote) return remote;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
return void 0;
|
|
1954
|
-
}
|
|
1955
|
-
function checkDirectCallPolicy(tool, directCalls) {
|
|
1956
|
-
if (!directCalls) return ALLOWED;
|
|
1957
|
-
if (directCalls.enabled === false) {
|
|
1958
|
-
return deny("Direct tool invocation is disabled");
|
|
1959
|
-
}
|
|
1960
|
-
if (directCalls.allowedTools && !directCalls.allowedTools.includes(tool.name)) {
|
|
1961
|
-
return deny(`Tool "${tool.name}" is not in the direct-call allowlist`);
|
|
1962
|
-
}
|
|
1963
|
-
if (directCalls.filter) {
|
|
1964
|
-
const permitted = directCalls.filter({
|
|
1965
|
-
name: tool.name,
|
|
1966
|
-
appId: tool.appId,
|
|
1967
|
-
source: tool.codecall?.source,
|
|
1968
|
-
tags: tool.codecall?.tags ?? tool.tags
|
|
1969
|
-
});
|
|
1970
|
-
if (!permitted) {
|
|
1971
|
-
return deny(`Tool "${tool.name}" is excluded by the directCalls filter`);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
return ALLOWED;
|
|
1975
|
-
}
|
|
1976
|
-
function checkCodeCallToolAccess(scope, config, name, options = {}) {
|
|
1977
|
-
const entry = resolveCodeCallTool(scope, name);
|
|
1978
|
-
if (!entry) return denyUnknownTool(name);
|
|
1979
|
-
const metadata = entry.metadata;
|
|
1980
|
-
const owner = entry.owner;
|
|
1981
|
-
const policyTool = {
|
|
1982
|
-
// The qualified name is the subject: `call-tool.flow.ts` dispatches `fullName`, so the
|
|
1983
|
-
// policy has to judge the same string the flow will run.
|
|
1984
|
-
name: entry.fullName || entry.name,
|
|
1985
|
-
aliases: [entry.name, name],
|
|
1986
|
-
appId: owner?.kind === "app" ? owner.id : void 0,
|
|
1987
|
-
description: metadata?.description,
|
|
1988
|
-
tags: metadata?.tags,
|
|
1989
|
-
hideFromDiscovery: metadata?.hideFromDiscovery,
|
|
1990
|
-
codecall: metadata?.codecall
|
|
1991
|
-
};
|
|
1992
|
-
const baseDecision = checkCodeCallToolPolicy(policyTool, {
|
|
1993
|
-
// An absent mode means an unparsed/partial config, not a hostile one: fall back to the
|
|
1994
|
-
// schema's own documented default rather than denying every call. An unrecognised mode
|
|
1995
|
-
// still fails closed inside the policy's switch.
|
|
1996
|
-
mode: config.get("mode") ?? "codecall_only",
|
|
1997
|
-
includeTools: config.get("includeTools")
|
|
1998
|
-
});
|
|
1999
|
-
if (!baseDecision.allowed) return baseDecision;
|
|
2000
|
-
if (!options.directCall) return ALLOWED;
|
|
2001
|
-
return checkDirectCallPolicy(policyTool, config.get("directCalls"));
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
2268
|
// plugins/plugin-codecall/src/errors/tool-call.errors.ts
|
|
2005
2269
|
var TOOL_CALL_ERROR_CODES = {
|
|
2006
2270
|
NOT_FOUND: "NOT_FOUND",
|
|
@@ -2503,21 +2767,24 @@ var describeToolOutputSchema = z5.object({
|
|
|
2503
2767
|
var DescribeTool = class extends ToolContext4 {
|
|
2504
2768
|
async execute(input) {
|
|
2505
2769
|
const { toolNames } = input;
|
|
2770
|
+
const audit = this.tryGet(AuditLoggerService);
|
|
2771
|
+
const executionId = audit ? audit.generateExecutionId() : "";
|
|
2772
|
+
const startedAt = Date.now();
|
|
2506
2773
|
const tools = [];
|
|
2507
2774
|
const notFound = [];
|
|
2508
|
-
const
|
|
2509
|
-
const toolMap = new Map(allTools.map((t) => [t.name, t]));
|
|
2510
|
-
const fullNameMap = new Map(allTools.map((t) => [t.fullName, t]));
|
|
2775
|
+
const config = this.get(CodeCallConfig);
|
|
2511
2776
|
for (const toolName of toolNames) {
|
|
2512
2777
|
if (isBlockedSelfReference(toolName)) {
|
|
2513
2778
|
notFound.push(toolName);
|
|
2514
2779
|
continue;
|
|
2515
2780
|
}
|
|
2516
|
-
const
|
|
2517
|
-
if (!
|
|
2781
|
+
const access = checkCodeCallToolAccess(this.scope, config, toolName);
|
|
2782
|
+
if (!access.allowed) {
|
|
2783
|
+
audit?.logSecurityAccessDenied(executionId, toolName, access.reason);
|
|
2518
2784
|
notFound.push(toolName);
|
|
2519
2785
|
continue;
|
|
2520
2786
|
}
|
|
2787
|
+
const tool = access.entry;
|
|
2521
2788
|
const appId = this.extractAppId(tool);
|
|
2522
2789
|
const inputSchema = this.getInputSchema(tool);
|
|
2523
2790
|
const outputSchema = this.toJsonSchema(tool.rawOutputSchema ?? tool.outputSchema);
|
|
@@ -2532,6 +2799,7 @@ var DescribeTool = class extends ToolContext4 {
|
|
|
2532
2799
|
usageExamples
|
|
2533
2800
|
});
|
|
2534
2801
|
}
|
|
2802
|
+
audit?.logDescribe(executionId, toolNames, Date.now() - startedAt);
|
|
2535
2803
|
return {
|
|
2536
2804
|
tools,
|
|
2537
2805
|
notFound: notFound.length > 0 ? notFound : void 0
|
|
@@ -2799,13 +3067,21 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2799
3067
|
}
|
|
2800
3068
|
async execute(input) {
|
|
2801
3069
|
const { script, allowedTools } = input;
|
|
3070
|
+
const audit = this.tryGet(AuditLoggerService);
|
|
3071
|
+
const executionId = audit ? audit.generateExecutionId() : "";
|
|
3072
|
+
const callDepth = 1;
|
|
3073
|
+
let toolCallCount = 0;
|
|
2802
3074
|
const allowedToolSet = allowedTools ? new Set(allowedTools) : null;
|
|
2803
3075
|
const environment = {
|
|
2804
3076
|
callTool: async (name, toolInput, options) => {
|
|
2805
3077
|
const throwOnError = options?.throwOnError !== false;
|
|
3078
|
+
if (isBlockedSelfReference(name)) {
|
|
3079
|
+
audit?.logSecuritySelfReference(executionId, name);
|
|
3080
|
+
}
|
|
2806
3081
|
assertNotSelfReference(name);
|
|
2807
3082
|
const decision = this.checkToolPolicy(name);
|
|
2808
3083
|
if (!decision.allowed) {
|
|
3084
|
+
audit?.logSecurityAccessDenied(executionId, name, decision.reason);
|
|
2809
3085
|
const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name, decision.reason);
|
|
2810
3086
|
if (throwOnError) {
|
|
2811
3087
|
throw error;
|
|
@@ -2813,12 +3089,16 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2813
3089
|
return { success: false, error };
|
|
2814
3090
|
}
|
|
2815
3091
|
if (allowedToolSet && !allowedToolSet.has(name)) {
|
|
3092
|
+
audit?.logSecurityAccessDenied(executionId, name, "not in the caller-supplied allowedTools list");
|
|
2816
3093
|
const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name);
|
|
2817
3094
|
if (throwOnError) {
|
|
2818
3095
|
throw error;
|
|
2819
3096
|
}
|
|
2820
3097
|
return { success: false, error };
|
|
2821
3098
|
}
|
|
3099
|
+
toolCallCount += 1;
|
|
3100
|
+
audit?.logToolCallStart(executionId, name, callDepth);
|
|
3101
|
+
const toolCallStartedAt = Date.now();
|
|
2822
3102
|
try {
|
|
2823
3103
|
const request = {
|
|
2824
3104
|
method: "tools/call",
|
|
@@ -2832,6 +3112,13 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2832
3112
|
};
|
|
2833
3113
|
const mcpResult = await this.scope.runFlow("tools:call-tool", { request, ctx });
|
|
2834
3114
|
if (!mcpResult) {
|
|
3115
|
+
audit?.logToolCallFailure(
|
|
3116
|
+
executionId,
|
|
3117
|
+
name,
|
|
3118
|
+
callDepth,
|
|
3119
|
+
Date.now() - toolCallStartedAt,
|
|
3120
|
+
TOOL_CALL_ERROR_CODES.EXECUTION
|
|
3121
|
+
);
|
|
2835
3122
|
const error = createToolCallError(TOOL_CALL_ERROR_CODES.EXECUTION, name, "Flow returned no result");
|
|
2836
3123
|
if (throwOnError) {
|
|
2837
3124
|
throw error;
|
|
@@ -2839,6 +3126,7 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2839
3126
|
return { success: false, error };
|
|
2840
3127
|
}
|
|
2841
3128
|
const result = extractResultFromCallToolResult(mcpResult);
|
|
3129
|
+
audit?.logToolCallSuccess(executionId, name, callDepth, Date.now() - toolCallStartedAt);
|
|
2842
3130
|
if (throwOnError) {
|
|
2843
3131
|
return result;
|
|
2844
3132
|
}
|
|
@@ -2846,6 +3134,7 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2846
3134
|
} catch (error) {
|
|
2847
3135
|
const errorCode = getErrorCode(error);
|
|
2848
3136
|
const rawMessage = error instanceof Error ? error.message : void 0;
|
|
3137
|
+
audit?.logToolCallFailure(executionId, name, callDepth, Date.now() - toolCallStartedAt, errorCode);
|
|
2849
3138
|
const sanitizedError = createToolCallError(errorCode, name, rawMessage);
|
|
2850
3139
|
if (throwOnError) {
|
|
2851
3140
|
throw sanitizedError;
|
|
@@ -2855,12 +3144,17 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2855
3144
|
},
|
|
2856
3145
|
getTool: (name) => {
|
|
2857
3146
|
try {
|
|
2858
|
-
if (isBlockedSelfReference(name))
|
|
2859
|
-
|
|
3147
|
+
if (isBlockedSelfReference(name)) {
|
|
3148
|
+
audit?.logSecuritySelfReference(executionId, name);
|
|
3149
|
+
return void 0;
|
|
3150
|
+
}
|
|
3151
|
+
const introspectionAccess = this.checkToolPolicy(name);
|
|
3152
|
+
if (!introspectionAccess.allowed) {
|
|
3153
|
+
audit?.logSecurityAccessDenied(executionId, name, introspectionAccess.reason);
|
|
3154
|
+
return void 0;
|
|
3155
|
+
}
|
|
2860
3156
|
if (allowedToolSet && !allowedToolSet.has(name)) return void 0;
|
|
2861
|
-
const
|
|
2862
|
-
const tool = tools.find((t) => t.name === name || t.fullName === name);
|
|
2863
|
-
if (!tool) return void 0;
|
|
3157
|
+
const tool = introspectionAccess.entry;
|
|
2864
3158
|
return toPlainJson({
|
|
2865
3159
|
name: tool.name,
|
|
2866
3160
|
description: tool.metadata?.description,
|
|
@@ -2880,8 +3174,11 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2880
3174
|
}
|
|
2881
3175
|
};
|
|
2882
3176
|
try {
|
|
2883
|
-
const
|
|
2884
|
-
const
|
|
3177
|
+
const policyConfig = readCodeCallPolicyConfig(this.get(CodeCallConfig));
|
|
3178
|
+
const callableTools = this.scope.tools.getTools(true).filter(
|
|
3179
|
+
(tool) => checkCodeCallToolPolicy(toCodeCallPolicyTool(tool, void 0, this.scope), policyConfig).allowed
|
|
3180
|
+
);
|
|
3181
|
+
const { namespaces, skipped } = buildToolNamespaces(callableTools, environment.callTool);
|
|
2885
3182
|
environment.namespaces = namespaces;
|
|
2886
3183
|
if (skipped.length > 0) {
|
|
2887
3184
|
this.logger?.debug?.("codecall: tools skipped during namespace generation", {
|
|
@@ -2897,9 +3194,14 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2897
3194
|
}
|
|
2898
3195
|
const enclaveService = this.get(EnclaveService);
|
|
2899
3196
|
const config = this.get(CodeCallConfig);
|
|
3197
|
+
const startedAt = Date.now();
|
|
3198
|
+
const elapsed = (reported) => reported ?? Date.now() - startedAt;
|
|
3199
|
+
audit?.logExecutionStart(executionId, script);
|
|
2900
3200
|
try {
|
|
2901
3201
|
const executionResult = await enclaveService.execute(script, environment);
|
|
3202
|
+
const durationMs = elapsed(executionResult.stats?.duration);
|
|
2902
3203
|
if (executionResult.timedOut) {
|
|
3204
|
+
audit?.logExecutionTimeout(executionId, script, durationMs);
|
|
2903
3205
|
return {
|
|
2904
3206
|
status: "timeout",
|
|
2905
3207
|
error: {
|
|
@@ -2910,6 +3212,8 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2910
3212
|
if (!executionResult.success) {
|
|
2911
3213
|
const error = executionResult.error;
|
|
2912
3214
|
if (error.code === "VALIDATION_ERROR" || error.name === "ValidationError") {
|
|
3215
|
+
audit?.logSecurityAstBlocked(executionId, error.blockedPatterns?.join(", ") ?? "UNKNOWN", error.message);
|
|
3216
|
+
audit?.logExecutionFailure(executionId, script, durationMs, error.message);
|
|
2913
3217
|
return {
|
|
2914
3218
|
status: "illegal_access",
|
|
2915
3219
|
error: {
|
|
@@ -2919,6 +3223,7 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2919
3223
|
};
|
|
2920
3224
|
}
|
|
2921
3225
|
if (error.toolName) {
|
|
3226
|
+
audit?.logExecutionFailure(executionId, script, durationMs, error.message);
|
|
2922
3227
|
return {
|
|
2923
3228
|
status: "tool_error",
|
|
2924
3229
|
error: {
|
|
@@ -2931,6 +3236,7 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2931
3236
|
}
|
|
2932
3237
|
};
|
|
2933
3238
|
}
|
|
3239
|
+
audit?.logExecutionFailure(executionId, script, durationMs, error.message);
|
|
2934
3240
|
return {
|
|
2935
3241
|
status: "runtime_error",
|
|
2936
3242
|
error: {
|
|
@@ -2941,6 +3247,7 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2941
3247
|
}
|
|
2942
3248
|
};
|
|
2943
3249
|
}
|
|
3250
|
+
audit?.logExecutionSuccess(executionId, script, durationMs, toolCallCount);
|
|
2944
3251
|
return {
|
|
2945
3252
|
status: "ok",
|
|
2946
3253
|
result: executionResult.result,
|
|
@@ -2949,6 +3256,7 @@ var ExecuteTool = class extends ToolContext5 {
|
|
|
2949
3256
|
} catch (error) {
|
|
2950
3257
|
const errorName = error instanceof Error ? error.name : "Error";
|
|
2951
3258
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3259
|
+
audit?.logExecutionFailure(executionId, script, elapsed(), errorMessage);
|
|
2952
3260
|
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
2953
3261
|
const errorLoc = error.loc;
|
|
2954
3262
|
if (errorName === "SyntaxError" || errorMessage?.includes("syntax")) {
|
|
@@ -3026,13 +3334,18 @@ function buildErrorResult(message) {
|
|
|
3026
3334
|
var InvokeTool = class extends ToolContext6 {
|
|
3027
3335
|
async execute(input) {
|
|
3028
3336
|
const { tool: toolName, input: toolInput } = input;
|
|
3337
|
+
const audit = this.tryGet(AuditLoggerService);
|
|
3338
|
+
const executionId = audit ? audit.generateExecutionId() : "";
|
|
3339
|
+
const startedAt = Date.now();
|
|
3029
3340
|
if (isBlockedSelfReference(toolName)) {
|
|
3341
|
+
audit?.logSecuritySelfReference(executionId, toolName);
|
|
3030
3342
|
return buildErrorResult(
|
|
3031
3343
|
`Tool "${toolName}" cannot be invoked directly. CodeCall tools are internal and not accessible via codecall:invoke.`
|
|
3032
3344
|
);
|
|
3033
3345
|
}
|
|
3034
3346
|
const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, { directCall: true });
|
|
3035
3347
|
if (!decision.allowed) {
|
|
3348
|
+
audit?.logSecurityAccessDenied(executionId, toolName, decision.reason);
|
|
3036
3349
|
return buildErrorResult(`Tool "${toolName}" is not available. Use codecall:search to discover available tools.`);
|
|
3037
3350
|
}
|
|
3038
3351
|
const request = {
|
|
@@ -3047,8 +3360,10 @@ var InvokeTool = class extends ToolContext6 {
|
|
|
3047
3360
|
};
|
|
3048
3361
|
const result = await this.scope.runFlow("tools:call-tool", { request, ctx });
|
|
3049
3362
|
if (!result) {
|
|
3363
|
+
audit?.logInvoke(executionId, toolName, false, Date.now() - startedAt);
|
|
3050
3364
|
return buildErrorResult(`Tool "${toolName}" not found. Use codecall:search to discover available tools.`);
|
|
3051
3365
|
}
|
|
3366
|
+
audit?.logInvoke(executionId, toolName, true, Date.now() - startedAt);
|
|
3052
3367
|
return result;
|
|
3053
3368
|
}
|
|
3054
3369
|
};
|
|
@@ -3071,6 +3386,10 @@ InvokeTool = __decorateClass([
|
|
|
3071
3386
|
], InvokeTool);
|
|
3072
3387
|
|
|
3073
3388
|
// plugins/plugin-codecall/src/codecall.plugin.ts
|
|
3389
|
+
function auditLogFields(event) {
|
|
3390
|
+
const { executionId, durationMs, data } = event;
|
|
3391
|
+
return durationMs === void 0 ? { executionId, ...data } : { executionId, durationMs, ...data };
|
|
3392
|
+
}
|
|
3074
3393
|
var CodeCallPlugin = class extends DynamicPlugin {
|
|
3075
3394
|
options;
|
|
3076
3395
|
cachedLogger;
|
|
@@ -3105,6 +3424,21 @@ var CodeCallPlugin = class extends DynamicPlugin {
|
|
|
3105
3424
|
return new EnclaveService(cfg);
|
|
3106
3425
|
}
|
|
3107
3426
|
},
|
|
3427
|
+
{
|
|
3428
|
+
// GHSA-adjacent: production.mdx promised operators these events and nothing emitted them,
|
|
3429
|
+
// because the service was never registered. The subscription lives here, in the factory,
|
|
3430
|
+
// for a reason: there is no provider teardown hook in the SDK, so subscribing anywhere
|
|
3431
|
+
// per-request would grow the listener Set for the life of the process.
|
|
3432
|
+
name: "codecall:audit-logger",
|
|
3433
|
+
provide: AuditLoggerService,
|
|
3434
|
+
inject: () => [ScopeEntry],
|
|
3435
|
+
useFactory: (scope) => {
|
|
3436
|
+
const audit = new AuditLoggerService();
|
|
3437
|
+
const logger = scope.logger.child("codecall:audit");
|
|
3438
|
+
audit.subscribe((event) => logger.info(event.type, auditLogFields(event)));
|
|
3439
|
+
return audit;
|
|
3440
|
+
}
|
|
3441
|
+
},
|
|
3108
3442
|
{
|
|
3109
3443
|
name: "codecall:tool-search",
|
|
3110
3444
|
provide: ToolSearchService,
|
|
@@ -3202,6 +3536,8 @@ CodeCallPlugin = __decorateClass([
|
|
|
3202
3536
|
})
|
|
3203
3537
|
], CodeCallPlugin);
|
|
3204
3538
|
export {
|
|
3539
|
+
AUDIT_EVENT_TYPES,
|
|
3540
|
+
AuditLoggerService,
|
|
3205
3541
|
CodeCallPlugin,
|
|
3206
3542
|
codeCallDirectCallsOptionsSchema,
|
|
3207
3543
|
codeCallEmbeddingOptionsSchema,
|