@frontmcp/plugin-codecall 1.7.2 → 1.8.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/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
- * Determines if a tool should be indexed in the search database.
1047
- * Filters based on:
1048
- * - Excludes codecall:* meta-tools (they should not be searchable)
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
- const toolName = tool.name || tool.fullName;
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
- if (!tool.owner) return void 0;
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 Provider2, ProviderScope as ProviderScope2 } from "@frontmcp/sdk";
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
- Provider2({
1828
+ Provider3({
1450
1829
  name: "codecall:enclave",
1451
1830
  description: "Executes AgentScript code in a secure enclave",
1452
- scope: ProviderScope2.GLOBAL
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 = [];
@@ -2384,21 +2767,24 @@ var describeToolOutputSchema = z5.object({
2384
2767
  var DescribeTool = class extends ToolContext4 {
2385
2768
  async execute(input) {
2386
2769
  const { toolNames } = input;
2770
+ const audit = this.tryGet(AuditLoggerService);
2771
+ const executionId = audit ? audit.generateExecutionId() : "";
2772
+ const startedAt = Date.now();
2387
2773
  const tools = [];
2388
2774
  const notFound = [];
2389
- const allTools = this.scope.tools.getTools(true);
2390
- const toolMap = new Map(allTools.map((t) => [t.name, t]));
2391
- const fullNameMap = new Map(allTools.map((t) => [t.fullName, t]));
2775
+ const config = this.get(CodeCallConfig);
2392
2776
  for (const toolName of toolNames) {
2393
2777
  if (isBlockedSelfReference(toolName)) {
2394
2778
  notFound.push(toolName);
2395
2779
  continue;
2396
2780
  }
2397
- const tool = toolMap.get(toolName) || fullNameMap.get(toolName);
2398
- if (!tool) {
2781
+ const access = checkCodeCallToolAccess(this.scope, config, toolName);
2782
+ if (!access.allowed) {
2783
+ audit?.logSecurityAccessDenied(executionId, toolName, access.reason);
2399
2784
  notFound.push(toolName);
2400
2785
  continue;
2401
2786
  }
2787
+ const tool = access.entry;
2402
2788
  const appId = this.extractAppId(tool);
2403
2789
  const inputSchema = this.getInputSchema(tool);
2404
2790
  const outputSchema = this.toJsonSchema(tool.rawOutputSchema ?? tool.outputSchema);
@@ -2413,6 +2799,7 @@ var DescribeTool = class extends ToolContext4 {
2413
2799
  usageExamples
2414
2800
  });
2415
2801
  }
2802
+ audit?.logDescribe(executionId, toolNames, Date.now() - startedAt);
2416
2803
  return {
2417
2804
  tools,
2418
2805
  notFound: notFound.length > 0 ? notFound : void 0
@@ -2667,20 +3054,51 @@ function getErrorCode(error) {
2667
3054
  return TOOL_CALL_ERROR_CODES.EXECUTION;
2668
3055
  }
2669
3056
  var ExecuteTool = class extends ToolContext5 {
3057
+ /**
3058
+ * Resolve the server-side CodeCall policy for a tool name (GHSA-6w3j-82v5-6qrr).
3059
+ *
3060
+ * Shares its decision with discovery and with `codecall:invoke`, so the three cannot
3061
+ * diverge. A name matching no registered tool is denied rather than passed through —
3062
+ * otherwise it would skip every metadata-driven check and reach `tools:call-tool`
3063
+ * unexamined.
3064
+ */
3065
+ checkToolPolicy(name) {
3066
+ return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name);
3067
+ }
2670
3068
  async execute(input) {
2671
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;
2672
3074
  const allowedToolSet = allowedTools ? new Set(allowedTools) : null;
2673
3075
  const environment = {
2674
3076
  callTool: async (name, toolInput, options) => {
2675
3077
  const throwOnError = options?.throwOnError !== false;
3078
+ if (isBlockedSelfReference(name)) {
3079
+ audit?.logSecuritySelfReference(executionId, name);
3080
+ }
2676
3081
  assertNotSelfReference(name);
3082
+ const decision = this.checkToolPolicy(name);
3083
+ if (!decision.allowed) {
3084
+ audit?.logSecurityAccessDenied(executionId, name, decision.reason);
3085
+ const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name, decision.reason);
3086
+ if (throwOnError) {
3087
+ throw error;
3088
+ }
3089
+ return { success: false, error };
3090
+ }
2677
3091
  if (allowedToolSet && !allowedToolSet.has(name)) {
3092
+ audit?.logSecurityAccessDenied(executionId, name, "not in the caller-supplied allowedTools list");
2678
3093
  const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name);
2679
3094
  if (throwOnError) {
2680
3095
  throw error;
2681
3096
  }
2682
3097
  return { success: false, error };
2683
3098
  }
3099
+ toolCallCount += 1;
3100
+ audit?.logToolCallStart(executionId, name, callDepth);
3101
+ const toolCallStartedAt = Date.now();
2684
3102
  try {
2685
3103
  const request = {
2686
3104
  method: "tools/call",
@@ -2694,6 +3112,13 @@ var ExecuteTool = class extends ToolContext5 {
2694
3112
  };
2695
3113
  const mcpResult = await this.scope.runFlow("tools:call-tool", { request, ctx });
2696
3114
  if (!mcpResult) {
3115
+ audit?.logToolCallFailure(
3116
+ executionId,
3117
+ name,
3118
+ callDepth,
3119
+ Date.now() - toolCallStartedAt,
3120
+ TOOL_CALL_ERROR_CODES.EXECUTION
3121
+ );
2697
3122
  const error = createToolCallError(TOOL_CALL_ERROR_CODES.EXECUTION, name, "Flow returned no result");
2698
3123
  if (throwOnError) {
2699
3124
  throw error;
@@ -2701,6 +3126,7 @@ var ExecuteTool = class extends ToolContext5 {
2701
3126
  return { success: false, error };
2702
3127
  }
2703
3128
  const result = extractResultFromCallToolResult(mcpResult);
3129
+ audit?.logToolCallSuccess(executionId, name, callDepth, Date.now() - toolCallStartedAt);
2704
3130
  if (throwOnError) {
2705
3131
  return result;
2706
3132
  }
@@ -2708,6 +3134,7 @@ var ExecuteTool = class extends ToolContext5 {
2708
3134
  } catch (error) {
2709
3135
  const errorCode = getErrorCode(error);
2710
3136
  const rawMessage = error instanceof Error ? error.message : void 0;
3137
+ audit?.logToolCallFailure(executionId, name, callDepth, Date.now() - toolCallStartedAt, errorCode);
2711
3138
  const sanitizedError = createToolCallError(errorCode, name, rawMessage);
2712
3139
  if (throwOnError) {
2713
3140
  throw sanitizedError;
@@ -2717,11 +3144,17 @@ var ExecuteTool = class extends ToolContext5 {
2717
3144
  },
2718
3145
  getTool: (name) => {
2719
3146
  try {
2720
- if (isBlockedSelfReference(name)) return void 0;
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
+ }
2721
3156
  if (allowedToolSet && !allowedToolSet.has(name)) return void 0;
2722
- const tools = this.scope.tools.getTools(true);
2723
- const tool = tools.find((t) => t.name === name || t.fullName === name);
2724
- if (!tool) return void 0;
3157
+ const tool = introspectionAccess.entry;
2725
3158
  return toPlainJson({
2726
3159
  name: tool.name,
2727
3160
  description: tool.metadata?.description,
@@ -2741,8 +3174,11 @@ var ExecuteTool = class extends ToolContext5 {
2741
3174
  }
2742
3175
  };
2743
3176
  try {
2744
- const registeredTools = this.scope.tools.getTools(true);
2745
- const { namespaces, skipped } = buildToolNamespaces(registeredTools, environment.callTool);
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);
2746
3182
  environment.namespaces = namespaces;
2747
3183
  if (skipped.length > 0) {
2748
3184
  this.logger?.debug?.("codecall: tools skipped during namespace generation", {
@@ -2758,9 +3194,14 @@ var ExecuteTool = class extends ToolContext5 {
2758
3194
  }
2759
3195
  const enclaveService = this.get(EnclaveService);
2760
3196
  const config = this.get(CodeCallConfig);
3197
+ const startedAt = Date.now();
3198
+ const elapsed = (reported) => reported ?? Date.now() - startedAt;
3199
+ audit?.logExecutionStart(executionId, script);
2761
3200
  try {
2762
3201
  const executionResult = await enclaveService.execute(script, environment);
3202
+ const durationMs = elapsed(executionResult.stats?.duration);
2763
3203
  if (executionResult.timedOut) {
3204
+ audit?.logExecutionTimeout(executionId, script, durationMs);
2764
3205
  return {
2765
3206
  status: "timeout",
2766
3207
  error: {
@@ -2771,6 +3212,8 @@ var ExecuteTool = class extends ToolContext5 {
2771
3212
  if (!executionResult.success) {
2772
3213
  const error = executionResult.error;
2773
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);
2774
3217
  return {
2775
3218
  status: "illegal_access",
2776
3219
  error: {
@@ -2780,6 +3223,7 @@ var ExecuteTool = class extends ToolContext5 {
2780
3223
  };
2781
3224
  }
2782
3225
  if (error.toolName) {
3226
+ audit?.logExecutionFailure(executionId, script, durationMs, error.message);
2783
3227
  return {
2784
3228
  status: "tool_error",
2785
3229
  error: {
@@ -2792,6 +3236,7 @@ var ExecuteTool = class extends ToolContext5 {
2792
3236
  }
2793
3237
  };
2794
3238
  }
3239
+ audit?.logExecutionFailure(executionId, script, durationMs, error.message);
2795
3240
  return {
2796
3241
  status: "runtime_error",
2797
3242
  error: {
@@ -2802,6 +3247,7 @@ var ExecuteTool = class extends ToolContext5 {
2802
3247
  }
2803
3248
  };
2804
3249
  }
3250
+ audit?.logExecutionSuccess(executionId, script, durationMs, toolCallCount);
2805
3251
  return {
2806
3252
  status: "ok",
2807
3253
  result: executionResult.result,
@@ -2810,6 +3256,7 @@ var ExecuteTool = class extends ToolContext5 {
2810
3256
  } catch (error) {
2811
3257
  const errorName = error instanceof Error ? error.name : "Error";
2812
3258
  const errorMessage = error instanceof Error ? error.message : String(error);
3259
+ audit?.logExecutionFailure(executionId, script, elapsed(), errorMessage);
2813
3260
  const errorStack = error instanceof Error ? error.stack : void 0;
2814
3261
  const errorLoc = error.loc;
2815
3262
  if (errorName === "SyntaxError" || errorMessage?.includes("syntax")) {
@@ -2887,11 +3334,20 @@ function buildErrorResult(message) {
2887
3334
  var InvokeTool = class extends ToolContext6 {
2888
3335
  async execute(input) {
2889
3336
  const { tool: toolName, input: toolInput } = input;
3337
+ const audit = this.tryGet(AuditLoggerService);
3338
+ const executionId = audit ? audit.generateExecutionId() : "";
3339
+ const startedAt = Date.now();
2890
3340
  if (isBlockedSelfReference(toolName)) {
3341
+ audit?.logSecuritySelfReference(executionId, toolName);
2891
3342
  return buildErrorResult(
2892
3343
  `Tool "${toolName}" cannot be invoked directly. CodeCall tools are internal and not accessible via codecall:invoke.`
2893
3344
  );
2894
3345
  }
3346
+ const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, { directCall: true });
3347
+ if (!decision.allowed) {
3348
+ audit?.logSecurityAccessDenied(executionId, toolName, decision.reason);
3349
+ return buildErrorResult(`Tool "${toolName}" is not available. Use codecall:search to discover available tools.`);
3350
+ }
2895
3351
  const request = {
2896
3352
  method: "tools/call",
2897
3353
  params: {
@@ -2904,8 +3360,10 @@ var InvokeTool = class extends ToolContext6 {
2904
3360
  };
2905
3361
  const result = await this.scope.runFlow("tools:call-tool", { request, ctx });
2906
3362
  if (!result) {
3363
+ audit?.logInvoke(executionId, toolName, false, Date.now() - startedAt);
2907
3364
  return buildErrorResult(`Tool "${toolName}" not found. Use codecall:search to discover available tools.`);
2908
3365
  }
3366
+ audit?.logInvoke(executionId, toolName, true, Date.now() - startedAt);
2909
3367
  return result;
2910
3368
  }
2911
3369
  };
@@ -2928,6 +3386,10 @@ InvokeTool = __decorateClass([
2928
3386
  ], InvokeTool);
2929
3387
 
2930
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
+ }
2931
3393
  var CodeCallPlugin = class extends DynamicPlugin {
2932
3394
  options;
2933
3395
  cachedLogger;
@@ -2962,6 +3424,21 @@ var CodeCallPlugin = class extends DynamicPlugin {
2962
3424
  return new EnclaveService(cfg);
2963
3425
  }
2964
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
+ },
2965
3442
  {
2966
3443
  name: "codecall:tool-search",
2967
3444
  provide: ToolSearchService,
@@ -3059,6 +3536,8 @@ CodeCallPlugin = __decorateClass([
3059
3536
  })
3060
3537
  ], CodeCallPlugin);
3061
3538
  export {
3539
+ AUDIT_EVENT_TYPES,
3540
+ AuditLoggerService,
3062
3541
  CodeCallPlugin,
3063
3542
  codeCallDirectCallsOptionsSchema,
3064
3543
  codeCallEmbeddingOptionsSchema,