@frontmcp/plugin-codecall 1.7.2 → 1.8.0

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
@@ -1882,6 +1882,125 @@ SearchKnowledgeTool = __decorateClass([
1882
1882
  import { toJSONSchema, z as z6, ZodType } from "@frontmcp/lazy-zod";
1883
1883
  import { Tool as Tool4, ToolContext as ToolContext4 } from "@frontmcp/sdk";
1884
1884
 
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
+
1885
2004
  // plugins/plugin-codecall/src/errors/tool-call.errors.ts
1886
2005
  var TOOL_CALL_ERROR_CODES = {
1887
2006
  NOT_FOUND: "NOT_FOUND",
@@ -2667,6 +2786,17 @@ function getErrorCode(error) {
2667
2786
  return TOOL_CALL_ERROR_CODES.EXECUTION;
2668
2787
  }
2669
2788
  var ExecuteTool = class extends ToolContext5 {
2789
+ /**
2790
+ * Resolve the server-side CodeCall policy for a tool name (GHSA-6w3j-82v5-6qrr).
2791
+ *
2792
+ * Shares its decision with discovery and with `codecall:invoke`, so the three cannot
2793
+ * diverge. A name matching no registered tool is denied rather than passed through —
2794
+ * otherwise it would skip every metadata-driven check and reach `tools:call-tool`
2795
+ * unexamined.
2796
+ */
2797
+ checkToolPolicy(name) {
2798
+ return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name);
2799
+ }
2670
2800
  async execute(input) {
2671
2801
  const { script, allowedTools } = input;
2672
2802
  const allowedToolSet = allowedTools ? new Set(allowedTools) : null;
@@ -2674,6 +2804,14 @@ var ExecuteTool = class extends ToolContext5 {
2674
2804
  callTool: async (name, toolInput, options) => {
2675
2805
  const throwOnError = options?.throwOnError !== false;
2676
2806
  assertNotSelfReference(name);
2807
+ const decision = this.checkToolPolicy(name);
2808
+ if (!decision.allowed) {
2809
+ const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name, decision.reason);
2810
+ if (throwOnError) {
2811
+ throw error;
2812
+ }
2813
+ return { success: false, error };
2814
+ }
2677
2815
  if (allowedToolSet && !allowedToolSet.has(name)) {
2678
2816
  const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name);
2679
2817
  if (throwOnError) {
@@ -2718,6 +2856,7 @@ var ExecuteTool = class extends ToolContext5 {
2718
2856
  getTool: (name) => {
2719
2857
  try {
2720
2858
  if (isBlockedSelfReference(name)) return void 0;
2859
+ if (!this.checkToolPolicy(name).allowed) return void 0;
2721
2860
  if (allowedToolSet && !allowedToolSet.has(name)) return void 0;
2722
2861
  const tools = this.scope.tools.getTools(true);
2723
2862
  const tool = tools.find((t) => t.name === name || t.fullName === name);
@@ -2892,6 +3031,10 @@ var InvokeTool = class extends ToolContext6 {
2892
3031
  `Tool "${toolName}" cannot be invoked directly. CodeCall tools are internal and not accessible via codecall:invoke.`
2893
3032
  );
2894
3033
  }
3034
+ const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, { directCall: true });
3035
+ if (!decision.allowed) {
3036
+ return buildErrorResult(`Tool "${toolName}" is not available. Use codecall:search to discover available tools.`);
3037
+ }
2895
3038
  const request = {
2896
3039
  method: "tools/call",
2897
3040
  params: {
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-codecall",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "CodeCall plugin for FrontMCP - AgentScript-based meta-tools for orchestrating MCP tools",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -49,13 +49,13 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@enclave-vm/core": "^2.15.2",
52
- "@frontmcp/protocol": "1.7.2",
53
- "@frontmcp/sdk": "1.7.2",
52
+ "@frontmcp/protocol": "1.8.0",
53
+ "@frontmcp/sdk": "1.8.0",
54
54
  "vectoriadb": "^2.3.2"
55
55
  },
56
56
  "peerDependencies": {
57
- "@frontmcp/lazy-zod": "1.7.2",
58
- "@frontmcp/plugin-cache": "1.7.2"
57
+ "@frontmcp/lazy-zod": "1.8.0",
58
+ "@frontmcp/plugin-cache": "1.8.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "reflect-metadata": "^0.2.2"
package/index.js CHANGED
@@ -1919,6 +1919,125 @@ SearchKnowledgeTool = __decorateClass([
1919
1919
  var import_lazy_zod6 = require("@frontmcp/lazy-zod");
1920
1920
  var import_sdk6 = require("@frontmcp/sdk");
1921
1921
 
1922
+ // plugins/plugin-codecall/src/security/codecall-tool-policy.ts
1923
+ var BLOCKED_NAMESPACE_PATTERNS = Object.freeze([/^system:/, /^internal:/, /^__/]);
1924
+ var ALLOWED = { allowed: true };
1925
+ function deny(reason) {
1926
+ return { allowed: false, reason };
1927
+ }
1928
+ function checkCodeCallToolPolicy(tool, config) {
1929
+ const { name } = tool;
1930
+ const names = [name, ...tool.aliases ?? []].filter((candidate) => !!candidate);
1931
+ if (names.some((candidate) => candidate.startsWith("codecall:"))) {
1932
+ return deny("CodeCall meta-tools are not callable from CodeCall");
1933
+ }
1934
+ for (const candidate of names) {
1935
+ if (BLOCKED_NAMESPACE_PATTERNS.some((pattern) => pattern.test(candidate))) {
1936
+ return deny(`Tool "${candidate}" is in a namespace CodeCall never calls`);
1937
+ }
1938
+ }
1939
+ if (tool.hideFromDiscovery === true) {
1940
+ return deny(`Tool "${name}" is hidden from discovery`);
1941
+ }
1942
+ const enabled = tool.codecall?.enabledInCodeCall;
1943
+ switch (config.mode) {
1944
+ case "codecall_only":
1945
+ case "metadata_driven":
1946
+ if (enabled === false) {
1947
+ return deny(`Tool "${name}" sets enabledInCodeCall: false`);
1948
+ }
1949
+ break;
1950
+ case "codecall_opt_in":
1951
+ if (enabled !== true) {
1952
+ return deny(`Tool "${name}" has not opted in to CodeCall`);
1953
+ }
1954
+ break;
1955
+ default:
1956
+ return deny(`Unknown CodeCall mode: ${String(config.mode)}`);
1957
+ }
1958
+ if (config.includeTools) {
1959
+ const included = config.includeTools({
1960
+ name,
1961
+ appId: tool.appId,
1962
+ source: tool.codecall?.source,
1963
+ description: tool.description,
1964
+ tags: tool.codecall?.tags ?? tool.tags
1965
+ });
1966
+ if (!included) {
1967
+ return deny(`Tool "${name}" is excluded by the includeTools filter`);
1968
+ }
1969
+ }
1970
+ return ALLOWED;
1971
+ }
1972
+ function denyUnknownTool(name) {
1973
+ return deny(`Tool "${name}" is not available through CodeCall`);
1974
+ }
1975
+ function resolveCodeCallTool(scope, name) {
1976
+ const typedScope = scope;
1977
+ const alias = /[-_]/.test(name) ? name.includes("_") ? name.replace(/_/g, "-") : name.replace(/-/g, "_") : void 0;
1978
+ const candidates = alias ? [name, alias] : [name];
1979
+ const matches = (entry) => candidates.includes(entry.fullName) || candidates.includes(entry.name);
1980
+ const local = typedScope.tools?.getTools(true)?.find(matches);
1981
+ if (local) return local;
1982
+ const registries = typedScope.providers?.getRegistries?.("AppRegistry") ?? [];
1983
+ for (const registry of registries) {
1984
+ for (const app of registry.getApps()) {
1985
+ if (!app.isRemote) continue;
1986
+ const remote = app.tools.getTools(true).find(matches);
1987
+ if (remote) return remote;
1988
+ }
1989
+ }
1990
+ return void 0;
1991
+ }
1992
+ function checkDirectCallPolicy(tool, directCalls) {
1993
+ if (!directCalls) return ALLOWED;
1994
+ if (directCalls.enabled === false) {
1995
+ return deny("Direct tool invocation is disabled");
1996
+ }
1997
+ if (directCalls.allowedTools && !directCalls.allowedTools.includes(tool.name)) {
1998
+ return deny(`Tool "${tool.name}" is not in the direct-call allowlist`);
1999
+ }
2000
+ if (directCalls.filter) {
2001
+ const permitted = directCalls.filter({
2002
+ name: tool.name,
2003
+ appId: tool.appId,
2004
+ source: tool.codecall?.source,
2005
+ tags: tool.codecall?.tags ?? tool.tags
2006
+ });
2007
+ if (!permitted) {
2008
+ return deny(`Tool "${tool.name}" is excluded by the directCalls filter`);
2009
+ }
2010
+ }
2011
+ return ALLOWED;
2012
+ }
2013
+ function checkCodeCallToolAccess(scope, config, name, options = {}) {
2014
+ const entry = resolveCodeCallTool(scope, name);
2015
+ if (!entry) return denyUnknownTool(name);
2016
+ const metadata = entry.metadata;
2017
+ const owner = entry.owner;
2018
+ const policyTool = {
2019
+ // The qualified name is the subject: `call-tool.flow.ts` dispatches `fullName`, so the
2020
+ // policy has to judge the same string the flow will run.
2021
+ name: entry.fullName || entry.name,
2022
+ aliases: [entry.name, name],
2023
+ appId: owner?.kind === "app" ? owner.id : void 0,
2024
+ description: metadata?.description,
2025
+ tags: metadata?.tags,
2026
+ hideFromDiscovery: metadata?.hideFromDiscovery,
2027
+ codecall: metadata?.codecall
2028
+ };
2029
+ const baseDecision = checkCodeCallToolPolicy(policyTool, {
2030
+ // An absent mode means an unparsed/partial config, not a hostile one: fall back to the
2031
+ // schema's own documented default rather than denying every call. An unrecognised mode
2032
+ // still fails closed inside the policy's switch.
2033
+ mode: config.get("mode") ?? "codecall_only",
2034
+ includeTools: config.get("includeTools")
2035
+ });
2036
+ if (!baseDecision.allowed) return baseDecision;
2037
+ if (!options.directCall) return ALLOWED;
2038
+ return checkDirectCallPolicy(policyTool, config.get("directCalls"));
2039
+ }
2040
+
1922
2041
  // plugins/plugin-codecall/src/errors/tool-call.errors.ts
1923
2042
  var TOOL_CALL_ERROR_CODES = {
1924
2043
  NOT_FOUND: "NOT_FOUND",
@@ -2704,6 +2823,17 @@ function getErrorCode(error) {
2704
2823
  return TOOL_CALL_ERROR_CODES.EXECUTION;
2705
2824
  }
2706
2825
  var ExecuteTool = class extends import_sdk7.ToolContext {
2826
+ /**
2827
+ * Resolve the server-side CodeCall policy for a tool name (GHSA-6w3j-82v5-6qrr).
2828
+ *
2829
+ * Shares its decision with discovery and with `codecall:invoke`, so the three cannot
2830
+ * diverge. A name matching no registered tool is denied rather than passed through —
2831
+ * otherwise it would skip every metadata-driven check and reach `tools:call-tool`
2832
+ * unexamined.
2833
+ */
2834
+ checkToolPolicy(name) {
2835
+ return checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), name);
2836
+ }
2707
2837
  async execute(input) {
2708
2838
  const { script, allowedTools } = input;
2709
2839
  const allowedToolSet = allowedTools ? new Set(allowedTools) : null;
@@ -2711,6 +2841,14 @@ var ExecuteTool = class extends import_sdk7.ToolContext {
2711
2841
  callTool: async (name, toolInput, options) => {
2712
2842
  const throwOnError = options?.throwOnError !== false;
2713
2843
  assertNotSelfReference(name);
2844
+ const decision = this.checkToolPolicy(name);
2845
+ if (!decision.allowed) {
2846
+ const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name, decision.reason);
2847
+ if (throwOnError) {
2848
+ throw error;
2849
+ }
2850
+ return { success: false, error };
2851
+ }
2714
2852
  if (allowedToolSet && !allowedToolSet.has(name)) {
2715
2853
  const error = createToolCallError(TOOL_CALL_ERROR_CODES.ACCESS_DENIED, name);
2716
2854
  if (throwOnError) {
@@ -2755,6 +2893,7 @@ var ExecuteTool = class extends import_sdk7.ToolContext {
2755
2893
  getTool: (name) => {
2756
2894
  try {
2757
2895
  if (isBlockedSelfReference(name)) return void 0;
2896
+ if (!this.checkToolPolicy(name).allowed) return void 0;
2758
2897
  if (allowedToolSet && !allowedToolSet.has(name)) return void 0;
2759
2898
  const tools = this.scope.tools.getTools(true);
2760
2899
  const tool = tools.find((t) => t.name === name || t.fullName === name);
@@ -2929,6 +3068,10 @@ var InvokeTool = class extends import_sdk8.ToolContext {
2929
3068
  `Tool "${toolName}" cannot be invoked directly. CodeCall tools are internal and not accessible via codecall:invoke.`
2930
3069
  );
2931
3070
  }
3071
+ const decision = checkCodeCallToolAccess(this.scope, this.get(CodeCallConfig), toolName, { directCall: true });
3072
+ if (!decision.allowed) {
3073
+ return buildErrorResult(`Tool "${toolName}" is not available. Use codecall:search to discover available tools.`);
3074
+ }
2932
3075
  const request = {
2933
3076
  method: "tools/call",
2934
3077
  params: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-codecall",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "CodeCall plugin for FrontMCP - AgentScript-based meta-tools for orchestrating MCP tools",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -49,13 +49,13 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@enclave-vm/core": "^2.15.2",
52
- "@frontmcp/protocol": "1.7.2",
53
- "@frontmcp/sdk": "1.7.2",
52
+ "@frontmcp/protocol": "1.8.0",
53
+ "@frontmcp/sdk": "1.8.0",
54
54
  "vectoriadb": "^2.3.2"
55
55
  },
56
56
  "peerDependencies": {
57
- "@frontmcp/lazy-zod": "1.7.2",
58
- "@frontmcp/plugin-cache": "1.7.2"
57
+ "@frontmcp/lazy-zod": "1.8.0",
58
+ "@frontmcp/plugin-cache": "1.8.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "reflect-metadata": "^0.2.2"
@@ -0,0 +1,89 @@
1
+ import type { CodeCallMode, CodeCallToolMetadata, DirectCallsFilterFn, IncludeToolsFilterFn } from '../codecall.types';
2
+ /** The subset of a ToolEntry this decision needs, so callers need not pass the whole entry. */
3
+ export interface CodeCallPolicyTool {
4
+ /** Canonical, most-qualified name — `fullName` where the entry has one. */
5
+ name: string;
6
+ /**
7
+ * Every other name this tool answers to, including the name the caller asked for.
8
+ *
9
+ * The namespace rules run over all of them. A tool can carry a bare `name` and a qualified
10
+ * `fullName` (`wipeConfig` / `system:wipeConfig`), and the flow dispatches the qualified
11
+ * one — so judging a single spelling lets the other one through.
12
+ */
13
+ aliases?: string[];
14
+ appId?: string;
15
+ description?: string;
16
+ tags?: string[];
17
+ hideFromDiscovery?: boolean;
18
+ codecall?: CodeCallToolMetadata;
19
+ }
20
+ export interface CodeCallPolicyConfig {
21
+ mode: CodeCallMode;
22
+ includeTools?: IncludeToolsFilterFn;
23
+ }
24
+ /** The `directCalls` options, which narrow `codecall:invoke` beyond the base policy. */
25
+ export interface CodeCallDirectCallsConfig {
26
+ enabled: boolean;
27
+ allowedTools?: string[];
28
+ filter?: DirectCallsFilterFn;
29
+ }
30
+ export type CodeCallPolicyDecision = {
31
+ allowed: true;
32
+ } | {
33
+ allowed: false;
34
+ reason: string;
35
+ };
36
+ /**
37
+ * Decide whether CodeCall may reach `tool`.
38
+ *
39
+ * Discovery and execution both call this. A tool the policy denies must be absent from
40
+ * search results AND rejected at `callTool` — hiding it from search is not authorization.
41
+ */
42
+ export declare function checkCodeCallToolPolicy(tool: CodeCallPolicyTool, config: CodeCallPolicyConfig): CodeCallPolicyDecision;
43
+ /**
44
+ * A tool the policy cannot evaluate is denied.
45
+ *
46
+ * `callTool` receives a bare string, so a name matching no resolvable tool would otherwise
47
+ * skip every metadata-driven check above and fall straight through to `tools:call-tool`.
48
+ */
49
+ export declare function denyUnknownTool(name: string): CodeCallPolicyDecision;
50
+ interface ResolvableEntry {
51
+ name: string;
52
+ fullName: string;
53
+ }
54
+ /**
55
+ * Resolve a tool name exactly as `call-tool.flow.ts` `findTool` does.
56
+ *
57
+ * MUST mirror that stage. The policy has to evaluate the same entry the flow will execute:
58
+ * resolve more narrowly and legitimate remote-app tools get denied; resolve more loosely and
59
+ * a tool reachable only through the flow's fallback escapes the policy entirely. The flow
60
+ * applies three steps, and so does this —
61
+ * 1. the scope registry,
62
+ * 2. a hyphen <-> underscore alias (issue #408),
63
+ * 3. remote app registries, for tools whose subscription has not yet propagated.
64
+ */
65
+ export declare function resolveCodeCallTool<T extends ResolvableEntry>(scope: unknown, name: string): T | undefined;
66
+ /**
67
+ * Apply the `directCalls` options to a `codecall:invoke` request.
68
+ *
69
+ * These options were declared in the plugin schema and read by nothing, so `codecall:invoke`
70
+ * proxied the entire registry regardless of how an operator configured it. They narrow the
71
+ * base policy; they never widen it.
72
+ */
73
+ export declare function checkDirectCallPolicy(tool: CodeCallPolicyTool, directCalls: CodeCallDirectCallsConfig | undefined): CodeCallPolicyDecision;
74
+ interface PolicyConfigReader {
75
+ get(key: string): unknown;
76
+ }
77
+ /**
78
+ * Resolve a tool name and decide whether CodeCall may reach it.
79
+ *
80
+ * The one entry point for both meta-tools: `codecall:execute` calls it for every `callTool`
81
+ * and `getTool`, `codecall:invoke` for its single target with `directCall: true`. Sharing it
82
+ * is the point — the advisory existed because execution and discovery each had their own
83
+ * answer.
84
+ */
85
+ export declare function checkCodeCallToolAccess(scope: unknown, config: PolicyConfigReader, name: string, options?: {
86
+ directCall?: boolean;
87
+ }): CodeCallPolicyDecision;
88
+ export {};
89
+ //# sourceMappingURL=codecall-tool-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecall-tool-policy.d.ts","sourceRoot":"","sources":["../../src/security/codecall-tool-policy.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAKvH,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC;AAED,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B;AAED,MAAM,MAAM,sBAAsB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAQ5F;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,oBAAoB,GAC3B,sBAAsB,CAwDxB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,sBAAsB,CAEpE;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAWD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAwB1G;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,EACxB,WAAW,EAAE,yBAAyB,GAAG,SAAS,GACjD,sBAAsB,CAyBxB;AAED,UAAU,kBAAkB;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,sBAAsB,CAmCxB"}
@@ -1,3 +1,4 @@
1
+ export * from './codecall-tool-policy';
1
2
  export * from './self-reference-guard';
2
3
  export * from './tool-access-control.service';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAEA,cAAc,wBAAwB,CAAC;AACvC,cAAc,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAEA,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,+BAA+B,CAAC"}
@@ -58,8 +58,18 @@ export interface ToolAccessDecision {
58
58
  /**
59
59
  * Tool Access Control Service
60
60
  *
61
- * Provides centralized access control for tool calls within CodeCall.
62
- * Implements a layered security model:
61
+ * NOT ON THE ENFORCEMENT PATH. Nothing constructs this service: the plugin declares
62
+ * `providers: []`, no `ToolAccessPolicy` can be supplied through the plugin options, and
63
+ * neither `codecall:execute` nor `codecall:invoke` consults it. Its unit tests pass by
64
+ * constructing it directly, which is exactly how GHSA-6w3j-82v5-6qrr stayed invisible — a
65
+ * green suite for a control that never ran.
66
+ *
67
+ * The policy CodeCall actually enforces lives in `codecall-tool-policy.ts`
68
+ * (`checkCodeCallToolAccess`), called by both meta-tools. This class is kept only so the
69
+ * exported types do not disappear from the package's public surface in a security patch;
70
+ * wiring it in, or removing it, is a follow-up with its own BC note.
71
+ *
72
+ * The richer model it offers, unused today:
63
73
  *
64
74
  * 1. Self-reference blocking (handled separately in self-reference-guard.ts)
65
75
  * 2. Default blacklist (always enforced)
@@ -1 +1 @@
1
- {"version":3,"file":"tool-access-control.service.d.ts","sourceRoot":"","sources":["../../src/security/tool-access-control.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IAEF;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD;;;;;;;;;;;GAWG;AACH,qBAIa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;gBAE5B,MAAM,CAAC,EAAE,gBAAgB;IAWrC;;;;;;OAMG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAsCxF,kBAAkB;YAqBlB,kBAAkB;YAelB,gBAAgB;IAS9B;;OAEG;IACH,OAAO,CAAC,OAAO;IAmBf;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,gBAAgB,CAAC;CAGxC;AAED,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"tool-access-control.service.d.ts","sourceRoot":"","sources":["../../src/security/tool-access-control.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IAEF;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAIa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;gBAE5B,MAAM,CAAC,EAAE,gBAAgB;IAWrC;;;;;;OAMG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAsCxF,kBAAkB;YAqBlB,kBAAkB;YAelB,gBAAgB;IAS9B;;OAEG;IACH,OAAO,CAAC,OAAO;IAmBf;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,gBAAgB,CAAC;CAGxC;AAED,eAAe,wBAAwB,CAAC"}
@@ -1,6 +1,15 @@
1
1
  import { ToolContext } from '@frontmcp/sdk';
2
2
  import { type CodeCallExecuteResult, type ExecuteToolInput } from './execute.schema';
3
3
  export default class ExecuteTool extends ToolContext {
4
+ /**
5
+ * Resolve the server-side CodeCall policy for a tool name (GHSA-6w3j-82v5-6qrr).
6
+ *
7
+ * Shares its decision with discovery and with `codecall:invoke`, so the three cannot
8
+ * diverge. A name matching no registered tool is denied rather than passed through —
9
+ * otherwise it would skip every metadata-driven check and reach `tools:call-tool`
10
+ * unexamined.
11
+ */
12
+ private checkToolPolicy;
4
13
  execute(input: ExecuteToolInput): Promise<CodeCallExecuteResult>;
5
14
  }
6
15
  //# sourceMappingURL=execute.tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"execute.tool.d.ts","sourceRoot":"","sources":["../../src/tools/execute.tool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,WAAW,EAAE,MAAM,eAAe,CAAC;AAclD,OAAO,EAIL,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAyC1B,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAC5C,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAqPvE"}
1
+ {"version":3,"file":"execute.tool.d.ts","sourceRoot":"","sources":["../../src/tools/execute.tool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,WAAW,EAAE,MAAM,eAAe,CAAC;AAmBlD,OAAO,EAIL,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAyC1B,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAIjB,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA4QvE"}
@@ -6,6 +6,8 @@ import { InvokeToolInput, InvokeToolOutput } from './invoke.schema';
6
6
  *
7
7
  * Security Considerations:
8
8
  * - Self-reference blocking: Cannot invoke codecall:* tools
9
+ * - Shares the CodeCall access policy with `codecall:execute`, so a tool withheld from
10
+ * scripts is not reachable by naming it here instead (GHSA-6w3j-82v5-6qrr)
9
11
  * - All middleware (auth, PII, rate limiting) applies via normal tool execution
10
12
  */
11
13
  export default class InvokeTool extends ToolContext {
@@ -1 +1 @@
1
- {"version":3,"file":"invoke.tool.d.ts","sourceRoot":"","sources":["../../src/tools/invoke.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,WAAW,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EACL,eAAe,EAEf,gBAAgB,EAGjB,MAAM,iBAAiB,CAAC;AAazB;;;;;;;GAOG;AAeH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IAC3C,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAkCjE"}
1
+ {"version":3,"file":"invoke.tool.d.ts","sourceRoot":"","sources":["../../src/tools/invoke.tool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,WAAW,EAAE,MAAM,eAAe,CAAC;AAIlD,OAAO,EAEL,eAAe,EAEf,gBAAgB,EAEjB,MAAM,iBAAiB,CAAC;AAYzB;;;;;;;;;GASG;AAeH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IAC3C,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA4CjE"}