@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/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 = [];
@@ -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 allTools = this.scope.tools.getTools(true);
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 tool = toolMap.get(toolName) || fullNameMap.get(toolName);
2517
- if (!tool) {
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)) return void 0;
2859
- if (!this.checkToolPolicy(name).allowed) 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
+ }
2860
3156
  if (allowedToolSet && !allowedToolSet.has(name)) return void 0;
2861
- const tools = this.scope.tools.getTools(true);
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 registeredTools = this.scope.tools.getTools(true);
2884
- 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);
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,