llm-trust-guard 4.19.1 → 4.20.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/CHANGELOG.md +24 -0
- package/dist/guards/mcp-security-guard.d.ts +37 -1
- package/dist/guards/mcp-security-guard.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to `llm-trust-guard` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.20.0] - 2026-04-24
|
|
9
|
+
|
|
10
|
+
### Added — MCP Sampling Attack Detection (Unit42 + Blueinfy, Feb 2026)
|
|
11
|
+
|
|
12
|
+
`MCPSecurityGuard` now validates MCP sampling responses via `validateSamplingResponse()`, closing the only previously unaddressed MCP attack surface.
|
|
13
|
+
|
|
14
|
+
Three attack vectors detected (tied to published Unit42 + Blueinfy Feb 2026 research):
|
|
15
|
+
|
|
16
|
+
- **Resource drain** (`sd_call_again`, `sd_loop_until`, `sd_do_not_stop`, `sd_n_times`, `sd_exhaust_resources`): Hidden instructions embedded in sampling response bodies that cause the agent to loop indefinitely, repeat tool calls N times, or exhaust token quotas — degrading or DoS-ing the agent runtime
|
|
17
|
+
- **Conversation hijacking** (`sd_fake_user_turn`, `sd_fake_assistant_turn`, `sd_role_json`, `sd_system_xml`, `sd_from_now_on`, `sd_new_instructions`, `sd_ignore_previous`): Injected fake user/assistant turns, JSON role fields (`"role": "system"`), XML role tags, and system-prompt override phrases that redirect agent behavior within the sampling response
|
|
18
|
+
- **Covert tool invocation** (`sd_anthropic_tool_xml`, `sd_tool_result_xml`, `sd_openai_tool_call`, `sd_bracket_tool_call`, `sd_double_brace_call`, `sd_invoke_name_attr`): Tool-call syntax embedded in plain-text responses (Anthropic `<function_calls>`, OpenAI `"tool_calls": [...]`, bracket notation `[TOOL:...]`) that cause the agent to invoke tools without user awareness
|
|
19
|
+
|
|
20
|
+
New export: `MCPSamplingResponse` interface.
|
|
21
|
+
|
|
22
|
+
Server reputation degrades automatically on any sampling attack detection.
|
|
23
|
+
|
|
24
|
+
### Tests
|
|
25
|
+
|
|
26
|
+
- +6 `MCPSecurityGuard` sampling tests (resource drain, conversation hijack ×2, covert tool invocation, reputation degradation, clean FP)
|
|
27
|
+
- **All 711 tests pass** (was 705), zero regressions
|
|
28
|
+
|
|
29
|
+
### Stats
|
|
30
|
+
- 34 guards, 711 tests, zero dependencies
|
|
31
|
+
|
|
8
32
|
## [4.19.1] - 2026-04-23
|
|
9
33
|
|
|
10
34
|
### Added — Measured Performance
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* MCPSecurityGuard (L16)
|
|
3
3
|
*
|
|
4
4
|
* Secures Model Context Protocol (MCP) tool integrations.
|
|
5
|
-
* Prevents tool shadowing, server impersonation,
|
|
5
|
+
* Prevents tool shadowing, server impersonation, supply chain attacks,
|
|
6
|
+
* and MCP Sampling channel attacks.
|
|
6
7
|
*
|
|
7
8
|
* Threat Model:
|
|
8
9
|
* - ASI04: Agentic Supply Chain Vulnerabilities
|
|
@@ -10,6 +11,15 @@
|
|
|
10
11
|
* - CVE-2025-6514: mcp-remote command injection
|
|
11
12
|
* - CVE-2025-32711: EchoLeak - silent data exfiltration
|
|
12
13
|
* - Tool Shadowing: Malicious MCP servers impersonating legitimate tools
|
|
14
|
+
* - MCP Sampling Attacks (Unit42 + Blueinfy, Feb 2026): Three concrete vectors
|
|
15
|
+
* delivered through the MCP sampling response channel:
|
|
16
|
+
* (1) Resource drain — hidden prompt appends that trigger infinite tool loops
|
|
17
|
+
* or token exhaustion to degrade or DoS the agent runtime
|
|
18
|
+
* (2) Conversation hijacking — injecting fake user/assistant turns or system
|
|
19
|
+
* prompt overrides into the sampling response body to redirect agent behavior
|
|
20
|
+
* (3) Covert tool invocation — embedding tool-call syntax (Anthropic XML,
|
|
21
|
+
* OpenAI JSON, bracket notation) in plain-text sampling responses to cause
|
|
22
|
+
* the agent to invoke tools without user awareness
|
|
13
23
|
*
|
|
14
24
|
* Protection Capabilities:
|
|
15
25
|
* - MCP server identity verification (signature-based)
|
|
@@ -19,6 +29,7 @@
|
|
|
19
29
|
* - Tool shadowing detection
|
|
20
30
|
* - Server reputation scoring
|
|
21
31
|
* - Command injection prevention
|
|
32
|
+
* - Sampling response scanning (resource drain, conversation hijack, covert tool calls)
|
|
22
33
|
*
|
|
23
34
|
* Upstream SDK advisory — cannot be mitigated at the detection layer:
|
|
24
35
|
* - CVE-2026-25536 (@modelcontextprotocol/sdk 1.10.0–1.25.3, CVSS 7.1):
|
|
@@ -119,6 +130,17 @@ export interface MCPToolCall {
|
|
|
119
130
|
agentId?: string;
|
|
120
131
|
};
|
|
121
132
|
}
|
|
133
|
+
/** Represents a response received from an MCP server via the sampling channel. */
|
|
134
|
+
export interface MCPSamplingResponse {
|
|
135
|
+
/** Text content returned by the MCP server */
|
|
136
|
+
content: string;
|
|
137
|
+
/** Server that produced this sampling response */
|
|
138
|
+
serverId: string;
|
|
139
|
+
/** Agent or tool that initiated the sampling request */
|
|
140
|
+
requestedBy?: string;
|
|
141
|
+
/** Conversation/session ID for audit trail */
|
|
142
|
+
conversationId?: string;
|
|
143
|
+
}
|
|
122
144
|
export interface MCPSecurityResult {
|
|
123
145
|
allowed: boolean;
|
|
124
146
|
reason: string;
|
|
@@ -138,6 +160,12 @@ export interface MCPSecurityResult {
|
|
|
138
160
|
injection_detected: boolean;
|
|
139
161
|
risk_level: string;
|
|
140
162
|
};
|
|
163
|
+
sampling_analysis?: {
|
|
164
|
+
resource_drain_detected: boolean;
|
|
165
|
+
conversation_hijack_detected: boolean;
|
|
166
|
+
covert_tool_invocation_detected: boolean;
|
|
167
|
+
pattern_matches: string[];
|
|
168
|
+
};
|
|
141
169
|
recommendations: string[];
|
|
142
170
|
}
|
|
143
171
|
export declare class MCPSecurityGuard {
|
|
@@ -148,6 +176,7 @@ export declare class MCPSecurityGuard {
|
|
|
148
176
|
private toolToServer;
|
|
149
177
|
private serverViolations;
|
|
150
178
|
private toolDefinitionHashes;
|
|
179
|
+
private readonly SAMPLING_ATTACK_PATTERNS;
|
|
151
180
|
private readonly COMMAND_INJECTION_PATTERNS;
|
|
152
181
|
private readonly SHADOWING_INDICATORS;
|
|
153
182
|
private readonly MALICIOUS_SERVER_PATTERNS;
|
|
@@ -160,6 +189,13 @@ export declare class MCPSecurityGuard {
|
|
|
160
189
|
* Validate MCP tool call
|
|
161
190
|
*/
|
|
162
191
|
validateToolCall(toolCall: MCPToolCall, requestId?: string): MCPSecurityResult;
|
|
192
|
+
/**
|
|
193
|
+
* Validate an MCP sampling response for attack patterns.
|
|
194
|
+
*
|
|
195
|
+
* Covers the three Unit42/Blueinfy (Feb 2026) sampling attack vectors:
|
|
196
|
+
* resource drain, conversation hijacking, and covert tool invocation.
|
|
197
|
+
*/
|
|
198
|
+
validateSamplingResponse(response: MCPSamplingResponse, requestId?: string): MCPSecurityResult;
|
|
163
199
|
/**
|
|
164
200
|
* Register a trusted MCP server
|
|
165
201
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?(function(u,e,i,s){s===void 0&&(s=i);var t=Object.getOwnPropertyDescriptor(e,i);(!t||("get"in t?!e.__esModule:t.writable||t.configurable))&&(t={enumerable:!0,get:function(){return e[i]}}),Object.defineProperty(u,s,t)}):(function(u,e,i,s){s===void 0&&(s=i),u[s]=e[i]})),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?(function(u,e){Object.defineProperty(u,"default",{enumerable:!0,value:e})}):function(u,e){u.default=e}),__importStar=this&&this.__importStar||(function(){var u=function(e){return u=Object.getOwnPropertyNames||function(i){var s=[];for(var t in i)Object.prototype.hasOwnProperty.call(i,t)&&(s[s.length]=t);return s},u(e)};return function(e){if(e&&e.__esModule)return e;var i={};if(e!=null)for(var s=u(e),t=0;t<s.length;t++)s[t]!=="default"&&__createBinding(i,e,s[t]);return __setModuleDefault(i,e),i}})();Object.defineProperty(exports,"__esModule",{value:!0}),exports.MCPSecurityGuard=void 0;const crypto=__importStar(require("crypto"));class MCPSecurityGuard{constructor(e={}){this.registeredServers=new Map,this.registeredTools=new Map,this.serverReputation=new Map,this.toolToServer=new Map,this.serverViolations=new Map,this.toolDefinitionHashes=new Map,this.COMMAND_INJECTION_PATTERNS=[{name:"shell_injection",pattern:/[;&|`$]|\$\(|\)\s*[;&|]|`[^`]+`/g,severity:50},{name:"command_substitution",pattern:/\$\{[^}]+\}|\$\([^)]+\)/g,severity:50},{name:"pipe_injection",pattern:/\|\s*(cat|rm|curl|wget|nc|bash|sh|exec)/i,severity:55},{name:"path_traversal",pattern:/\.\.[\/\\]|\.\.%2[fF]/g,severity:45},{name:"absolute_path",pattern:/^\/(?:etc|usr|var|tmp|bin|root)/i,severity:40},{name:"oauth_injection",pattern:/authorization_endpoint.*[;&|`$]/i,severity:55},{name:"redirect_manipulation",pattern:/redirect_uri.*[^\w\-_.~:/?#[\]@!$&'()*+,;=%]/i,severity:45},{name:"applescript_injection",pattern:/osascript|do\s+shell\s+script|tell\s+application/i,severity:55},{name:"git_injection",pattern:/--upload-pack|--receive-pack|-c\s+core\./i,severity:50},{name:"git_url_injection",pattern:/ext::|file:\/\/|ssh:\/\/.*@/i,severity:45},{name:"argument_injection",pattern:/\s--[a-z]+=.*[;&|`$]/i,severity:45},{name:"env_injection",pattern:/\bLD_PRELOAD\b|\bPATH\s*=/i,severity:50}],this.SHADOWING_INDICATORS=[{legitimate:"file_reader",suspicious:/file[-_]?read(er)?s?|read[-_]?files?/i},{legitimate:"database_query",suspicious:/db[-_]?query|sql[-_]?query|query[-_]?db/i},{legitimate:"email_sender",suspicious:/send[-_]?emails?|email[-_]?send(er)?/i},{legitimate:"api_caller",suspicious:/call[-_]?api|api[-_]?call(er)?/i},{legitimate:"code_executor",suspicious:/exec[-_]?code|run[-_]?code|code[-_]?run/i}],this.MALICIOUS_SERVER_PATTERNS=[/postmark-mcp.*fake/i,/unofficial/i,/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/,/pastebin|gist\.github/i,/temp|tmp|test.*mcp/i],this.config={requireServerSignature:e.requireServerSignature??!1,trustedServers:e.trustedServers??[],blockedServers:e.blockedServers??[],allowDynamicRegistration:e.allowDynamicRegistration??!0,toolAllowlist:e.toolAllowlist??[],toolBlocklist:e.toolBlocklist??[],validateOAuthEndpoints:e.validateOAuthEndpoints??!0,allowedOAuthDomains:e.allowedOAuthDomains??[],detectToolShadowing:e.detectToolShadowing??!0,minServerReputation:e.minServerReputation??30,strictMode:e.strictMode??!1,customInjectionPatterns:e.customInjectionPatterns??[]};for(const i of this.config.trustedServers)this.registeredServers.set(i.serverId,{...i,registeredAt:Date.now(),reputationScore:i.reputationScore??90}),this.serverReputation.set(i.serverId,i.reputationScore??90)}validateServerRegistration(e,i){const s=i||`mcp-reg-${Date.now()}`,t=[];let o=!1,r=!1,a=!1,h=!0,n=50;const{server:l,tools:d,oauth:f,signature:g,timestamp:_}=e;this.isServerBlocked(l.serverId,l.name)&&(t.push("server_blocked"),n=0);const v=this.checkMaliciousPatterns(l);if(v.suspicious&&(t.push(...v.violations),n-=30),this.config.requireServerSignature?!g||!l.publicKey?t.push("missing_server_signature"):(r=this.verifyServerSignature(l,g),r?(o=!0,n+=20):(t.push("invalid_server_signature"),n-=40)):o=!0,this.config.detectToolShadowing){const c=this.detectToolShadowing(d,l.serverId);c.detected&&(a=!0,t.push(...c.violations),n-=50)}for(const c of d)this.config.toolAllowlist.length>0&&!this.config.toolAllowlist.includes(c.name)&&(t.push(`tool_not_in_allowlist: ${c.name}`),h=!1),this.config.toolBlocklist.includes(c.name)&&(t.push(`tool_blocked: ${c.name}`),h=!1),this.detectInjection(c.description).detected&&(t.push(`injection_in_tool_description: ${c.name}`),n-=20);if(f&&this.config.validateOAuthEndpoints){const c=this.validateOAuthConfig(f);c.valid||(t.push(...c.violations),n-=30)}const m=Date.now()-_;m<0?t.push("future_timestamp"):m>300*1e3&&t.push("stale_registration"),!this.config.allowDynamicRegistration&&!this.isTrustedServer(l.serverId)&&t.push("dynamic_registration_disabled"),n=Math.max(0,Math.min(100,n));const p=n<this.config.minServerReputation||this.config.strictMode&&t.length>0||a;return p||this.registerServer(l,d,n),{allowed:!p,reason:p?`Server registration blocked: ${t.slice(0,3).join(", ")}`:"Server registration validated",violations:t,request_id:s,server_analysis:{server_verified:o,signature_valid:r,reputation_score:n,is_shadowing:a,tools_allowed:h},recommendations:this.generateRecommendations(t,"registration")}}validateToolCall(e,i){const s=i||`mcp-call-${Date.now()}`,t=[];let o=!1,r=!0,a=!0,h=!1,n="low";const{toolName:l,serverId:d,parameters:f}=e,g=this.registeredTools.get(l);if(g){o=!0,n=g.riskLevel||"low";const p=this.toolToServer.get(l);p&&p!==d&&(t.push("server_tool_mismatch"),h=!0)}else t.push("tool_not_registered");const _=this.serverReputation.get(d)??0;_<this.config.minServerReputation&&t.push("low_server_reputation"),this.config.toolAllowlist.length>0&&!this.config.toolAllowlist.includes(l)&&(t.push("tool_not_in_allowlist"),r=!1),this.config.toolBlocklist.includes(l)&&(t.push("tool_blocked"),r=!1);const v=this.scanParameters(f);if(v.injectionDetected&&(h=!0,a=!1,t.push(...v.violations)),this.isHighRiskOperation(l,f)&&(n="high",_<70&&t.push("high_risk_low_reputation")),t.length>0){const p=this.serverViolations.get(d)||0;this.serverViolations.set(d,p+t.length);const c=this.serverReputation.get(d)||50;this.serverReputation.set(d,Math.max(0,c-t.length*5))}const m=!o||!r||h||this.config.strictMode&&t.length>0;return{allowed:!m,reason:m?`Tool call blocked: ${t.slice(0,3).join(", ")}`:"Tool call validated",violations:t,request_id:s,tool_analysis:{tool_registered:o,tool_allowed:r,parameters_safe:a,injection_detected:h,risk_level:n},server_analysis:{server_verified:this.registeredServers.has(d),signature_valid:!0,reputation_score:_,is_shadowing:!1,tools_allowed:r},recommendations:this.generateRecommendations(t,"tool_call")}}registerTrustedServer(e,i){this.registerServer(e,i,90)}blockServer(e){this.config.blockedServers.includes(e)||this.config.blockedServers.push(e),this.registeredServers.delete(e),this.serverReputation.set(e,0)}getServerReputation(e){return this.serverReputation.get(e)??0}updateServerReputation(e,i){const s=this.serverReputation.get(e)??50;this.serverReputation.set(e,Math.max(0,Math.min(100,s+i)))}getRegisteredServers(){return[...this.registeredServers.values()]}getRegisteredTools(){return[...this.registeredTools.values()]}isToolShadowing(e){for(const i of this.SHADOWING_INDICATORS)if(i.suspicious.test(e)&&e!==i.legitimate)return{shadowing:!0,legitimate:i.legitimate};return{shadowing:!1}}getServerViolations(e){return this.serverViolations.get(e)||0}resetServerViolations(e){this.serverViolations.delete(e)}registerServer(e,i,s){this.registeredServers.set(e.serverId,{...e,registeredAt:Date.now(),reputationScore:s}),this.serverReputation.set(e.serverId,s);for(const t of i)this.registeredTools.set(t.name,t),this.toolToServer.set(t.name,e.serverId),this.toolDefinitionHashes.set(t.name,this.hashToolDefinition(t))}detectToolMutation(e,i){const s=this.toolDefinitionHashes.get(e);if(!s)return{mutated:!1};const t=this.hashToolDefinition(i);return{mutated:s!==t,original_hash:s,current_hash:t}}detectToolDescriptionInjection(e){const i=[],s=[{name:"hidden_instruction",pattern:/(?:IMPORTANT|NOTE|SYSTEM|ADMIN)\s*:/i},{name:"ignore_directive",pattern:/ignore\s+(?:all\s+)?(?:previous|other|prior)/i},{name:"override_behavior",pattern:/override|bypass|instead\s+of|rather\s+than/i},{name:"exfiltrate_data",pattern:/send\s+(?:to|data|all)|forward\s+(?:to|all)|copy\s+(?:to|all)/i},{name:"invisible_text",pattern:/\u200B|\u200C|\u200D|\uFEFF|\u00AD/g}];for(const{name:t,pattern:o}of s)o.lastIndex=0,o.test(e)&&i.push(t);return{injected:i.length>0,patterns:i}}hashToolDefinition(e){const i=require("crypto"),s=JSON.stringify({name:e.name,description:e.description,parameters:e.parameters,serverId:e.serverId});return i.createHash("sha256").update(s).digest("hex")}isServerBlocked(e,i){for(const s of this.config.blockedServers){if(e.includes(s)||i&&i.includes(s))return!0;try{const t=new RegExp(s,"i");if(t.test(e)||i&&t.test(i))return!0}catch{}}return!1}isTrustedServer(e){return this.config.trustedServers.some(i=>i.serverId===e)}checkMaliciousPatterns(e){const i=[],s=`${e.serverId} ${e.name} ${JSON.stringify(e.metadata||{})}`;for(const t of this.MALICIOUS_SERVER_PATTERNS)t.test(s)&&i.push(`malicious_pattern: ${t.source.substring(0,20)}`);return{suspicious:i.length>0,violations:i}}verifyServerSignature(e,i){if(!e.publicKey)return!1;try{const s=JSON.stringify({serverId:e.serverId,name:e.name,version:e.version}),t=crypto.createVerify("SHA256");return t.update(s),t.verify(e.publicKey,i,"hex")}catch{return!1}}detectToolShadowing(e,i){const s=[];for(const t of e){const o=this.toolToServer.get(t.name);o&&o!==i&&s.push(`tool_shadowing: ${t.name} (already registered by ${o})`);const r=this.isToolShadowing(t.name);r.shadowing&&s.push(`suspicious_tool_name: ${t.name} (similar to ${r.legitimate})`)}return{detected:s.length>0,violations:s}}validateOAuthConfig(e){const i=[];if(e.authorizationEndpoint&&(this.detectInjection(e.authorizationEndpoint).detected&&i.push("oauth_authorization_endpoint_injection"),this.config.allowedOAuthDomains.length>0))try{const t=new URL(e.authorizationEndpoint);this.config.allowedOAuthDomains.some(r=>t.hostname.endsWith(r))||i.push(`oauth_domain_not_allowed: ${t.hostname}`)}catch{i.push("invalid_oauth_authorization_url")}return e.tokenEndpoint&&this.detectInjection(e.tokenEndpoint).detected&&i.push("oauth_token_endpoint_injection"),{valid:i.length===0,violations:i}}detectInjection(e){const i=[],s=[...this.COMMAND_INJECTION_PATTERNS,...this.config.customInjectionPatterns.map((t,o)=>({name:`custom_${o}`,pattern:t,severity:50}))];for(const{name:t,pattern:o}of s)o.test(e)&&i.push(t);return{detected:i.length>0,patterns:i}}scanParameters(e){const i=[],s=JSON.stringify(e),t=this.detectInjection(s);t.detected&&i.push(...t.patterns.map(r=>`param_injection_${r}`));for(const[r,a]of Object.entries(e))typeof a=="string"&&a.length>1e4&&i.push(`oversized_parameter: ${r}`);const o=["__proto__","constructor","prototype","eval","exec"];for(const r of Object.keys(e))o.includes(r.toLowerCase())&&i.push(`suspicious_parameter_key: ${r}`);for(const[r,a]of Object.entries(e))typeof a=="string"&&(/^https?:\/\/(?:127\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|0\.|localhost|169\.254\.|0\.0\.0\.0|\[?::1\]?)/i.test(a)&&i.push(`ssrf_internal_ip: ${r}`),/^(?:file|gopher|dict|ftp|ldap|ssh|telnet):\/\//i.test(a)&&i.push(`ssrf_dangerous_protocol: ${r}`),/%252e%252e|%c0%ae%c0%ae|%2e%2e%5c|\.\.%255c|\.\.%c0%af|\.\.%c1%9c/i.test(a)&&i.push(`encoded_path_traversal: ${r}`),/\/etc\/(?:passwd|shadow|hosts)|\/proc\/self|\/dev\/(?:null|random)|\.ssh\/|\.env/i.test(a)&&i.push(`sensitive_file_access: ${r}`));return{injectionDetected:i.length>0,violations:i}}isHighRiskOperation(e,i){const s=["execute_code","run_command","shell_exec","eval","file_write","file_delete","database_write","database_delete","send_email","make_payment","transfer_funds","modify_permissions","create_user","delete_user"],t=e.toLowerCase();if(s.some(r=>t.includes(r)))return!0;const o=JSON.stringify(i).toLowerCase();return!!(o.includes("delete")||o.includes("drop")||o.includes("truncate")||o.includes("exec"))}generateRecommendations(e,i){const s=[];return i==="registration"?(e.some(t=>t.includes("signature"))&&s.push("Enable server signature verification for production"),e.some(t=>t.includes("shadowing"))&&s.push("Review tool names for potential shadowing attacks"),e.some(t=>t.includes("oauth"))&&s.push("Configure OAuth domain allowlist"),e.some(t=>t.includes("malicious"))&&s.push("Block suspicious servers and review server sources")):(e.some(t=>t.includes("injection"))&&s.push("Sanitize tool parameters before execution"),e.some(t=>t.includes("reputation"))&&s.push("Only use tools from high-reputation servers"),e.some(t=>t.includes("not_registered"))&&s.push("Register tools before allowing execution")),s.length===0&&s.push(i==="registration"?"Server registration validated successfully":"Tool call validated successfully"),s}}exports.MCPSecurityGuard=MCPSecurityGuard;
|
|
1
|
+
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?(function(u,e,s,i){i===void 0&&(i=s);var t=Object.getOwnPropertyDescriptor(e,s);(!t||("get"in t?!e.__esModule:t.writable||t.configurable))&&(t={enumerable:!0,get:function(){return e[s]}}),Object.defineProperty(u,i,t)}):(function(u,e,s,i){i===void 0&&(i=s),u[i]=e[s]})),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?(function(u,e){Object.defineProperty(u,"default",{enumerable:!0,value:e})}):function(u,e){u.default=e}),__importStar=this&&this.__importStar||(function(){var u=function(e){return u=Object.getOwnPropertyNames||function(s){var i=[];for(var t in s)Object.prototype.hasOwnProperty.call(s,t)&&(i[i.length]=t);return i},u(e)};return function(e){if(e&&e.__esModule)return e;var s={};if(e!=null)for(var i=u(e),t=0;t<i.length;t++)i[t]!=="default"&&__createBinding(s,e,i[t]);return __setModuleDefault(s,e),s}})();Object.defineProperty(exports,"__esModule",{value:!0}),exports.MCPSecurityGuard=void 0;const crypto=__importStar(require("crypto"));class MCPSecurityGuard{constructor(e={}){this.registeredServers=new Map,this.registeredTools=new Map,this.serverReputation=new Map,this.toolToServer=new Map,this.serverViolations=new Map,this.toolDefinitionHashes=new Map,this.SAMPLING_ATTACK_PATTERNS=[{name:"sd_call_again",type:"resource_drain",pattern:/call\s+this\s+(?:again|once\s+more|repeatedly)|repeat\s+this\s+(?:call|request)|run\s+this\s+(?:again|tool\s+again)/i},{name:"sd_loop_until",type:"resource_drain",pattern:/loop\s+until|continue\s+(?:calling|running|until)|keep\s+(?:calling|running|generating|going)\s/i},{name:"sd_do_not_stop",type:"resource_drain",pattern:/do\s+not\s+stop\s+until|don'?t\s+stop\s+until|never\s+stop\s+generating|generate\s+as\s+many\s+as\s+possible/i},{name:"sd_n_times",type:"resource_drain",pattern:/\b\d{2,}\s+times\b|repeat\s+\d+\s+times|call\s+\d+\s+more\s+times/i},{name:"sd_exhaust_resources",type:"resource_drain",pattern:/exhaust\s+(?:all|resources|quota|rate.?limit)|max\s+out\s+(?:the\s+)?(?:quota|rate\s+limit)|use\s+all\s+(?:remaining\s+)?tokens/i},{name:"sd_fake_user_turn",type:"conversation_hijack",pattern:/\n\s*(?:User|Human)\s*:\s*(?=\S)/i},{name:"sd_fake_assistant_turn",type:"conversation_hijack",pattern:/\n\s*(?:Assistant|AI|Bot|Claude|GPT)\s*:\s*(?=\S)/i},{name:"sd_role_json",type:"conversation_hijack",pattern:/"role"\s*:\s*"(?:system|user|assistant)"/i},{name:"sd_system_xml",type:"conversation_hijack",pattern:/<(?:system|user|assistant)\s*>|<\/(?:system|user|assistant)>/i},{name:"sd_from_now_on",type:"conversation_hijack",pattern:/from\s+now\s+on\s+you\s+(?:are|will|must)|henceforth\s+you|for\s+the\s+rest\s+of\s+(?:this\s+)?(?:conversation|session)\s+you/i},{name:"sd_new_instructions",type:"conversation_hijack",pattern:/your\s+new\s+(?:instructions|system\s+prompt|directives?)\s+(?:are|is)|updated\s+system\s+prompt|override\s+your\s+(?:system|instructions)/i},{name:"sd_ignore_previous",type:"conversation_hijack",pattern:/ignore\s+(?:all\s+)?(?:previous|prior|earlier)\s+instructions|disregard\s+(?:your\s+)?instructions/i},{name:"sd_anthropic_tool_xml",type:"covert_tool_invocation",pattern:/<(?:tool_use|function_calls|invoke)[\s>]/i},{name:"sd_tool_result_xml",type:"covert_tool_invocation",pattern:/<(?:tool_result|function_result)[\s>]/i},{name:"sd_openai_tool_call",type:"covert_tool_invocation",pattern:/"type"\s*:\s*"tool_use"|"tool_calls"\s*:\s*\[/i},{name:"sd_bracket_tool_call",type:"covert_tool_invocation",pattern:/\[(?:TOOL|FUNCTION|CALL)\s*:/i},{name:"sd_double_brace_call",type:"covert_tool_invocation",pattern:/\{\{\s*(?:call|tool|function|invoke)\s*:/i},{name:"sd_invoke_name_attr",type:"covert_tool_invocation",pattern:/<invoke\s+name\s*=/i}],this.COMMAND_INJECTION_PATTERNS=[{name:"shell_injection",pattern:/[;&|`$]|\$\(|\)\s*[;&|]|`[^`]+`/g,severity:50},{name:"command_substitution",pattern:/\$\{[^}]+\}|\$\([^)]+\)/g,severity:50},{name:"pipe_injection",pattern:/\|\s*(cat|rm|curl|wget|nc|bash|sh|exec)/i,severity:55},{name:"path_traversal",pattern:/\.\.[\/\\]|\.\.%2[fF]/g,severity:45},{name:"absolute_path",pattern:/^\/(?:etc|usr|var|tmp|bin|root)/i,severity:40},{name:"oauth_injection",pattern:/authorization_endpoint.*[;&|`$]/i,severity:55},{name:"redirect_manipulation",pattern:/redirect_uri.*[^\w\-_.~:/?#[\]@!$&'()*+,;=%]/i,severity:45},{name:"applescript_injection",pattern:/osascript|do\s+shell\s+script|tell\s+application/i,severity:55},{name:"git_injection",pattern:/--upload-pack|--receive-pack|-c\s+core\./i,severity:50},{name:"git_url_injection",pattern:/ext::|file:\/\/|ssh:\/\/.*@/i,severity:45},{name:"argument_injection",pattern:/\s--[a-z]+=.*[;&|`$]/i,severity:45},{name:"env_injection",pattern:/\bLD_PRELOAD\b|\bPATH\s*=/i,severity:50}],this.SHADOWING_INDICATORS=[{legitimate:"file_reader",suspicious:/file[-_]?read(er)?s?|read[-_]?files?/i},{legitimate:"database_query",suspicious:/db[-_]?query|sql[-_]?query|query[-_]?db/i},{legitimate:"email_sender",suspicious:/send[-_]?emails?|email[-_]?send(er)?/i},{legitimate:"api_caller",suspicious:/call[-_]?api|api[-_]?call(er)?/i},{legitimate:"code_executor",suspicious:/exec[-_]?code|run[-_]?code|code[-_]?run/i}],this.MALICIOUS_SERVER_PATTERNS=[/postmark-mcp.*fake/i,/unofficial/i,/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/,/pastebin|gist\.github/i,/temp|tmp|test.*mcp/i],this.config={requireServerSignature:e.requireServerSignature??!1,trustedServers:e.trustedServers??[],blockedServers:e.blockedServers??[],allowDynamicRegistration:e.allowDynamicRegistration??!0,toolAllowlist:e.toolAllowlist??[],toolBlocklist:e.toolBlocklist??[],validateOAuthEndpoints:e.validateOAuthEndpoints??!0,allowedOAuthDomains:e.allowedOAuthDomains??[],detectToolShadowing:e.detectToolShadowing??!0,minServerReputation:e.minServerReputation??30,strictMode:e.strictMode??!1,customInjectionPatterns:e.customInjectionPatterns??[]};for(const s of this.config.trustedServers)this.registeredServers.set(s.serverId,{...s,registeredAt:Date.now(),reputationScore:s.reputationScore??90}),this.serverReputation.set(s.serverId,s.reputationScore??90)}validateServerRegistration(e,s){const i=s||`mcp-reg-${Date.now()}`,t=[];let r=!1,o=!1,a=!1,_=!0,l=50;const{server:n,tools:d,oauth:f,signature:m,timestamp:p}=e;this.isServerBlocked(n.serverId,n.name)&&(t.push("server_blocked"),l=0);const h=this.checkMaliciousPatterns(n);if(h.suspicious&&(t.push(...h.violations),l-=30),this.config.requireServerSignature?!m||!n.publicKey?t.push("missing_server_signature"):(o=this.verifyServerSignature(n,m),o?(r=!0,l+=20):(t.push("invalid_server_signature"),l-=40)):r=!0,this.config.detectToolShadowing){const c=this.detectToolShadowing(d,n.serverId);c.detected&&(a=!0,t.push(...c.violations),l-=50)}for(const c of d)this.config.toolAllowlist.length>0&&!this.config.toolAllowlist.includes(c.name)&&(t.push(`tool_not_in_allowlist: ${c.name}`),_=!1),this.config.toolBlocklist.includes(c.name)&&(t.push(`tool_blocked: ${c.name}`),_=!1),this.detectInjection(c.description).detected&&(t.push(`injection_in_tool_description: ${c.name}`),l-=20);if(f&&this.config.validateOAuthEndpoints){const c=this.validateOAuthConfig(f);c.valid||(t.push(...c.violations),l-=30)}const v=Date.now()-p;v<0?t.push("future_timestamp"):v>300*1e3&&t.push("stale_registration"),!this.config.allowDynamicRegistration&&!this.isTrustedServer(n.serverId)&&t.push("dynamic_registration_disabled"),l=Math.max(0,Math.min(100,l));const g=l<this.config.minServerReputation||this.config.strictMode&&t.length>0||a;return g||this.registerServer(n,d,l),{allowed:!g,reason:g?`Server registration blocked: ${t.slice(0,3).join(", ")}`:"Server registration validated",violations:t,request_id:i,server_analysis:{server_verified:r,signature_valid:o,reputation_score:l,is_shadowing:a,tools_allowed:_},recommendations:this.generateRecommendations(t,"registration")}}validateToolCall(e,s){const i=s||`mcp-call-${Date.now()}`,t=[];let r=!1,o=!0,a=!0,_=!1,l="low";const{toolName:n,serverId:d,parameters:f}=e,m=this.registeredTools.get(n);if(m){r=!0,l=m.riskLevel||"low";const g=this.toolToServer.get(n);g&&g!==d&&(t.push("server_tool_mismatch"),_=!0)}else t.push("tool_not_registered");const p=this.serverReputation.get(d)??0;p<this.config.minServerReputation&&t.push("low_server_reputation"),this.config.toolAllowlist.length>0&&!this.config.toolAllowlist.includes(n)&&(t.push("tool_not_in_allowlist"),o=!1),this.config.toolBlocklist.includes(n)&&(t.push("tool_blocked"),o=!1);const h=this.scanParameters(f);if(h.injectionDetected&&(_=!0,a=!1,t.push(...h.violations)),this.isHighRiskOperation(n,f)&&(l="high",p<70&&t.push("high_risk_low_reputation")),t.length>0){const g=this.serverViolations.get(d)||0;this.serverViolations.set(d,g+t.length);const c=this.serverReputation.get(d)||50;this.serverReputation.set(d,Math.max(0,c-t.length*5))}const v=!r||!o||_||this.config.strictMode&&t.length>0;return{allowed:!v,reason:v?`Tool call blocked: ${t.slice(0,3).join(", ")}`:"Tool call validated",violations:t,request_id:i,tool_analysis:{tool_registered:r,tool_allowed:o,parameters_safe:a,injection_detected:_,risk_level:l},server_analysis:{server_verified:this.registeredServers.has(d),signature_valid:!0,reputation_score:p,is_shadowing:!1,tools_allowed:o},recommendations:this.generateRecommendations(t,"tool_call")}}validateSamplingResponse(e,s){const i=s||`mcp-sampling-${Date.now()}`,t=[];let r=!1,o=!1,a=!1;const _=[],{content:l,serverId:n}=e;(this.serverReputation.get(n)??50)<this.config.minServerReputation&&t.push("low_server_reputation");for(const{name:p,type:h,pattern:v}of this.SAMPLING_ATTACK_PATTERNS)v.lastIndex=0,v.test(l)&&(_.push(p),t.push(`sampling_${h}_${p}`),h==="resource_drain"?r=!0:h==="conversation_hijack"?o=!0:h==="covert_tool_invocation"&&(a=!0));const f=this.detectInjection(l.slice(0,2e3));if(f.detected&&t.push(...f.patterns.map(p=>`sampling_cmd_${p}`)),t.length>0&&n){const p=this.serverReputation.get(n)??50;this.serverReputation.set(n,Math.max(0,p-t.length*10));const h=this.serverViolations.get(n)||0;this.serverViolations.set(n,h+t.length)}const m=r||o||a||this.config.strictMode&&t.length>0;return{allowed:!m,reason:m?`Sampling response blocked: ${t.slice(0,3).join(", ")}`:"Sampling response validated",violations:t,request_id:i,sampling_analysis:{resource_drain_detected:r,conversation_hijack_detected:o,covert_tool_invocation_detected:a,pattern_matches:_},recommendations:this.generateRecommendations(t,"sampling")}}registerTrustedServer(e,s){this.registerServer(e,s,90)}blockServer(e){this.config.blockedServers.includes(e)||this.config.blockedServers.push(e),this.registeredServers.delete(e),this.serverReputation.set(e,0)}getServerReputation(e){return this.serverReputation.get(e)??0}updateServerReputation(e,s){const i=this.serverReputation.get(e)??50;this.serverReputation.set(e,Math.max(0,Math.min(100,i+s)))}getRegisteredServers(){return[...this.registeredServers.values()]}getRegisteredTools(){return[...this.registeredTools.values()]}isToolShadowing(e){for(const s of this.SHADOWING_INDICATORS)if(s.suspicious.test(e)&&e!==s.legitimate)return{shadowing:!0,legitimate:s.legitimate};return{shadowing:!1}}getServerViolations(e){return this.serverViolations.get(e)||0}resetServerViolations(e){this.serverViolations.delete(e)}registerServer(e,s,i){this.registeredServers.set(e.serverId,{...e,registeredAt:Date.now(),reputationScore:i}),this.serverReputation.set(e.serverId,i);for(const t of s)this.registeredTools.set(t.name,t),this.toolToServer.set(t.name,e.serverId),this.toolDefinitionHashes.set(t.name,this.hashToolDefinition(t))}detectToolMutation(e,s){const i=this.toolDefinitionHashes.get(e);if(!i)return{mutated:!1};const t=this.hashToolDefinition(s);return{mutated:i!==t,original_hash:i,current_hash:t}}detectToolDescriptionInjection(e){const s=[],i=[{name:"hidden_instruction",pattern:/(?:IMPORTANT|NOTE|SYSTEM|ADMIN)\s*:/i},{name:"ignore_directive",pattern:/ignore\s+(?:all\s+)?(?:previous|other|prior)/i},{name:"override_behavior",pattern:/override|bypass|instead\s+of|rather\s+than/i},{name:"exfiltrate_data",pattern:/send\s+(?:to|data|all)|forward\s+(?:to|all)|copy\s+(?:to|all)/i},{name:"invisible_text",pattern:/\u200B|\u200C|\u200D|\uFEFF|\u00AD/g}];for(const{name:t,pattern:r}of i)r.lastIndex=0,r.test(e)&&s.push(t);return{injected:s.length>0,patterns:s}}hashToolDefinition(e){const s=require("crypto"),i=JSON.stringify({name:e.name,description:e.description,parameters:e.parameters,serverId:e.serverId});return s.createHash("sha256").update(i).digest("hex")}isServerBlocked(e,s){for(const i of this.config.blockedServers){if(e.includes(i)||s&&s.includes(i))return!0;try{const t=new RegExp(i,"i");if(t.test(e)||s&&t.test(s))return!0}catch{}}return!1}isTrustedServer(e){return this.config.trustedServers.some(s=>s.serverId===e)}checkMaliciousPatterns(e){const s=[],i=`${e.serverId} ${e.name} ${JSON.stringify(e.metadata||{})}`;for(const t of this.MALICIOUS_SERVER_PATTERNS)t.test(i)&&s.push(`malicious_pattern: ${t.source.substring(0,20)}`);return{suspicious:s.length>0,violations:s}}verifyServerSignature(e,s){if(!e.publicKey)return!1;try{const i=JSON.stringify({serverId:e.serverId,name:e.name,version:e.version}),t=crypto.createVerify("SHA256");return t.update(i),t.verify(e.publicKey,s,"hex")}catch{return!1}}detectToolShadowing(e,s){const i=[];for(const t of e){const r=this.toolToServer.get(t.name);r&&r!==s&&i.push(`tool_shadowing: ${t.name} (already registered by ${r})`);const o=this.isToolShadowing(t.name);o.shadowing&&i.push(`suspicious_tool_name: ${t.name} (similar to ${o.legitimate})`)}return{detected:i.length>0,violations:i}}validateOAuthConfig(e){const s=[];if(e.authorizationEndpoint&&(this.detectInjection(e.authorizationEndpoint).detected&&s.push("oauth_authorization_endpoint_injection"),this.config.allowedOAuthDomains.length>0))try{const t=new URL(e.authorizationEndpoint);this.config.allowedOAuthDomains.some(o=>t.hostname.endsWith(o))||s.push(`oauth_domain_not_allowed: ${t.hostname}`)}catch{s.push("invalid_oauth_authorization_url")}return e.tokenEndpoint&&this.detectInjection(e.tokenEndpoint).detected&&s.push("oauth_token_endpoint_injection"),{valid:s.length===0,violations:s}}detectInjection(e){const s=[],i=[...this.COMMAND_INJECTION_PATTERNS,...this.config.customInjectionPatterns.map((t,r)=>({name:`custom_${r}`,pattern:t,severity:50}))];for(const{name:t,pattern:r}of i)r.test(e)&&s.push(t);return{detected:s.length>0,patterns:s}}scanParameters(e){const s=[],i=JSON.stringify(e),t=this.detectInjection(i);t.detected&&s.push(...t.patterns.map(o=>`param_injection_${o}`));for(const[o,a]of Object.entries(e))typeof a=="string"&&a.length>1e4&&s.push(`oversized_parameter: ${o}`);const r=["__proto__","constructor","prototype","eval","exec"];for(const o of Object.keys(e))r.includes(o.toLowerCase())&&s.push(`suspicious_parameter_key: ${o}`);for(const[o,a]of Object.entries(e))typeof a=="string"&&(/^https?:\/\/(?:127\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|0\.|localhost|169\.254\.|0\.0\.0\.0|\[?::1\]?)/i.test(a)&&s.push(`ssrf_internal_ip: ${o}`),/^(?:file|gopher|dict|ftp|ldap|ssh|telnet):\/\//i.test(a)&&s.push(`ssrf_dangerous_protocol: ${o}`),/%252e%252e|%c0%ae%c0%ae|%2e%2e%5c|\.\.%255c|\.\.%c0%af|\.\.%c1%9c/i.test(a)&&s.push(`encoded_path_traversal: ${o}`),/\/etc\/(?:passwd|shadow|hosts)|\/proc\/self|\/dev\/(?:null|random)|\.ssh\/|\.env/i.test(a)&&s.push(`sensitive_file_access: ${o}`));return{injectionDetected:s.length>0,violations:s}}isHighRiskOperation(e,s){const i=["execute_code","run_command","shell_exec","eval","file_write","file_delete","database_write","database_delete","send_email","make_payment","transfer_funds","modify_permissions","create_user","delete_user"],t=e.toLowerCase();if(i.some(o=>t.includes(o)))return!0;const r=JSON.stringify(s).toLowerCase();return!!(r.includes("delete")||r.includes("drop")||r.includes("truncate")||r.includes("exec"))}generateRecommendations(e,s){const i=[];return s==="registration"?(e.some(t=>t.includes("signature"))&&i.push("Enable server signature verification for production"),e.some(t=>t.includes("shadowing"))&&i.push("Review tool names for potential shadowing attacks"),e.some(t=>t.includes("oauth"))&&i.push("Configure OAuth domain allowlist"),e.some(t=>t.includes("malicious"))&&i.push("Block suspicious servers and review server sources")):s==="tool_call"?(e.some(t=>t.includes("injection"))&&i.push("Sanitize tool parameters before execution"),e.some(t=>t.includes("reputation"))&&i.push("Only use tools from high-reputation servers"),e.some(t=>t.includes("not_registered"))&&i.push("Register tools before allowing execution")):(e.some(t=>t.includes("resource_drain"))&&i.push("Block this MCP server \u2014 sampling response contains resource exhaustion directives (Unit42/Blueinfy Feb 2026)"),e.some(t=>t.includes("conversation_hijack"))&&i.push("Block this MCP server \u2014 sampling response attempts conversation hijacking via role injection"),e.some(t=>t.includes("covert_tool_invocation"))&&i.push("Block this MCP server \u2014 sampling response embeds covert tool-call syntax")),i.length===0&&i.push(s==="registration"?"Server registration validated successfully":s==="tool_call"?"Tool call validated successfully":"Sampling response validated successfully"),i}}exports.MCPSecurityGuard=MCPSecurityGuard;
|
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export { CodeExecutionGuard, CodeExecutionGuardConfig, CodeAnalysisResult, Sandb
|
|
|
37
37
|
export { AgentCommunicationGuard, AgentCommunicationGuardConfig, AgentIdentity, AgentMessage, MessageValidationResult } from "./guards/agent-communication-guard";
|
|
38
38
|
export { CircuitBreaker, CircuitBreakerConfig, CircuitState, CircuitStats, CircuitBreakerResult } from "./guards/circuit-breaker";
|
|
39
39
|
export { DriftDetector, DriftDetectorConfig, BehaviorSample, BaselineProfile, DriftAnalysis, DriftDetectorResult } from "./guards/drift-detector";
|
|
40
|
-
export { MCPSecurityGuard, MCPSecurityGuardConfig, MCPServerIdentity, MCPToolDefinition, MCPServerRegistration, MCPToolCall, MCPSecurityResult } from "./guards/mcp-security-guard";
|
|
40
|
+
export { MCPSecurityGuard, MCPSecurityGuardConfig, MCPServerIdentity, MCPToolDefinition, MCPServerRegistration, MCPToolCall, MCPSamplingResponse, MCPSecurityResult } from "./guards/mcp-security-guard";
|
|
41
41
|
export { PromptLeakageGuard, PromptLeakageGuardConfig, PromptLeakageResult, OutputLeakageResult } from "./guards/prompt-leakage-guard";
|
|
42
42
|
export { TrustExploitationGuard, TrustExploitationGuardConfig, AgentAction, TrustContext, TrustExploitationResult } from "./guards/trust-exploitation-guard";
|
|
43
43
|
export { AutonomyEscalationGuard, AutonomyEscalationGuardConfig, AutonomyRequest, AgentCapabilities, AutonomyEscalationResult } from "./guards/autonomy-escalation-guard";
|