@gaunt-sloth/core 2.0.0-alpha.8 → 2.0.0-beta.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/.gsloth.review.md +2 -0
- package/README.md +71 -20
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/configDiscovery.d.ts +79 -0
- package/dist/config/configDiscovery.js +80 -0
- package/dist/config/configDiscovery.js.map +1 -0
- package/dist/config/defaults.d.ts +20 -20
- package/dist/config/defaults.js +10 -8
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/filesystem-tools.d.ts +41 -0
- package/dist/config/filesystem-tools.js +56 -0
- package/dist/config/filesystem-tools.js.map +1 -0
- package/dist/config/loader.d.ts +171 -19
- package/dist/config/loader.js +1054 -144
- package/dist/config/loader.js.map +1 -1
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- package/dist/config/profiles.d.ts +68 -0
- package/dist/config/profiles.js +93 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providerKeys.d.ts +69 -0
- package/dist/config/providerKeys.js +69 -0
- package/dist/config/providerKeys.js.map +1 -0
- package/dist/config/schema.d.ts +2671 -138
- package/dist/config/schema.js +1361 -85
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +899 -111
- package/dist/config/shell-policy.js +800 -70
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +211 -0
- package/dist/config/tool-descriptions.js +272 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +352 -41
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +28 -1
- package/dist/constants.js +28 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +166 -11
- package/dist/core/GthAbstractAgent.js +484 -45
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +543 -57
- package/dist/core/GthAgentRunner.js +1494 -140
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +117 -2
- package/dist/core/GthLangChainAgent.js +602 -29
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/annotations.d.ts +122 -0
- package/dist/core/approvals/annotations.js +137 -0
- package/dist/core/approvals/annotations.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +216 -0
- package/dist/core/approvals/grants.js +469 -0
- package/dist/core/approvals/grants.js.map +1 -0
- package/dist/core/approvals/matcher.d.ts +202 -0
- package/dist/core/approvals/matcher.js +267 -0
- package/dist/core/approvals/matcher.js.map +1 -0
- package/dist/core/approvals/mcpSubjects.d.ts +40 -0
- package/dist/core/approvals/mcpSubjects.js +99 -0
- package/dist/core/approvals/mcpSubjects.js.map +1 -0
- package/dist/core/approvals/promptHeader.d.ts +28 -0
- package/dist/core/approvals/promptHeader.js +62 -0
- package/dist/core/approvals/promptHeader.js.map +1 -0
- package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
- package/dist/core/approvals/toolAnnotationSources.js +277 -0
- package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
- package/dist/core/approvals/toolHost.d.ts +46 -0
- package/dist/core/approvals/toolHost.js +108 -0
- package/dist/core/approvals/toolHost.js.map +1 -0
- package/dist/core/debugCapture.d.ts +74 -0
- package/dist/core/debugCapture.js +100 -0
- package/dist/core/debugCapture.js.map +1 -0
- package/dist/core/gthLeanAgentFactory.d.ts +4 -4
- package/dist/core/gthLeanAgentFactory.js +4 -4
- package/dist/core/launchBanner.d.ts +127 -0
- package/dist/core/launchBanner.js +414 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/modelLabel.d.ts +19 -0
- package/dist/core/modelLabel.js +26 -0
- package/dist/core/modelLabel.js.map +1 -0
- package/dist/core/plainToolIndication.d.ts +15 -0
- package/dist/core/plainToolIndication.js +174 -0
- package/dist/core/plainToolIndication.js.map +1 -0
- package/dist/core/reasoningBlocks.d.ts +65 -0
- package/dist/core/reasoningBlocks.js +103 -0
- package/dist/core/reasoningBlocks.js.map +1 -0
- package/dist/core/refusal.d.ts +53 -0
- package/dist/core/refusal.js +133 -0
- package/dist/core/refusal.js.map +1 -0
- package/dist/core/runHeader.d.ts +38 -0
- package/dist/core/runHeader.js +42 -0
- package/dist/core/runHeader.js.map +1 -0
- package/dist/core/runStats.d.ts +14 -3
- package/dist/core/runStats.js +48 -3
- package/dist/core/runStats.js.map +1 -1
- package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
- package/dist/core/shell/ShellCommandFailedError.js +3 -4
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
- package/dist/core/shell/abstention.d.ts +88 -0
- package/dist/core/shell/abstention.js +184 -0
- package/dist/core/shell/abstention.js.map +1 -0
- package/dist/core/shell/approvalCapture.d.ts +271 -0
- package/dist/core/shell/approvalCapture.js +108 -0
- package/dist/core/shell/approvalCapture.js.map +1 -0
- package/dist/core/shell/approvalStop.d.ts +123 -0
- package/dist/core/shell/approvalStop.js +269 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/escalationSeverity.d.ts +141 -0
- package/dist/core/shell/escalationSeverity.js +89 -0
- package/dist/core/shell/escalationSeverity.js.map +1 -0
- package/dist/core/shell/framing.d.ts +190 -0
- package/dist/core/shell/framing.js +633 -0
- package/dist/core/shell/framing.js.map +1 -0
- package/dist/core/shell/hardline.d.ts +103 -0
- package/dist/core/shell/hardline.js +780 -0
- package/dist/core/shell/hardline.js.map +1 -0
- package/dist/core/shell/negotiation.d.ts +328 -0
- package/dist/core/shell/negotiation.js +488 -0
- package/dist/core/shell/negotiation.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +44 -4
- package/dist/core/shell/normalize.js +61 -7
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +263 -0
- package/dist/core/shell/openWorld.js +1188 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +873 -0
- package/dist/core/shell/rater.js +1454 -0
- package/dist/core/shell/rater.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +41 -0
- package/dist/core/shell/raterModel.js +51 -0
- package/dist/core/shell/raterModel.js.map +1 -0
- package/dist/core/shell/raterVocabulary.d.ts +121 -0
- package/dist/core/shell/raterVocabulary.js +116 -0
- package/dist/core/shell/raterVocabulary.js.map +1 -0
- package/dist/core/shell/rejection.d.ts +69 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/toolCallRepair/grammar.d.ts +41 -0
- package/dist/core/toolCallRepair/grammar.js +116 -0
- package/dist/core/toolCallRepair/grammar.js.map +1 -0
- package/dist/core/toolCallRepair/index.d.ts +2 -0
- package/dist/core/toolCallRepair/index.js +7 -0
- package/dist/core/toolCallRepair/index.js.map +1 -0
- package/dist/core/toolCallRepair/payload.d.ts +36 -0
- package/dist/core/toolCallRepair/payload.js +341 -0
- package/dist/core/toolCallRepair/payload.js.map +1 -0
- package/dist/core/toolCallRepair/promote.d.ts +45 -0
- package/dist/core/toolCallRepair/promote.js +90 -0
- package/dist/core/toolCallRepair/promote.js.map +1 -0
- package/dist/core/toolDisplay.d.ts +123 -0
- package/dist/core/toolDisplay.js +451 -0
- package/dist/core/toolDisplay.js.map +1 -0
- package/dist/core/toolOutputChannel.d.ts +95 -0
- package/dist/core/toolOutputChannel.js +165 -0
- package/dist/core/toolOutputChannel.js.map +1 -0
- package/dist/core/types.d.ts +349 -16
- package/dist/core/types.js.map +1 -1
- package/dist/history/historyFormat.d.ts +12 -3
- package/dist/history/historyFormat.js +50 -8
- package/dist/history/historyFormat.js.map +1 -1
- package/dist/history/historyStore.d.ts +77 -0
- package/dist/history/historyStore.js +173 -6
- package/dist/history/historyStore.js.map +1 -1
- package/dist/history/recordSession.d.ts +10 -1
- package/dist/history/recordSession.js +27 -0
- package/dist/history/recordSession.js.map +1 -1
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/configurationPassthrough.d.ts +107 -0
- package/dist/providers/configurationPassthrough.js +148 -0
- package/dist/providers/configurationPassthrough.js.map +1 -0
- package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
- package/dist/providers/geminiSchemaSanitizer.js +347 -0
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +60 -0
- package/dist/providers/geminiThinking.js +92 -0
- package/dist/providers/geminiThinking.js.map +1 -0
- package/dist/providers/google-genai.js +18 -1
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.js +12 -0
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/huggingface.d.ts +25 -0
- package/dist/providers/huggingface.js +69 -0
- package/dist/providers/huggingface.js.map +1 -0
- package/dist/providers/modelCatalog.d.ts +109 -0
- package/dist/providers/modelCatalog.js +245 -0
- package/dist/providers/modelCatalog.js.map +1 -0
- package/dist/providers/modelDiscovery.d.ts +99 -5
- package/dist/providers/modelDiscovery.js +191 -35
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/ollama.d.ts +18 -4
- package/dist/providers/ollama.js +67 -37
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +34 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +26 -4
- package/dist/providers/openrouter.js +83 -26
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +19 -1
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.js +20 -0
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +105 -0
- package/dist/runtime/askStructured.js +120 -0
- package/dist/runtime/askStructured.js.map +1 -0
- package/dist/runtime/conversation.d.ts +64 -0
- package/dist/runtime/conversation.js +171 -0
- package/dist/runtime/conversation.js.map +1 -0
- package/dist/runtime/singleShot.d.ts +37 -6
- package/dist/runtime/singleShot.js +113 -67
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/runtime/structuredOutput.d.ts +104 -0
- package/dist/runtime/structuredOutput.js +393 -0
- package/dist/runtime/structuredOutput.js.map +1 -0
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/aiignoreUtils.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +95 -0
- package/dist/utils/consoleUtils.js +112 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/crashHandler.d.ts +87 -0
- package/dist/utils/crashHandler.js +128 -0
- package/dist/utils/crashHandler.js.map +1 -0
- package/dist/utils/debugDump.d.ts +134 -0
- package/dist/utils/debugDump.js +381 -0
- package/dist/utils/debugDump.js.map +1 -0
- package/dist/utils/debugUtils.d.ts +13 -4
- package/dist/utils/debugUtils.js +36 -13
- package/dist/utils/debugUtils.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +53 -0
- package/dist/utils/displayWidth.js +195 -0
- package/dist/utils/displayWidth.js.map +1 -0
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/globalConfigUtils.d.ts +14 -2
- package/dist/utils/globalConfigUtils.js +22 -4
- package/dist/utils/globalConfigUtils.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +39 -8
- package/dist/utils/llmUtils.js +76 -8
- package/dist/utils/llmUtils.js.map +1 -1
- package/dist/utils/redactSecrets.d.ts +63 -0
- package/dist/utils/redactSecrets.js +286 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +222 -0
- package/dist/utils/systemPromptNotes.js +338 -0
- package/dist/utils/systemPromptNotes.js.map +1 -0
- package/dist/utils/systemUtils.d.ts +18 -1
- package/dist/utils/systemUtils.js +38 -3
- package/dist/utils/systemUtils.js.map +1 -1
- package/dist/utils/toolMatching.d.ts +30 -0
- package/dist/utils/toolMatching.js +44 -0
- package/dist/utils/toolMatching.js.map +1 -0
- package/dist/utils/untrustedText.d.ts +86 -0
- package/dist/utils/untrustedText.js +101 -0
- package/dist/utils/untrustedText.js.map +1 -0
- package/package.json +21 -7
- package/schema/gsloth-config.schema.json +1921 -326
- package/dist/core/shell/allowlist.d.ts +0 -75
- package/dist/core/shell/allowlist.js +0 -187
- package/dist/core/shell/allowlist.js.map +0 -1
- package/dist/core/shell/judge.d.ts +0 -161
- package/dist/core/shell/judge.js +0 -261
- package/dist/core/shell/judge.js.map +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/approvals/matcher
|
|
3
|
+
*
|
|
4
|
+
* EXT-71 (spec §3.1, §3.2, §3.3) — **the one comparison engine** over {@link ApprovalEntry}, and
|
|
5
|
+
* the three-list resolution built on top of it. Every approvals decision that is not the rung's own
|
|
6
|
+
* default runs through {@link resolveApprovalRules}; there is no second matcher anywhere.
|
|
7
|
+
*
|
|
8
|
+
* ## What an entry compares against
|
|
9
|
+
*
|
|
10
|
+
* `exact` / `glob` / `regexp` compare against the **normalized command** for a `shell` entry and
|
|
11
|
+
* against the **tool name** for `tool` / `mcpTool`. The normalization is
|
|
12
|
+
* {@link import('../shell/normalize.js').normalizeCommand} — the same one the command classifier and
|
|
13
|
+
* the §8 floor already use, never a second one of this module's own, because a second normalization
|
|
14
|
+
* is how a matcher and a floor come to disagree about what a command is.
|
|
15
|
+
*
|
|
16
|
+
* `hint` is valid only on tool subjects and reads the call's **effective** annotations through
|
|
17
|
+
* {@link EffectiveToolAnnotationSource}, which `core/approvals/annotations.ts` builds from the user's
|
|
18
|
+
* per-server trust (§4.7.1). A caller that wires no source gets {@link failClosedToolAnnotations}:
|
|
19
|
+
* the MCP fail-closed defaults, which describe a tool that writes, destroys, is not idempotent and
|
|
20
|
+
* reaches the open world.
|
|
21
|
+
*
|
|
22
|
+
* ## The asymmetry (§3.1 "Compound commands"), which is the whole point of this module
|
|
23
|
+
*
|
|
24
|
+
* **No allow entry of any matcher matches a command that does not statically resolve.** If
|
|
25
|
+
* {@link classifyCommand} cannot resolve it — composition, substitution, redirection — it is a
|
|
26
|
+
* non-match for `exact`, `glob` and `regexp` alike, so a glob can never span a command separator and
|
|
27
|
+
* `git *` never grants `git status && curl x | sh`.
|
|
28
|
+
*
|
|
29
|
+
* **A deny (or escalate) entry MAY match a compound command**, and is matched against every segment
|
|
30
|
+
* a shell would run as well as against the whole string, because a prohibition that catches
|
|
31
|
+
* something unresolvable errs in the direction that costs nothing.
|
|
32
|
+
*
|
|
33
|
+
* The same split governs a match the engine cannot decide (a regexp over its run-time budget, or an
|
|
34
|
+
* annotation set nothing can supply): **undecidable is a non-match on the allow side and a match on
|
|
35
|
+
* the restrictive side.** Each fails toward a prompt, never toward an execution.
|
|
36
|
+
*
|
|
37
|
+
* ## Resolution
|
|
38
|
+
*
|
|
39
|
+
* `deny` > `escalate` > `allow`, most-restrictive-wins: every deny entry is consulted before any
|
|
40
|
+
* escalate entry and every escalate entry before any allow entry, so **author order and merge order
|
|
41
|
+
* cannot change the outcome** and an appended entry can never perturb an existing one. Within a
|
|
42
|
+
* list the first match is reported, which decides only the provenance shown to the user.
|
|
43
|
+
*
|
|
44
|
+
* ## Case
|
|
45
|
+
*
|
|
46
|
+
* Shell `exact` / `glob` comparisons on the restrictive lists fold case, matching the established
|
|
47
|
+
* deny behaviour (`core/shell/denylist.ts`) — broader on a list whose breadth is fail-safe. Allow
|
|
48
|
+
* comparisons, tool names, and **every `regexp` on every list** are compared as written: silently
|
|
49
|
+
* adding an `i` flag to a pattern the user authored would make it mean something they did not write.
|
|
50
|
+
*/
|
|
51
|
+
import { type ApprovalEntry, type ShellApprovalGateNotice } from '#src/config/shell-policy.js';
|
|
52
|
+
/** A gated `run_shell_command` call: the command exactly as the model proposed it. */
|
|
53
|
+
export interface ShellApprovalSubject {
|
|
54
|
+
kind: 'shell';
|
|
55
|
+
/** RAW command. Normalization happens here so no caller can normalize differently. */
|
|
56
|
+
command: string;
|
|
57
|
+
}
|
|
58
|
+
/** A gated built-in / custom in-process tool call. */
|
|
59
|
+
export interface ToolApprovalSubject {
|
|
60
|
+
kind: 'tool';
|
|
61
|
+
name: string;
|
|
62
|
+
/** §4.7.4 — the host this call reaches, where it has one. */
|
|
63
|
+
host?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A gated MCP tool call. `server` is the user's own key in `mcpServers` (§4.7.5) — the only identity
|
|
67
|
+
* a server has that is stable, unique and user-authored, and the key its trust and its rule entries
|
|
68
|
+
* are both written against. Nothing a server declares about its own name ever participates.
|
|
69
|
+
*/
|
|
70
|
+
export interface McpToolApprovalSubject {
|
|
71
|
+
kind: 'mcpTool';
|
|
72
|
+
server: string;
|
|
73
|
+
name: string;
|
|
74
|
+
host?: string;
|
|
75
|
+
}
|
|
76
|
+
/** Any subject the matcher can be asked about. */
|
|
77
|
+
export type ApprovalSubject = ShellApprovalSubject | ToolApprovalSubject | McpToolApprovalSubject;
|
|
78
|
+
/**
|
|
79
|
+
* §4.7.1 — the four MCP `ToolAnnotations` booleans, as they **effectively** hold for a call after
|
|
80
|
+
* provenance and trust have been applied. All four are present: a hint entry asks a yes/no question
|
|
81
|
+
* of each name it uses, so "unknown" is expressed by the source returning `undefined` for the whole
|
|
82
|
+
* set, never by a missing key.
|
|
83
|
+
*/
|
|
84
|
+
export interface EffectiveToolAnnotations {
|
|
85
|
+
readOnlyHint: boolean;
|
|
86
|
+
destructiveHint: boolean;
|
|
87
|
+
idempotentHint: boolean;
|
|
88
|
+
openWorldHint: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolves the effective annotations of a tool call, or `undefined` when they cannot be determined
|
|
92
|
+
* — which the matcher treats as undecidable (non-match on allow, match on deny/escalate).
|
|
93
|
+
*
|
|
94
|
+
* This function type is the whole contract between the matcher and the trust model:
|
|
95
|
+
* `createEffectiveToolAnnotationSource` (`core/approvals/annotations.ts`) is the one implementation
|
|
96
|
+
* that applies per-server, per-hint trust (§4.7.1), and it is the ONLY place an effective set is
|
|
97
|
+
* derived. Nothing in this module knows how trust is decided, which is what lets the two evolve
|
|
98
|
+
* independently.
|
|
99
|
+
*/
|
|
100
|
+
export type EffectiveToolAnnotationSource = (subject: ToolApprovalSubject | McpToolApprovalSubject) => EffectiveToolAnnotations | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* The MCP fail-closed defaults (spec §4.7.2): a tool that has said nothing about itself is assumed
|
|
103
|
+
* to write, to destroy, to be non-idempotent and to reach the open world. These are the values the
|
|
104
|
+
* MCP specification itself defines for absent annotations, so an entry written against them is
|
|
105
|
+
* written against the protocol's own conservative reading.
|
|
106
|
+
*
|
|
107
|
+
* **Frozen**, and read-only to the type system, because it is shared: an effective set is something
|
|
108
|
+
* callers snapshot (§4.7.4 records one on a sticky grant), so a source that handed this object out
|
|
109
|
+
* instead of a copy would let one caller's snapshot rewrite the fail-closed floor for every other.
|
|
110
|
+
* Freezing turns that aliasing bug into an immediate throw rather than a silent, global loosening.
|
|
111
|
+
*/
|
|
112
|
+
export declare const MCP_FAIL_CLOSED_ANNOTATIONS: Readonly<EffectiveToolAnnotations>;
|
|
113
|
+
/**
|
|
114
|
+
* The {@link EffectiveToolAnnotationSource} a caller gets when it wires none: every tool reads as
|
|
115
|
+
* {@link MCP_FAIL_CLOSED_ANNOTATIONS}.
|
|
116
|
+
*
|
|
117
|
+
* It is the safe default and not a stub — it is what a fully distrustful configuration computes
|
|
118
|
+
* anyway (§4.7.1: an untrusted server's effective set IS this constant), so a call site that has no
|
|
119
|
+
* annotations to offer behaves exactly like one whose user believes nothing. Deliberately NOT a
|
|
120
|
+
* trust model: trust needs config, and config belongs to `core/approvals/annotations.ts`.
|
|
121
|
+
*
|
|
122
|
+
* It answers with a **fresh object per call**, matching `createEffectiveToolAnnotationSource`, so
|
|
123
|
+
* the whole contract — not merely its configured half — is safe to snapshot.
|
|
124
|
+
*/
|
|
125
|
+
export declare const failClosedToolAnnotations: EffectiveToolAnnotationSource;
|
|
126
|
+
/**
|
|
127
|
+
* §3.1 — the run-time budget for ONE `regexp` entry evaluation, in milliseconds.
|
|
128
|
+
*
|
|
129
|
+
* It is a **backstop reported rather than swallowed**, not the primary defence: a pattern is
|
|
130
|
+
* length-capped and compile-checked when the config loads (`approvalEntrySchema`), and a short
|
|
131
|
+
* pattern can still backtrack catastrophically. What this bounds is the damage — and, more
|
|
132
|
+
* importantly, it makes a pattern that stopped deciding *visible*, so a session in which one quietly
|
|
133
|
+
* fell out of use cannot be mistaken for one in which it works.
|
|
134
|
+
*/
|
|
135
|
+
export declare const REGEXP_MATCH_BUDGET_MS = 50;
|
|
136
|
+
/** Tuning + seams for {@link resolveApprovalRules}. All optional; the defaults are the product's. */
|
|
137
|
+
export interface ApprovalMatcherOptions {
|
|
138
|
+
/**
|
|
139
|
+
* Where a `hint` entry reads its effective values (§4.7.1). Build it with
|
|
140
|
+
* `createEffectiveToolAnnotationSource`; defaults to {@link failClosedToolAnnotations}.
|
|
141
|
+
*/
|
|
142
|
+
annotations?: EffectiveToolAnnotationSource;
|
|
143
|
+
/**
|
|
144
|
+
* Where a run-time report goes — the established {@link ShellApprovalGateNotice} shape, which the
|
|
145
|
+
* runner forwards to `statusUpdate`. Absent means the report is dropped, which is why the runner
|
|
146
|
+
* always passes one.
|
|
147
|
+
*/
|
|
148
|
+
onNotice?: (notice: ShellApprovalGateNotice) => void;
|
|
149
|
+
/** Override {@link REGEXP_MATCH_BUDGET_MS}. */
|
|
150
|
+
regexpBudgetMs?: number;
|
|
151
|
+
/** Clock, injectable so the budget backstop is testable without a pathological pattern. */
|
|
152
|
+
now?: () => number;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* §3.2 — whether the rater still reviews a call this entry matched. Honored at the rater rungs and
|
|
156
|
+
* inert at the deterministic ones; a per-entry `rate` wins in **both** directions.
|
|
157
|
+
*
|
|
158
|
+
* The default derives from one principle: *an entry skips the rater only to the extent that it
|
|
159
|
+
* recorded what the rater would have seen.* A `shell` + `exact` entry recorded the whole command, so
|
|
160
|
+
* it needs no rating; a pattern recorded a shape, and a tool entry recorded identity (never
|
|
161
|
+
* arguments), so both keep the rater.
|
|
162
|
+
*/
|
|
163
|
+
export declare function approvalEntryRatesCall(entry: ApprovalEntry): boolean;
|
|
164
|
+
/** The three declared lists, as one argument. */
|
|
165
|
+
export interface ApprovalRuleLists {
|
|
166
|
+
allow: readonly ApprovalEntry[];
|
|
167
|
+
deny: readonly ApprovalEntry[];
|
|
168
|
+
escalate: readonly ApprovalEntry[];
|
|
169
|
+
}
|
|
170
|
+
/** What the rule lists decided about a call, or `null` when no entry matched. */
|
|
171
|
+
export interface ApprovalRuleDecision {
|
|
172
|
+
/** §3 — most-restrictive-wins: `deny` over `escalate` over `allow`. */
|
|
173
|
+
action: 'deny' | 'escalate' | 'allow';
|
|
174
|
+
/** The entry that fired, for the provenance the prompt and the refusal message must show. */
|
|
175
|
+
entry: ApprovalEntry;
|
|
176
|
+
/**
|
|
177
|
+
* §3.2 — for an `allow` action, whether the rater still reviews the call as a **tripwire**.
|
|
178
|
+
* Always `false` for `deny` (nothing to rate) and for `escalate` (which goes straight to the
|
|
179
|
+
* human with no rating call, because the user pre-decided that a human answers).
|
|
180
|
+
*/
|
|
181
|
+
rate: boolean;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* §3.3 — resolve a call against all three lists, most-restrictive-wins.
|
|
185
|
+
*
|
|
186
|
+
* **Every** deny entry is consulted before **any** escalate entry, and every escalate entry before
|
|
187
|
+
* any allow entry. That ordering is the whole property: author order within a list, and the order
|
|
188
|
+
* in which lists were concatenated, cannot change the outcome, and appending an entry can never
|
|
189
|
+
* perturb an existing one. The first match inside a list decides only which entry is reported.
|
|
190
|
+
*
|
|
191
|
+
* Returns `null` when nothing matched — the rung then decides on its own.
|
|
192
|
+
*/
|
|
193
|
+
export declare function resolveApprovalRules(subject: ApprovalSubject, lists: ApprovalRuleLists, options?: ApprovalMatcherOptions): ApprovalRuleDecision | null;
|
|
194
|
+
/**
|
|
195
|
+
* A one-line rendering of an entry, for the provenance a refusal message and an escalation prompt
|
|
196
|
+
* must carry (§3, §3.2) and for the `/approvals` list display.
|
|
197
|
+
*
|
|
198
|
+
* A `shell` + `exact` entry renders as its bare pattern — that is the command the user wrote, and
|
|
199
|
+
* decorating it would only get between them and the line they are looking for. Everything else says
|
|
200
|
+
* what kind of thing it is, because a pattern that is not the command needs to be readable as one.
|
|
201
|
+
*/
|
|
202
|
+
export declare function describeApprovalEntry(entry: ApprovalEntry): string;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { classifyCommand } from '#src/core/shell/arity.js';
|
|
2
|
+
import { normalizeCommand } from '#src/core/shell/normalize.js';
|
|
3
|
+
import { commandSegments } from '#src/core/shell/denylist.js';
|
|
4
|
+
import { StatusLevel } from '#src/core/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* The MCP fail-closed defaults (spec §4.7.2): a tool that has said nothing about itself is assumed
|
|
7
|
+
* to write, to destroy, to be non-idempotent and to reach the open world. These are the values the
|
|
8
|
+
* MCP specification itself defines for absent annotations, so an entry written against them is
|
|
9
|
+
* written against the protocol's own conservative reading.
|
|
10
|
+
*
|
|
11
|
+
* **Frozen**, and read-only to the type system, because it is shared: an effective set is something
|
|
12
|
+
* callers snapshot (§4.7.4 records one on a sticky grant), so a source that handed this object out
|
|
13
|
+
* instead of a copy would let one caller's snapshot rewrite the fail-closed floor for every other.
|
|
14
|
+
* Freezing turns that aliasing bug into an immediate throw rather than a silent, global loosening.
|
|
15
|
+
*/
|
|
16
|
+
export const MCP_FAIL_CLOSED_ANNOTATIONS = Object.freeze({
|
|
17
|
+
readOnlyHint: false,
|
|
18
|
+
destructiveHint: true,
|
|
19
|
+
idempotentHint: false,
|
|
20
|
+
openWorldHint: true,
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* The {@link EffectiveToolAnnotationSource} a caller gets when it wires none: every tool reads as
|
|
24
|
+
* {@link MCP_FAIL_CLOSED_ANNOTATIONS}.
|
|
25
|
+
*
|
|
26
|
+
* It is the safe default and not a stub — it is what a fully distrustful configuration computes
|
|
27
|
+
* anyway (§4.7.1: an untrusted server's effective set IS this constant), so a call site that has no
|
|
28
|
+
* annotations to offer behaves exactly like one whose user believes nothing. Deliberately NOT a
|
|
29
|
+
* trust model: trust needs config, and config belongs to `core/approvals/annotations.ts`.
|
|
30
|
+
*
|
|
31
|
+
* It answers with a **fresh object per call**, matching `createEffectiveToolAnnotationSource`, so
|
|
32
|
+
* the whole contract — not merely its configured half — is safe to snapshot.
|
|
33
|
+
*/
|
|
34
|
+
export const failClosedToolAnnotations = () => ({
|
|
35
|
+
...MCP_FAIL_CLOSED_ANNOTATIONS,
|
|
36
|
+
});
|
|
37
|
+
/* -------------------------------------------------------------------------------------------- *
|
|
38
|
+
* The engine.
|
|
39
|
+
* -------------------------------------------------------------------------------------------- */
|
|
40
|
+
/**
|
|
41
|
+
* §3.1 — the run-time budget for ONE `regexp` entry evaluation, in milliseconds.
|
|
42
|
+
*
|
|
43
|
+
* It is a **backstop reported rather than swallowed**, not the primary defence: a pattern is
|
|
44
|
+
* length-capped and compile-checked when the config loads (`approvalEntrySchema`), and a short
|
|
45
|
+
* pattern can still backtrack catastrophically. What this bounds is the damage — and, more
|
|
46
|
+
* importantly, it makes a pattern that stopped deciding *visible*, so a session in which one quietly
|
|
47
|
+
* fell out of use cannot be mistaken for one in which it works.
|
|
48
|
+
*/
|
|
49
|
+
export const REGEXP_MATCH_BUDGET_MS = 50;
|
|
50
|
+
function contextFrom(options) {
|
|
51
|
+
return {
|
|
52
|
+
annotations: options?.annotations ?? failClosedToolAnnotations,
|
|
53
|
+
onNotice: options?.onNotice,
|
|
54
|
+
regexpBudgetMs: options?.regexpBudgetMs ?? REGEXP_MATCH_BUDGET_MS,
|
|
55
|
+
now: options?.now ?? Date.now,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Every regexp metacharacter EXCEPT `*`, which the glob translation owns. */
|
|
59
|
+
const GLOB_METACHARACTERS = /[.+?^${}()|[\]\\]/g;
|
|
60
|
+
/**
|
|
61
|
+
* §3.1 — compile a glob pattern. `*` matches any run of characters **including none**, matched
|
|
62
|
+
* against the **whole** string rather than token by token; everything else is literal.
|
|
63
|
+
*
|
|
64
|
+
* The consequence worth stating, because it is the first thing anyone gets wrong: `npm publish *`
|
|
65
|
+
* does NOT match a bare `npm publish` — the space before the `*` is part of the pattern.
|
|
66
|
+
* `npm publish*` matches both, and is almost always what was meant.
|
|
67
|
+
*
|
|
68
|
+
* The `s` flag is deliberate: a normalized compound command contains real line breaks (EXT-55 keeps
|
|
69
|
+
* them, because a line break is a command separator), and on the restrictive lists a `*` must be
|
|
70
|
+
* able to cross one. It can never do so on the allow side, where a command containing a separator
|
|
71
|
+
* has already been refused as unresolvable.
|
|
72
|
+
*/
|
|
73
|
+
function globToRegExp(pattern) {
|
|
74
|
+
const escaped = pattern.replace(GLOB_METACHARACTERS, '\\$&').replace(/\*/g, '.*');
|
|
75
|
+
return new RegExp(`^${escaped}$`, 's');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Evaluate a `regexp` entry against one candidate string, under the run-time budget.
|
|
79
|
+
*
|
|
80
|
+
* The pattern compiles as written — no flags are added. Silently making a user's pattern
|
|
81
|
+
* case-insensitive would make it mean something they did not write, on a list where breadth has
|
|
82
|
+
* consequences in both directions.
|
|
83
|
+
*/
|
|
84
|
+
function testRegexpPattern(pattern, candidate, ctx) {
|
|
85
|
+
let compiled;
|
|
86
|
+
try {
|
|
87
|
+
compiled = new RegExp(pattern);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Unreachable via config (the schema compile-checks every pattern at load) and deliberately not
|
|
91
|
+
// an exception: a pattern that cannot be compiled cannot be decided, which the caller already
|
|
92
|
+
// knows how to fail safely on.
|
|
93
|
+
return 'undecidable';
|
|
94
|
+
}
|
|
95
|
+
const started = ctx.now();
|
|
96
|
+
const matched = compiled.test(candidate);
|
|
97
|
+
const elapsed = ctx.now() - started;
|
|
98
|
+
if (elapsed > ctx.regexpBudgetMs) {
|
|
99
|
+
ctx.onNotice?.({
|
|
100
|
+
level: StatusLevel.WARNING,
|
|
101
|
+
message: `An approvals regexp entry took ${elapsed}ms to evaluate, over its ${ctx.regexpBudgetMs}ms ` +
|
|
102
|
+
`budget, so it was treated as undecided for this call: ${pattern}. An allow entry that ` +
|
|
103
|
+
'cannot be decided does not approve, and a deny or escalate entry that cannot be decided ' +
|
|
104
|
+
'still applies. Simplify the pattern if this repeats.',
|
|
105
|
+
});
|
|
106
|
+
return 'undecidable';
|
|
107
|
+
}
|
|
108
|
+
return matched ? 'match' : 'no-match';
|
|
109
|
+
}
|
|
110
|
+
/** Compare one candidate string against a string-patterned entry. */
|
|
111
|
+
function comparePattern(matcher, pattern, candidate, foldCase, ctx) {
|
|
112
|
+
if (matcher === 'regexp')
|
|
113
|
+
return testRegexpPattern(pattern, candidate, ctx);
|
|
114
|
+
const left = foldCase ? candidate.toLowerCase() : candidate;
|
|
115
|
+
const right = foldCase ? pattern.toLowerCase() : pattern;
|
|
116
|
+
if (matcher === 'exact')
|
|
117
|
+
return left === right ? 'match' : 'no-match';
|
|
118
|
+
return globToRegExp(right).test(left) ? 'match' : 'no-match';
|
|
119
|
+
}
|
|
120
|
+
/** Fold several candidate results: any match wins; otherwise any undecidable wins. */
|
|
121
|
+
function foldMatches(results) {
|
|
122
|
+
if (results.includes('match'))
|
|
123
|
+
return 'match';
|
|
124
|
+
if (results.includes('undecidable'))
|
|
125
|
+
return 'undecidable';
|
|
126
|
+
return 'no-match';
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A `shell` entry against a shell subject.
|
|
130
|
+
*
|
|
131
|
+
* On the **allow** side the command must statically resolve (§3.1) — that check comes FIRST and
|
|
132
|
+
* applies to every matcher, which is what stops a glob spanning a command separator — and the
|
|
133
|
+
* comparison is against the whole normalized command, as written.
|
|
134
|
+
*
|
|
135
|
+
* On the **restrictive** side the entry is compared against the whole normalized command AND
|
|
136
|
+
* against every segment a shell would run (`a; b`, `a && b`, the body of a `$(…)`), with case
|
|
137
|
+
* folded for `exact`/`glob`.
|
|
138
|
+
*/
|
|
139
|
+
function matchShellEntry(entry, command, mode, ctx) {
|
|
140
|
+
if (mode === 'allow') {
|
|
141
|
+
// §3.1 — no allow entry of ANY matcher matches a command that does not statically resolve.
|
|
142
|
+
if (classifyCommand(command, normalizeCommand) === null)
|
|
143
|
+
return 'no-match';
|
|
144
|
+
return comparePattern(entry.matcher, entry.pattern, normalizeCommand(command), false, ctx);
|
|
145
|
+
}
|
|
146
|
+
const candidates = [normalizeCommand(command), ...commandSegments(command)];
|
|
147
|
+
const foldCase = entry.matcher !== 'regexp';
|
|
148
|
+
return foldMatches(candidates.map((candidate) => comparePattern(entry.matcher, entry.pattern, candidate, foldCase, ctx)));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* §3.1 — a `hint` pattern holds when **every** annotation it names holds effectively (AND within
|
|
152
|
+
* the entry; alternatives are separate entries). Hints it does not name are unconstrained. An empty
|
|
153
|
+
* pattern is a config error, so it cannot reach here as a match-everything.
|
|
154
|
+
*/
|
|
155
|
+
function matchHintPattern(pattern, subject, ctx) {
|
|
156
|
+
const effective = ctx.annotations(subject);
|
|
157
|
+
if (!effective)
|
|
158
|
+
return 'undecidable';
|
|
159
|
+
for (const [name, required] of Object.entries(pattern)) {
|
|
160
|
+
if (required === undefined)
|
|
161
|
+
continue;
|
|
162
|
+
if (effective[name] !== required)
|
|
163
|
+
return 'no-match';
|
|
164
|
+
}
|
|
165
|
+
return 'match';
|
|
166
|
+
}
|
|
167
|
+
/** One entry against one subject, in one list's fail-direction. */
|
|
168
|
+
function matchEntry(entry, subject, mode, ctx) {
|
|
169
|
+
if (entry.type === 'shell') {
|
|
170
|
+
// A `shell` entry is about a command, so it says nothing about a tool call. `run_shell_command`
|
|
171
|
+
// is deliberately NOT also matchable as a `tool` subject — see `approvalSubjectFor` in the
|
|
172
|
+
// runner and [[EXT-30]].
|
|
173
|
+
return subject.kind === 'shell'
|
|
174
|
+
? matchShellEntry(entry, subject.command, mode, ctx)
|
|
175
|
+
: 'no-match';
|
|
176
|
+
}
|
|
177
|
+
if (subject.kind === 'shell')
|
|
178
|
+
return 'no-match';
|
|
179
|
+
if (entry.type !== subject.kind)
|
|
180
|
+
return 'no-match';
|
|
181
|
+
if (entry.type === 'mcpTool' && subject.kind === 'mcpTool') {
|
|
182
|
+
// §3.1 — `*` is the reserved literal meaning every server, and a configured server may not be
|
|
183
|
+
// named it (validated at load).
|
|
184
|
+
if (entry.server !== '*' && entry.server !== subject.server)
|
|
185
|
+
return 'no-match';
|
|
186
|
+
}
|
|
187
|
+
// §4.7.4 — `host`, where present, is an additional exact-match condition. A call with no host
|
|
188
|
+
// never matches an entry that specifies one.
|
|
189
|
+
if (entry.host !== undefined) {
|
|
190
|
+
if (subject.host === undefined || subject.host !== entry.host)
|
|
191
|
+
return 'no-match';
|
|
192
|
+
}
|
|
193
|
+
if (entry.matcher === 'hint') {
|
|
194
|
+
return matchHintPattern(entry.pattern, subject, ctx);
|
|
195
|
+
}
|
|
196
|
+
return comparePattern(entry.matcher, entry.pattern, subject.name, false, ctx);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* §3.2 — whether the rater still reviews a call this entry matched. Honored at the rater rungs and
|
|
200
|
+
* inert at the deterministic ones; a per-entry `rate` wins in **both** directions.
|
|
201
|
+
*
|
|
202
|
+
* The default derives from one principle: *an entry skips the rater only to the extent that it
|
|
203
|
+
* recorded what the rater would have seen.* A `shell` + `exact` entry recorded the whole command, so
|
|
204
|
+
* it needs no rating; a pattern recorded a shape, and a tool entry recorded identity (never
|
|
205
|
+
* arguments), so both keep the rater.
|
|
206
|
+
*/
|
|
207
|
+
export function approvalEntryRatesCall(entry) {
|
|
208
|
+
if (entry.rate !== undefined)
|
|
209
|
+
return entry.rate;
|
|
210
|
+
return !(entry.type === 'shell' && entry.matcher === 'exact');
|
|
211
|
+
}
|
|
212
|
+
/** The first entry of a list that matches, honoring the list's fail-direction. */
|
|
213
|
+
function firstMatch(entries, subject, mode, ctx) {
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
const result = matchEntry(entry, subject, mode, ctx);
|
|
216
|
+
if (result === 'match')
|
|
217
|
+
return entry;
|
|
218
|
+
// §3.1 — an allow match that cannot be decided is NOT a match (escalate); a deny match that
|
|
219
|
+
// cannot be decided IS one (refuse). Escalate follows deny: it fails toward a prompt, which is
|
|
220
|
+
// never an execution.
|
|
221
|
+
if (result === 'undecidable' && mode === 'restrictive')
|
|
222
|
+
return entry;
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* §3.3 — resolve a call against all three lists, most-restrictive-wins.
|
|
228
|
+
*
|
|
229
|
+
* **Every** deny entry is consulted before **any** escalate entry, and every escalate entry before
|
|
230
|
+
* any allow entry. That ordering is the whole property: author order within a list, and the order
|
|
231
|
+
* in which lists were concatenated, cannot change the outcome, and appending an entry can never
|
|
232
|
+
* perturb an existing one. The first match inside a list decides only which entry is reported.
|
|
233
|
+
*
|
|
234
|
+
* Returns `null` when nothing matched — the rung then decides on its own.
|
|
235
|
+
*/
|
|
236
|
+
export function resolveApprovalRules(subject, lists, options) {
|
|
237
|
+
const ctx = contextFrom(options);
|
|
238
|
+
const denied = firstMatch(lists.deny, subject, 'restrictive', ctx);
|
|
239
|
+
if (denied)
|
|
240
|
+
return { action: 'deny', entry: denied, rate: false };
|
|
241
|
+
const escalated = firstMatch(lists.escalate, subject, 'restrictive', ctx);
|
|
242
|
+
if (escalated)
|
|
243
|
+
return { action: 'escalate', entry: escalated, rate: false };
|
|
244
|
+
const allowed = firstMatch(lists.allow, subject, 'allow', ctx);
|
|
245
|
+
if (allowed)
|
|
246
|
+
return { action: 'allow', entry: allowed, rate: approvalEntryRatesCall(allowed) };
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* A one-line rendering of an entry, for the provenance a refusal message and an escalation prompt
|
|
251
|
+
* must carry (§3, §3.2) and for the `/approvals` list display.
|
|
252
|
+
*
|
|
253
|
+
* A `shell` + `exact` entry renders as its bare pattern — that is the command the user wrote, and
|
|
254
|
+
* decorating it would only get between them and the line they are looking for. Everything else says
|
|
255
|
+
* what kind of thing it is, because a pattern that is not the command needs to be readable as one.
|
|
256
|
+
*/
|
|
257
|
+
export function describeApprovalEntry(entry) {
|
|
258
|
+
const pattern = typeof entry.pattern === 'string' ? entry.pattern : JSON.stringify(entry.pattern);
|
|
259
|
+
const host = entry.type !== 'shell' && entry.host ? ` (host ${entry.host})` : '';
|
|
260
|
+
if (entry.type === 'shell') {
|
|
261
|
+
return entry.matcher === 'exact' ? pattern : `${pattern} (${entry.matcher})`;
|
|
262
|
+
}
|
|
263
|
+
const matcher = entry.matcher === 'exact' ? '' : ` (${entry.matcher})`;
|
|
264
|
+
const subject = entry.type === 'mcpTool' ? `mcpTool ${entry.server}/${pattern}` : `tool ${pattern}`;
|
|
265
|
+
return `${subject}${matcher}${host}`;
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../../src/core/approvals/matcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { McpToolApprovalSubject, ToolApprovalSubject } from '#src/core/approvals/matcher.js';
|
|
2
|
+
/**
|
|
3
|
+
* The server identity an MCP tool call gets when its name does not resolve to exactly one
|
|
4
|
+
* configured `mcpServers` key — nothing matched, or two nested keys both did.
|
|
5
|
+
*
|
|
6
|
+
* **The empty string, and that is load-bearing rather than arbitrary.** A server key is
|
|
7
|
+
* `z.string().min(1)` both under `approvals.mcp.servers` (§9) and on an `mcpTool` rule entry
|
|
8
|
+
* (§3.1), so this value is the one identity a user *cannot spell in config*: no entry can name it.
|
|
9
|
+
* An unresolvable call is therefore matchable only by the reserved `server: "*"` (which means every
|
|
10
|
+
* server and is correct here).
|
|
11
|
+
*
|
|
12
|
+
* Being unspellable is what makes it unnameable; it is **not** by itself what makes it untrusted,
|
|
13
|
+
* because a lookup that misses under `servers` falls through to `approvals.mcp.defaults`. That
|
|
14
|
+
* second half is enforced in `trustedAnnotationHints`, which refuses this value outright, and in
|
|
15
|
+
* `mcpDeclaredAnnotationLookup`, which will not look a declaration up under it. Both are required:
|
|
16
|
+
* the first stops a permissive `defaults` reaching an unattributable call, the second stops one
|
|
17
|
+
* being found by string arithmetic on the sentinel.
|
|
18
|
+
*/
|
|
19
|
+
export declare const UNRESOLVED_MCP_SERVER = "";
|
|
20
|
+
/** Does this registered tool name sit in the MCP namespace? */
|
|
21
|
+
export declare function isMcpNamespacedToolName(toolName: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The registered tool name mcp-adapters emits for one server's tool — the inverse of
|
|
24
|
+
* {@link approvalSubjectForToolName}'s split, single-sourced so a declared-annotation lookup keyed
|
|
25
|
+
* by registered name round-trips exactly instead of re-deriving the separator.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mcpToolRegisteredName(server: string, toolName: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* §4.7.1/§4.7.5 — the subject a gated call on `toolName` presents to the rule matcher.
|
|
30
|
+
*
|
|
31
|
+
* A name outside the MCP namespace is one of ours (a built-in, or a tool the user wired into their
|
|
32
|
+
* own config): `kind: 'tool'`, the trusted provenance. A name inside it is `kind: 'mcpTool'`
|
|
33
|
+
* **always** — with its resolved server where the configured keys name exactly one, and with
|
|
34
|
+
* {@link UNRESOLVED_MCP_SERVER} otherwise.
|
|
35
|
+
*
|
|
36
|
+
* @param toolName The registered tool name, exactly as the model called it.
|
|
37
|
+
* @param configuredMcpServers `Object.keys(config.mcpServers)` — the user's own keys, and the only
|
|
38
|
+
* thing consulted. Nothing a server declares about its own name ever participates (§4.7.5).
|
|
39
|
+
*/
|
|
40
|
+
export declare function approvalSubjectForToolName(toolName: string, configuredMcpServers: Iterable<string>): ToolApprovalSubject | McpToolApprovalSubject;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/approvals/mcpSubjects
|
|
3
|
+
*
|
|
4
|
+
* EXT-70 (spec §4.7.5) — **which approval subject a registered tool name presents**.
|
|
5
|
+
*
|
|
6
|
+
* The distinction this module exists to make is the one §4.7.1 rests on: a `tool` subject's
|
|
7
|
+
* annotations are read **verbatim**, an `mcpTool` subject's are believed only per hint and only
|
|
8
|
+
* where the user said so. So a tool that reaches a third-party server while presenting as
|
|
9
|
+
* `kind: 'tool'` is a gate that server can opt itself out of — the exact failure §4.7.1 names.
|
|
10
|
+
* Every name carrying the MCP namespace therefore becomes an `mcpTool` subject, and there is no
|
|
11
|
+
* path back to `kind: 'tool'` for one.
|
|
12
|
+
*
|
|
13
|
+
* ## The server is resolved against the configured keys, never by splitting
|
|
14
|
+
*
|
|
15
|
+
* An MCP tool is named `mcp__<server>__<tool>` (the mcp-adapters convention, single-sourced from
|
|
16
|
+
* {@link MCP_TOOL_NAME_PREFIX}), where `<server>` is the user's own `mcpServers` key — the identity
|
|
17
|
+
* §4.7.5 requires. **A key may itself contain `__`**, so splitting on the separator attributes such
|
|
18
|
+
* a tool to a shorter key that also happens to be configured, and the wrong server means the wrong
|
|
19
|
+
* TRUST lookup: one server's tool wearing another server's belief. Resolution is therefore a scan
|
|
20
|
+
* of the configured key set for the keys that actually prefix this name.
|
|
21
|
+
*
|
|
22
|
+
* Where that does not yield **exactly one** key — no configured server matches, or two nested keys
|
|
23
|
+
* both do — the call resolves to {@link UNRESOLVED_MCP_SERVER}, which is still an `mcpTool`
|
|
24
|
+
* subject. Failing closed to "an MCP tool under a server we cannot name" is the only safe answer;
|
|
25
|
+
* falling back to `kind: 'tool'` would hand an unidentifiable tool the trusted provenance.
|
|
26
|
+
*/
|
|
27
|
+
import { MCP_TOOL_NAME_PREFIX } from '#src/constants.js';
|
|
28
|
+
/** The separator mcp-adapters puts between the prefix, the server key and the tool name. */
|
|
29
|
+
const NAME_SEPARATOR = '__';
|
|
30
|
+
/** The `mcp__` namespace every MCP tool name carries. */
|
|
31
|
+
const MCP_NAME_PREFIX = `${MCP_TOOL_NAME_PREFIX}${NAME_SEPARATOR}`;
|
|
32
|
+
/**
|
|
33
|
+
* The server identity an MCP tool call gets when its name does not resolve to exactly one
|
|
34
|
+
* configured `mcpServers` key — nothing matched, or two nested keys both did.
|
|
35
|
+
*
|
|
36
|
+
* **The empty string, and that is load-bearing rather than arbitrary.** A server key is
|
|
37
|
+
* `z.string().min(1)` both under `approvals.mcp.servers` (§9) and on an `mcpTool` rule entry
|
|
38
|
+
* (§3.1), so this value is the one identity a user *cannot spell in config*: no entry can name it.
|
|
39
|
+
* An unresolvable call is therefore matchable only by the reserved `server: "*"` (which means every
|
|
40
|
+
* server and is correct here).
|
|
41
|
+
*
|
|
42
|
+
* Being unspellable is what makes it unnameable; it is **not** by itself what makes it untrusted,
|
|
43
|
+
* because a lookup that misses under `servers` falls through to `approvals.mcp.defaults`. That
|
|
44
|
+
* second half is enforced in `trustedAnnotationHints`, which refuses this value outright, and in
|
|
45
|
+
* `mcpDeclaredAnnotationLookup`, which will not look a declaration up under it. Both are required:
|
|
46
|
+
* the first stops a permissive `defaults` reaching an unattributable call, the second stops one
|
|
47
|
+
* being found by string arithmetic on the sentinel.
|
|
48
|
+
*/
|
|
49
|
+
export const UNRESOLVED_MCP_SERVER = '';
|
|
50
|
+
/** Does this registered tool name sit in the MCP namespace? */
|
|
51
|
+
export function isMcpNamespacedToolName(toolName) {
|
|
52
|
+
return toolName.startsWith(MCP_NAME_PREFIX);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The registered tool name mcp-adapters emits for one server's tool — the inverse of
|
|
56
|
+
* {@link approvalSubjectForToolName}'s split, single-sourced so a declared-annotation lookup keyed
|
|
57
|
+
* by registered name round-trips exactly instead of re-deriving the separator.
|
|
58
|
+
*/
|
|
59
|
+
export function mcpToolRegisteredName(server, toolName) {
|
|
60
|
+
return `${MCP_NAME_PREFIX}${server}${NAME_SEPARATOR}${toolName}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* §4.7.1/§4.7.5 — the subject a gated call on `toolName` presents to the rule matcher.
|
|
64
|
+
*
|
|
65
|
+
* A name outside the MCP namespace is one of ours (a built-in, or a tool the user wired into their
|
|
66
|
+
* own config): `kind: 'tool'`, the trusted provenance. A name inside it is `kind: 'mcpTool'`
|
|
67
|
+
* **always** — with its resolved server where the configured keys name exactly one, and with
|
|
68
|
+
* {@link UNRESOLVED_MCP_SERVER} otherwise.
|
|
69
|
+
*
|
|
70
|
+
* @param toolName The registered tool name, exactly as the model called it.
|
|
71
|
+
* @param configuredMcpServers `Object.keys(config.mcpServers)` — the user's own keys, and the only
|
|
72
|
+
* thing consulted. Nothing a server declares about its own name ever participates (§4.7.5).
|
|
73
|
+
*/
|
|
74
|
+
export function approvalSubjectForToolName(toolName, configuredMcpServers) {
|
|
75
|
+
if (!isMcpNamespacedToolName(toolName))
|
|
76
|
+
return { kind: 'tool', name: toolName };
|
|
77
|
+
let resolved;
|
|
78
|
+
for (const server of configuredMcpServers) {
|
|
79
|
+
// An empty key is unnameable in `approvals.mcp.servers` and on an `mcpTool` entry, so resolving
|
|
80
|
+
// to it would be indistinguishable from not resolving at all — treat it as the latter.
|
|
81
|
+
if (server === UNRESOLVED_MCP_SERVER)
|
|
82
|
+
continue;
|
|
83
|
+
const prefix = mcpToolRegisteredName(server, '');
|
|
84
|
+
if (!toolName.startsWith(prefix))
|
|
85
|
+
continue;
|
|
86
|
+
const name = toolName.slice(prefix.length);
|
|
87
|
+
if (name.length === 0)
|
|
88
|
+
continue;
|
|
89
|
+
// A second matching key means two nested server names both explain this tool. Neither is more
|
|
90
|
+
// right than the other, so neither is used.
|
|
91
|
+
if (resolved)
|
|
92
|
+
return { kind: 'mcpTool', server: UNRESOLVED_MCP_SERVER, name: toolName };
|
|
93
|
+
resolved = { kind: 'mcpTool', server, name };
|
|
94
|
+
}
|
|
95
|
+
// Nothing configured explains the name: keep the whole name as the tool's identity, since there
|
|
96
|
+
// is no server key to strip and guessing at one is exactly what this function refuses to do.
|
|
97
|
+
return resolved ?? { kind: 'mcpTool', server: UNRESOLVED_MCP_SERVER, name: toolName };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=mcpSubjects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpSubjects.js","sourceRoot":"","sources":["../../../src/core/approvals/mcpSubjects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,4FAA4F;AAC5F,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,yDAAyD;AACzD,MAAM,eAAe,GAAG,GAAG,oBAAoB,GAAG,cAAc,EAAE,CAAC;AAEnE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC,+DAA+D;AAC/D,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,OAAO,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,QAAgB;IACpE,OAAO,GAAG,eAAe,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,EAAE,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAgB,EAChB,oBAAsC;IAEtC,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAEhF,IAAI,QAA4C,CAAC;IACjD,KAAK,MAAM,MAAM,IAAI,oBAAoB,EAAE,CAAC;QAC1C,gGAAgG;QAChG,uFAAuF;QACvF,IAAI,MAAM,KAAK,qBAAqB;YAAE,SAAS;QAC/C,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAChC,8FAA8F;QAC9F,4CAA4C;QAC5C,IAAI,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,qBAAqB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QACxF,QAAQ,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,gGAAgG;IAChG,6FAA6F;IAC7F,OAAO,QAAQ,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,qBAAqB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACxF,CAAC"}
|