mcptrustchecker 1.0.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/LICENSE +21 -0
- package/README.md +379 -0
- package/dist/acquire/clientConfig.d.ts +47 -0
- package/dist/acquire/clientConfig.js +124 -0
- package/dist/acquire/discover.d.ts +12 -0
- package/dist/acquire/discover.js +53 -0
- package/dist/acquire/index.d.ts +24 -0
- package/dist/acquire/index.js +175 -0
- package/dist/acquire/live.d.ts +47 -0
- package/dist/acquire/live.js +357 -0
- package/dist/acquire/manifest.d.ts +10 -0
- package/dist/acquire/manifest.js +94 -0
- package/dist/acquire/npm.d.ts +10 -0
- package/dist/acquire/npm.js +55 -0
- package/dist/acquire/source.d.ts +16 -0
- package/dist/acquire/source.js +109 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.js +396 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +81 -0
- package/dist/data/capabilityLexicon.d.ts +51 -0
- package/dist/data/capabilityLexicon.js +204 -0
- package/dist/data/confusables.d.ts +17 -0
- package/dist/data/confusables.js +65 -0
- package/dist/data/injectionPatterns.d.ts +55 -0
- package/dist/data/injectionPatterns.js +216 -0
- package/dist/data/knownCves.d.ts +24 -0
- package/dist/data/knownCves.js +49 -0
- package/dist/data/protectedPackages.d.ts +30 -0
- package/dist/data/protectedPackages.js +89 -0
- package/dist/data/ruleCatalog.d.ts +16 -0
- package/dist/data/ruleCatalog.js +94 -0
- package/dist/data/sourcePatterns.d.ts +25 -0
- package/dist/data/sourcePatterns.js +92 -0
- package/dist/data/unicode.d.ts +34 -0
- package/dist/data/unicode.js +99 -0
- package/dist/detectors/capability.d.ts +14 -0
- package/dist/detectors/capability.js +281 -0
- package/dist/detectors/collision.d.ts +11 -0
- package/dist/detectors/collision.js +63 -0
- package/dist/detectors/flowGraph.d.ts +43 -0
- package/dist/detectors/flowGraph.js +100 -0
- package/dist/detectors/index.d.ts +18 -0
- package/dist/detectors/index.js +26 -0
- package/dist/detectors/injection.d.ts +9 -0
- package/dist/detectors/injection.js +178 -0
- package/dist/detectors/meta.d.ts +8 -0
- package/dist/detectors/meta.js +33 -0
- package/dist/detectors/posture.d.ts +9 -0
- package/dist/detectors/posture.js +128 -0
- package/dist/detectors/source.d.ts +12 -0
- package/dist/detectors/source.js +87 -0
- package/dist/detectors/supplyChain.d.ts +17 -0
- package/dist/detectors/supplyChain.js +243 -0
- package/dist/detectors/toxicFlow.d.ts +22 -0
- package/dist/detectors/toxicFlow.js +201 -0
- package/dist/detectors/unicode.d.ts +21 -0
- package/dist/detectors/unicode.js +244 -0
- package/dist/engine.d.ts +27 -0
- package/dist/engine.js +135 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +46 -0
- package/dist/lockfile.d.ts +30 -0
- package/dist/lockfile.js +87 -0
- package/dist/policy.d.ts +13 -0
- package/dist/policy.js +48 -0
- package/dist/report/badge.d.ts +14 -0
- package/dist/report/badge.js +18 -0
- package/dist/report/json.d.ts +4 -0
- package/dist/report/json.js +4 -0
- package/dist/report/markdown.d.ts +6 -0
- package/dist/report/markdown.js +110 -0
- package/dist/report/sarif.d.ts +7 -0
- package/dist/report/sarif.js +105 -0
- package/dist/report/terminal.d.ts +15 -0
- package/dist/report/terminal.js +252 -0
- package/dist/scoring/capability.d.ts +10 -0
- package/dist/scoring/capability.js +48 -0
- package/dist/scoring/index.d.ts +10 -0
- package/dist/scoring/index.js +96 -0
- package/dist/scoring/model.d.ts +50 -0
- package/dist/scoring/model.js +101 -0
- package/dist/types.d.ts +361 -0
- package/dist/types.js +12 -0
- package/dist/util/ansi.d.ts +26 -0
- package/dist/util/ansi.js +42 -0
- package/dist/util/capabilities.d.ts +17 -0
- package/dist/util/capabilities.js +68 -0
- package/dist/util/distance.d.ts +23 -0
- package/dist/util/distance.js +119 -0
- package/dist/util/hash.d.ts +20 -0
- package/dist/util/hash.js +80 -0
- package/dist/util/text.d.ts +45 -0
- package/dist/util/text.js +170 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +13 -0
- package/package.json +72 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* The curated "protected" MCP package list. Typosquatting is checked only
|
|
4
|
+
* against this anchor set (never all-pairs), which is both fast and precise.
|
|
5
|
+
* Weekly-download figures feed the download-anomaly gate: a near-miss on a
|
|
6
|
+
* high-traffic name with near-zero downloads of its own is malicious, not a
|
|
7
|
+
* coincidence.
|
|
8
|
+
*/
|
|
9
|
+
export const PROTECTED_PACKAGES = [
|
|
10
|
+
// --- Official @modelcontextprotocol npm scope ---
|
|
11
|
+
{ name: '@modelcontextprotocol/sdk', registry: 'npm', weeklyDownloads: 42_800_000, official: true },
|
|
12
|
+
{ name: '@modelcontextprotocol/inspector', registry: 'npm', official: true },
|
|
13
|
+
{ name: '@modelcontextprotocol/create-server', registry: 'npm', official: true },
|
|
14
|
+
{ name: '@modelcontextprotocol/server-filesystem', registry: 'npm', official: true },
|
|
15
|
+
{ name: '@modelcontextprotocol/server-memory', registry: 'npm', official: true },
|
|
16
|
+
{ name: '@modelcontextprotocol/server-sequential-thinking', registry: 'npm', official: true },
|
|
17
|
+
{ name: '@modelcontextprotocol/server-everything', registry: 'npm', official: true },
|
|
18
|
+
{ name: '@modelcontextprotocol/server-github', registry: 'npm', official: true },
|
|
19
|
+
{ name: '@modelcontextprotocol/server-gitlab', registry: 'npm', official: true },
|
|
20
|
+
{ name: '@modelcontextprotocol/server-slack', registry: 'npm', official: true },
|
|
21
|
+
{ name: '@modelcontextprotocol/server-postgres', registry: 'npm', official: true },
|
|
22
|
+
{ name: '@modelcontextprotocol/server-puppeteer', registry: 'npm', official: true },
|
|
23
|
+
{ name: '@modelcontextprotocol/server-brave-search', registry: 'npm', official: true },
|
|
24
|
+
{ name: '@modelcontextprotocol/server-google-maps', registry: 'npm', official: true },
|
|
25
|
+
{ name: '@modelcontextprotocol/server-google-drive', registry: 'npm', official: true },
|
|
26
|
+
{ name: '@modelcontextprotocol/server-redis', registry: 'npm', official: true },
|
|
27
|
+
{ name: '@modelcontextprotocol/server-sentry', registry: 'npm', official: true },
|
|
28
|
+
{ name: 'mcp-remote', registry: 'npm', official: true },
|
|
29
|
+
// --- Official PyPI ---
|
|
30
|
+
{ name: 'mcp', registry: 'pypi', official: true },
|
|
31
|
+
{ name: 'mcp-server-git', registry: 'pypi', official: true },
|
|
32
|
+
{ name: 'mcp-server-fetch', registry: 'pypi', official: true },
|
|
33
|
+
{ name: 'mcp-server-time', registry: 'pypi', official: true },
|
|
34
|
+
{ name: 'mcp-server-sqlite', registry: 'pypi', official: true },
|
|
35
|
+
// --- Popular community / vendor (npm) with download weights ---
|
|
36
|
+
{ name: '@playwright/mcp', registry: 'npm', weeklyDownloads: 5_990_000 },
|
|
37
|
+
{ name: '@upstash/context7-mcp', registry: 'npm', weeklyDownloads: 524_000 },
|
|
38
|
+
{ name: '@notionhq/notion-mcp-server', registry: 'npm', weeklyDownloads: 126_000 },
|
|
39
|
+
{ name: 'firecrawl-mcp', registry: 'npm', weeklyDownloads: 93_000 },
|
|
40
|
+
{ name: '@sentry/mcp-server', registry: 'npm', weeklyDownloads: 85_000 },
|
|
41
|
+
{ name: 'figma-developer-mcp', registry: 'npm', weeklyDownloads: 76_000 },
|
|
42
|
+
{ name: '@supabase/mcp-server-supabase', registry: 'npm', weeklyDownloads: 65_000 },
|
|
43
|
+
{ name: '@wonderwhy-er/desktop-commander', registry: 'npm', weeklyDownloads: 43_000 },
|
|
44
|
+
{ name: 'tavily-mcp', registry: 'npm', weeklyDownloads: 28_000 },
|
|
45
|
+
{ name: 'exa-mcp-server', registry: 'npm', weeklyDownloads: 18_000 },
|
|
46
|
+
{ name: '@executeautomation/playwright-mcp-server', registry: 'npm', weeklyDownloads: 17_000 },
|
|
47
|
+
{ name: '@brave/brave-search-mcp-server', registry: 'npm', weeklyDownloads: 15_000 },
|
|
48
|
+
{ name: '@21st-dev/magic', registry: 'npm', weeklyDownloads: 13_000 },
|
|
49
|
+
{ name: '@stripe/mcp', registry: 'npm', weeklyDownloads: 11_000 },
|
|
50
|
+
{ name: '@browsermcp/mcp', registry: 'npm', weeklyDownloads: 9_000 },
|
|
51
|
+
{ name: '@heroku/mcp-server', registry: 'npm', weeklyDownloads: 6_000 },
|
|
52
|
+
{ name: '@cloudflare/mcp-server-cloudflare', registry: 'npm', weeklyDownloads: 2_000 },
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Fake official-scope / homoglyph scope patterns. Any package whose scope
|
|
56
|
+
* matches one of these is impersonating the official namespace.
|
|
57
|
+
*/
|
|
58
|
+
export const FAKE_SCOPE_PATTERNS = [
|
|
59
|
+
/^@modlecontextprotocol\//i,
|
|
60
|
+
/^@model-context-protocol\//i,
|
|
61
|
+
/^@modelcontextprotocal\//i,
|
|
62
|
+
/^@modelcontext-protocol\//i,
|
|
63
|
+
/^@modelcontextprotocol-/i, // unscoped hyphen shadow of the scope
|
|
64
|
+
];
|
|
65
|
+
/** Bare (unscoped) names that shadow official *scoped* packages. */
|
|
66
|
+
export const UNSCOPED_SHADOWS = new Set([
|
|
67
|
+
'server-filesystem',
|
|
68
|
+
'server-github',
|
|
69
|
+
'server-memory',
|
|
70
|
+
'server-slack',
|
|
71
|
+
'server-postgres',
|
|
72
|
+
'modelcontextprotocol-sdk',
|
|
73
|
+
]);
|
|
74
|
+
/**
|
|
75
|
+
* Pre-computed known squats mapping a malicious/confusable name to the real
|
|
76
|
+
* package it imitates (collapses several matchers to an instant hit).
|
|
77
|
+
*/
|
|
78
|
+
export const KNOWN_SQUATS = {
|
|
79
|
+
'playwright-mcp': '@playwright/mcp',
|
|
80
|
+
'playwright-mcp-server': '@playwright/mcp',
|
|
81
|
+
'mcp-playwright': '@playwright/mcp',
|
|
82
|
+
'@playwright/mcp-server': '@playwright/mcp',
|
|
83
|
+
'@playwrite/mcp': '@playwright/mcp',
|
|
84
|
+
'context7-mcp': '@upstash/context7-mcp',
|
|
85
|
+
'notion-mcp-server': '@notionhq/notion-mcp-server',
|
|
86
|
+
'firecrawl-mcp-server': 'firecrawl-mcp',
|
|
87
|
+
};
|
|
88
|
+
/** Combosquat suffixes stripped before comparison (e.g. `foo-js` ≈ `foo`). */
|
|
89
|
+
export const COMBOSQUAT_SUFFIXES = ['-js', '-py', '-core', '-utils', '-mcp', '-server', '-official', '-cli'];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Human-readable catalogue of every rule MCP Trust Checker can emit. Powers
|
|
4
|
+
* `mcptrustchecker rules` / `mcptrustchecker explain <id>` and docs/rules.md. Keep in sync
|
|
5
|
+
* with the detectors (tested by rules.test.ts).
|
|
6
|
+
*/
|
|
7
|
+
import type { Category, Severity } from '../types.js';
|
|
8
|
+
export interface RuleDoc {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
category: Category;
|
|
12
|
+
severity: Severity;
|
|
13
|
+
summary: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const RULE_CATALOG: RuleDoc[];
|
|
16
|
+
export declare function findRule(id: string): RuleDoc | undefined;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Human-readable catalogue of every rule MCP Trust Checker can emit. Powers
|
|
4
|
+
* `mcptrustchecker rules` / `mcptrustchecker explain <id>` and docs/rules.md. Keep in sync
|
|
5
|
+
* with the detectors (tested by rules.test.ts).
|
|
6
|
+
*/
|
|
7
|
+
export const RULE_CATALOG = [
|
|
8
|
+
// Stage 1 — Unicode
|
|
9
|
+
{ id: 'MTC-UNI-001', title: 'Tags-block smuggling channel', category: 'injection', severity: 'critical', summary: 'Invisible U+E0000 Tags characters decode to hidden ASCII instructions read by the model.' },
|
|
10
|
+
{ id: 'MTC-UNI-002', title: 'Variation-selector byte channel', category: 'injection', severity: 'high', summary: 'Variation selectors used as a 256-value side channel to smuggle a payload.' },
|
|
11
|
+
{ id: 'MTC-UNI-003', title: 'Bidirectional override', category: 'injection', severity: 'high', summary: 'BiDi override characters can reorder how text is displayed vs. how it is read.' },
|
|
12
|
+
{ id: 'MTC-UNI-004', title: 'Bidirectional isolate/mark', category: 'injection', severity: 'medium', summary: 'BiDi isolates/marks in metadata; can be used to hide or reorder content.' },
|
|
13
|
+
{ id: 'MTC-UNI-005', title: 'Zero-width / invisible-math run', category: 'injection', severity: 'medium', summary: 'Zero-width or invisible-math characters, above threshold, indicate an encoded payload.' },
|
|
14
|
+
{ id: 'MTC-UNI-006', title: 'Default-ignorable characters', category: 'injection', severity: 'medium', summary: 'Default-ignorable characters (soft hyphen, interlinear annotation, etc.) in metadata.' },
|
|
15
|
+
{ id: 'MTC-UNI-007', title: 'Unusual whitespace', category: 'injection', severity: 'low', summary: 'Non-breaking / ideographic / thin spaces used in place of normal spaces.' },
|
|
16
|
+
{ id: 'MTC-UNI-008', title: 'Control characters', category: 'injection', severity: 'high', summary: 'C0/C1 control characters (excluding tab/newline/CR) are abnormal in metadata.' },
|
|
17
|
+
{ id: 'MTC-UNI-009', title: 'Mixed-script / homoglyph text', category: 'injection', severity: 'high', summary: 'A single token mixes scripts (e.g. Latin + Cyrillic) — a homoglyph impersonation signal.' },
|
|
18
|
+
{ id: 'MTC-UNI-010', title: 'ANSI terminal escape sequence', category: 'injection', severity: 'high', summary: 'ANSI/CSI/OSC escapes in metadata can hide or rewrite what a terminal user sees (consent phishing).' },
|
|
19
|
+
// Stage 2 — Injection
|
|
20
|
+
{ id: 'MTC-INJ-AUTH-1', title: 'Authority framing', category: 'injection', severity: 'low', summary: '"IMPORTANT"/"ATTENTION"-style authority framing in tool metadata (weak signal on its own).' },
|
|
21
|
+
{ id: 'MTC-INJ-AUTH-2', title: 'Instruction-override directive', category: 'injection', severity: 'high', summary: '"ignore previous instructions"-style override aimed at the model.' },
|
|
22
|
+
{ id: 'MTC-INJ-SECRECY-1', title: 'Secrecy directive', category: 'injection', severity: 'high', summary: 'Metadata instructs the model to conceal an action from the user.' },
|
|
23
|
+
{ id: 'MTC-INJ-SECRECY-2', title: 'Fabricated policy framing', category: 'injection', severity: 'medium', summary: 'Fake "compliance"/"policy" justification for an instruction.' },
|
|
24
|
+
{ id: 'MTC-INJ-SEQ-1', title: 'Forced-sequencing (line jumping)', category: 'injection', severity: 'medium', summary: '"before using any tool"/"always call this first" — line-jumping directive.' },
|
|
25
|
+
{ id: 'MTC-INJ-SEQ-2', title: 'Conditional behavior directive', category: 'injection', severity: 'low', summary: 'Conditional "when you…"/"every time…" behavior instruction.' },
|
|
26
|
+
{ id: 'MTC-INJ-TARGET-1', title: 'Sensitive file/credential reference', category: 'injection', severity: 'high', summary: 'Metadata references ~/.ssh, .env, credentials, API keys, etc.' },
|
|
27
|
+
{ id: 'MTC-INJ-TARGET-2', title: 'Context/history solicitation', category: 'injection', severity: 'high', summary: 'Metadata asks for system prompt, chat history, or environment details.' },
|
|
28
|
+
{ id: 'MTC-INJ-EXFIL-1', title: 'Exfil-shaped parameter description', category: 'injection', severity: 'high', summary: 'A parameter description tells the model to include/append context or file contents.' },
|
|
29
|
+
{ id: 'MTC-INJ-SHADOW-1', title: 'Cross-tool redirection of a recipient/credential', category: 'injection', severity: 'high', summary: 'Description reroutes an email/message/credential/token to another destination (exfiltration/hijack).' },
|
|
30
|
+
{ id: 'MTC-INJ-ENC-1', title: 'Encoded-payload decode/execute', category: 'injection', severity: 'high', summary: 'Instruction to decode base64/hex and execute the result.' },
|
|
31
|
+
{ id: 'MTC-INJ-ENC-2', title: 'Encoded blob + decode instruction', category: 'injection', severity: 'high', summary: 'A long base64 blob paired with a decode/execute instruction.' },
|
|
32
|
+
{ id: 'MTC-INJ-CMD-1', title: 'Shell command in metadata', category: 'injection', severity: 'high', summary: 'A shell command (rm -rf, curl|sh, etc.) embedded in tool metadata.' },
|
|
33
|
+
{ id: 'MTC-INJ-CAPS', title: 'Excessive ALL-CAPS', category: 'injection', severity: 'low', summary: 'A run of shouted words used to give injected instructions false authority.' },
|
|
34
|
+
{ id: 'MTC-INJ-PARAM', title: 'Suspicious hidden-channel parameter', category: 'injection', severity: 'medium', summary: 'A parameter named like a known exfil channel (side_note, context, feedback…).' },
|
|
35
|
+
{ id: 'MTC-INJ-POISON', title: 'Compound tool-poisoning pattern', category: 'injection', severity: 'critical', summary: 'Multiple poisoning signals co-occur in one field — the canonical poisoning shape.' },
|
|
36
|
+
{ id: 'MTC-INJ-SHADOW-2', title: 'Cross-server tool-name collision', category: 'injection', severity: 'high', summary: 'A tool name collides with (or is a homoglyph/near-miss of) a tool on another scanned server.' },
|
|
37
|
+
{ id: 'MTC-INJ-SHADOW-3', title: 'Tool-selection hijack (suppress other tools)', category: 'injection', severity: 'high', summary: '"Do not use / ignore the other tools"-style text that suppresses all sibling tools in model selection.' },
|
|
38
|
+
{ id: 'MTC-INJ-SHADOW-4', title: 'Assertive tool self-preference', category: 'injection', severity: 'low', summary: 'Comparative self-promotion ("only correct tool", "use this instead of the X tool") — common in legit docs; a confidence-axis nudge that escalates only with secrecy/override.' },
|
|
39
|
+
{ id: 'MTC-INJ-SECRET-1', title: 'Embedded credential value', category: 'exfiltration', severity: 'high', summary: 'A live-looking secret (AWS/GitHub/Slack/JWT/PEM…) is hardcoded in server metadata.' },
|
|
40
|
+
{ id: 'MTC-INJ-URL-1', title: 'Suspicious external URL in tool metadata', category: 'exfiltration', severity: 'medium', summary: 'A hardcoded webhook/paste/exfil endpoint or raw IP in a tool description — a data-exfiltration channel.' },
|
|
41
|
+
// Stage 3 — Capability
|
|
42
|
+
{ id: 'MTC-CAP-001', title: 'Command/code execution capability', category: 'permissions', severity: 'high', summary: 'Tool runs shell commands or evaluates code.' },
|
|
43
|
+
{ id: 'MTC-CAP-002', title: 'Filesystem mutation capability', category: 'permissions', severity: 'medium', summary: 'Tool can write, overwrite, or delete files.' },
|
|
44
|
+
{ id: 'MTC-CAP-003', title: 'Annotation-vs-behavior mismatch', category: 'permissions', severity: 'medium', summary: 'Tool claims read-only/non-destructive but its behavior mutates/egresses.' },
|
|
45
|
+
{ id: 'MTC-CAP-004', title: 'Open-world tool reads sensitive data', category: 'exfiltration', severity: 'medium', summary: 'openWorldHint=true plus a sensitive-data read is a lethal-trifecta indicator.' },
|
|
46
|
+
{ id: 'MTC-CAP-005', title: 'Mutating tool without destructiveHint', category: 'hygiene', severity: 'low', summary: 'A tool that mutates/egresses declares no destructiveHint, so some clients may not prompt.' },
|
|
47
|
+
{ id: 'MTC-CAP-006', title: 'Unconstrained command parameter', category: 'permissions', severity: 'medium', summary: 'A command-shaped param with no enum/pattern — the command-injection precondition.' },
|
|
48
|
+
{ id: 'MTC-CAP-007', title: 'Unconstrained URL/host parameter', category: 'network', severity: 'medium', summary: 'An outbound tool takes an unbounded URL/host — the SSRF / cloud-metadata precondition.' },
|
|
49
|
+
{ id: 'MTC-CAP-008', title: 'Unconstrained path parameter', category: 'permissions', severity: 'low', summary: 'A file tool takes an unconstrained path — the path-traversal precondition.' },
|
|
50
|
+
{ id: 'MTC-CAP-009', title: 'Declared sampling capability', category: 'permissions', severity: 'medium', summary: 'Server can drive the client LLM with its own prompts (reverse-trust / resource drain).' },
|
|
51
|
+
{ id: 'MTC-CAP-010', title: 'Declared elicitation capability', category: 'permissions', severity: 'medium', summary: 'Server can pop mid-session input requests (capability; blast-radius only).' },
|
|
52
|
+
{ id: 'MTC-CAP-011', title: 'Elicitation solicits secrets', category: 'exfiltration', severity: 'high', summary: 'Declared elicitation + a secret-seeking field — a consent-phishing threat.' },
|
|
53
|
+
// Stage 4 — Toxic flow
|
|
54
|
+
{ id: 'MTC-FLOW-001', title: 'Self-contained exfiltration primitive', category: 'exfiltration', severity: 'critical', summary: 'One tool ingests untrusted input, reads sensitive data, and can exfiltrate it.' },
|
|
55
|
+
{ id: 'MTC-FLOW-002', title: 'Completed trifecta across tools', category: 'exfiltration', severity: 'critical', summary: 'Untrusted-input, sensitive-source and external-sink roles co-exist across tools.' },
|
|
56
|
+
{ id: 'MTC-FLOW-003', title: 'Read-and-egress in one tool', category: 'exfiltration', severity: 'high', summary: 'A single tool both reads sensitive data and can send it out.' },
|
|
57
|
+
{ id: 'MTC-FLOW-004', title: 'Source + sink co-exist', category: 'exfiltration', severity: 'high', summary: 'A sensitive-data source and an external sink are exposed together.' },
|
|
58
|
+
{ id: 'MTC-FLOW-005', title: 'Untrusted input drives an action', category: 'exfiltration', severity: 'medium', summary: 'Untrusted input can reach an external-action sink (no sensitive source found).' },
|
|
59
|
+
// Stage 5 — Supply chain
|
|
60
|
+
{ id: 'MTC-SUP-001', title: 'Known typosquat', category: 'supply-chain', severity: 'high', summary: 'Name matches a known impersonation of a popular package.' },
|
|
61
|
+
{ id: 'MTC-SUP-002', title: 'Impersonated official scope', category: 'supply-chain', severity: 'high', summary: 'Scope crafted to look like @modelcontextprotocol.' },
|
|
62
|
+
{ id: 'MTC-SUP-003', title: 'Unscoped shadow', category: 'supply-chain', severity: 'high', summary: 'Unscoped name shadowing an official scoped package.' },
|
|
63
|
+
{ id: 'MTC-SUP-004', title: 'Homoglyph squat', category: 'supply-chain', severity: 'high', summary: 'Confusable skeleton identical to a protected package.' },
|
|
64
|
+
{ id: 'MTC-SUP-005', title: 'Edit-distance near-miss', category: 'supply-chain', severity: 'medium', summary: 'Damerau-Levenshtein 1–2 from a protected package, download-anomaly gated.' },
|
|
65
|
+
{ id: 'MTC-SUP-006', title: 'Combosquat', category: 'supply-chain', severity: 'medium', summary: 'Protected name plus a decorative suffix (-js, -server, …).' },
|
|
66
|
+
{ id: 'MTC-SUP-010', title: 'Install-time scripts', category: 'supply-chain', severity: 'high', summary: 'Package runs pre/post/install scripts — the dominant malware vector.' },
|
|
67
|
+
{ id: 'MTC-SUP-011', title: 'No source repository', category: 'supply-chain', severity: 'low', summary: 'Published artifact cannot be compared against reviewable source.' },
|
|
68
|
+
{ id: 'MTC-SUP-012', title: 'No license', category: 'hygiene', severity: 'low', summary: 'Package declares no license.' },
|
|
69
|
+
{ id: 'MTC-SUP-013', title: 'Package not version-pinned', category: 'supply-chain', severity: 'low', summary: 'Installed with @latest/floating spec — the rug-pull enabler; pinning is the recommended control.' },
|
|
70
|
+
{ id: 'MTC-SUP-014', title: 'Dependency squat / advisory match', category: 'supply-chain', severity: 'medium', summary: 'A declared dependency resembles a protected package or matches a known advisory by name.' },
|
|
71
|
+
// Stage 6 — Posture / CVE
|
|
72
|
+
{ id: 'MTC-NET-001', title: 'Known-vulnerable version', category: 'supply-chain', severity: 'high', summary: 'Installed version is in a known-CVE range.' },
|
|
73
|
+
{ id: 'MTC-NET-002', title: 'User-controlled stdio command', category: 'network', severity: 'critical', summary: 'stdio command from untrusted config without an allowlist — stdio RCE class.' },
|
|
74
|
+
{ id: 'MTC-NET-003', title: 'Plaintext HTTP transport', category: 'network', severity: 'medium', summary: 'Server reached over http:// — traffic and tokens exposed.' },
|
|
75
|
+
{ id: 'MTC-NET-004', title: 'Bound to 0.0.0.0', category: 'network', severity: 'medium', summary: 'Server exposed on all interfaces; verify authentication.' },
|
|
76
|
+
{ id: 'MTC-NET-005', title: 'Remote HTTP endpoint', category: 'network', severity: 'info', summary: 'Remote endpoint; server-side auth is out of scope for static analysis.' },
|
|
77
|
+
{ id: 'MTC-NET-006', title: 'Local HTTP/SSE — verify Origin (DNS rebinding)', category: 'network', severity: 'low', summary: 'A browser-reachable local MCP server is exposed to DNS rebinding unless it validates Host/Origin.' },
|
|
78
|
+
// Stage 7 — Integrity
|
|
79
|
+
{ id: 'MTC-TOFU-001', title: 'Surface drift since pin (rug pull)', category: 'supply-chain', severity: 'high', summary: 'Canonical fingerprint no longer matches the pinned value.' },
|
|
80
|
+
// Meta
|
|
81
|
+
{ id: 'MTC-META-001', title: 'Empty surface — nothing to analyze', category: 'hygiene', severity: 'info', summary: 'No tools/prompts/resources found; an empty surface is not a clean bill of health.' },
|
|
82
|
+
// Stage 4b — Implementation-level source analysis (what the code DOES, not what the tool claims)
|
|
83
|
+
{ id: 'MTC-SRC-001', title: 'Dynamic code execution in server code', category: 'permissions', severity: 'high', summary: 'eval / new Function / vm / exec(compile) — arbitrary-code-execution primitive in the implementation.' },
|
|
84
|
+
{ id: 'MTC-SRC-002', title: 'Shell/command execution in server code', category: 'permissions', severity: 'high', summary: 'child_process / os.system / subprocess(shell=True) — command-execution sink; RCE with unsanitized input.' },
|
|
85
|
+
{ id: 'MTC-SRC-003', title: 'Hardcoded egress to an external endpoint', category: 'exfiltration', severity: 'medium', summary: 'A fixed outbound http(s) call to a non-local host in the code — an exfiltration/telemetry channel.' },
|
|
86
|
+
{ id: 'MTC-SRC-004', title: 'Obfuscated / encoded payload in server code', category: 'injection', severity: 'high', summary: 'Decode-and-execute of an encoded blob, or \\x-escaped / fromCharCode runs hiding logic from review.' },
|
|
87
|
+
{ id: 'MTC-SRC-005', title: 'Dynamic module load from a non-literal', category: 'permissions', severity: 'medium', summary: 'require()/import()/__import__ from a variable — loads runtime-chosen (attacker-influenced) code.' },
|
|
88
|
+
{ id: 'MTC-SRC-006', title: 'Credential-path read or environment dump in code', category: 'exfiltration', severity: 'high', summary: 'Reads ~/.ssh / .aws/credentials / .netrc, or serializes the whole environment — a sensitive-data source.' },
|
|
89
|
+
{ id: 'MTC-SRC-007', title: 'Unsafe deserialization', category: 'permissions', severity: 'high', summary: 'pickle.loads / yaml.load / node-serialize / marshal.loads — a classic deserialization RCE gadget.' },
|
|
90
|
+
{ id: 'MTC-SRC-008', title: 'Hardcoded credential value in server code', category: 'exfiltration', severity: 'high', summary: 'A live-looking secret (AWS/GitHub/Slack/JWT/PEM…) embedded in the source, shipped to every install.' },
|
|
91
|
+
];
|
|
92
|
+
export function findRule(id) {
|
|
93
|
+
return RULE_CATALOG.find((r) => r.id.toLowerCase() === id.toLowerCase());
|
|
94
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Implementation-level sink lexicon. Tool metadata says what a server *claims*
|
|
4
|
+
* to do; these patterns read what its code *actually does* — the dangerous
|
|
5
|
+
* sinks (arbitrary execution, egress, secret handling) that a poisoned or
|
|
6
|
+
* negligent server hides in its implementation. Deterministic, no LLM.
|
|
7
|
+
*
|
|
8
|
+
* Kept high-precision: each pattern anchors on a real dangerous API, and the
|
|
9
|
+
* detector escalates when attacker-influenced data can reach it.
|
|
10
|
+
*/
|
|
11
|
+
import type { Confidence, Severity } from '../types.js';
|
|
12
|
+
export interface SourcePattern {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
/** Language hint for the finding text. */
|
|
16
|
+
lang: 'js' | 'py' | 'any';
|
|
17
|
+
pattern: RegExp;
|
|
18
|
+
severity: Severity;
|
|
19
|
+
confidence: Confidence;
|
|
20
|
+
category: 'permissions' | 'exfiltration' | 'injection';
|
|
21
|
+
why: string;
|
|
22
|
+
}
|
|
23
|
+
/** File extensions we treat as scannable server source. */
|
|
24
|
+
export declare const SOURCE_EXTENSIONS: Set<string>;
|
|
25
|
+
export declare const SOURCE_PATTERNS: SourcePattern[];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Implementation-level sink lexicon. Tool metadata says what a server *claims*
|
|
4
|
+
* to do; these patterns read what its code *actually does* — the dangerous
|
|
5
|
+
* sinks (arbitrary execution, egress, secret handling) that a poisoned or
|
|
6
|
+
* negligent server hides in its implementation. Deterministic, no LLM.
|
|
7
|
+
*
|
|
8
|
+
* Kept high-precision: each pattern anchors on a real dangerous API, and the
|
|
9
|
+
* detector escalates when attacker-influenced data can reach it.
|
|
10
|
+
*/
|
|
11
|
+
/** File extensions we treat as scannable server source. */
|
|
12
|
+
export const SOURCE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx', '.py']);
|
|
13
|
+
export const SOURCE_PATTERNS = [
|
|
14
|
+
// ── Arbitrary code execution ────────────────────────────────────────────────
|
|
15
|
+
{
|
|
16
|
+
id: 'MTC-SRC-001',
|
|
17
|
+
title: 'Dynamic code execution in server code',
|
|
18
|
+
lang: 'any',
|
|
19
|
+
pattern: /\beval\s*\(|\bnew\s+Function\s*\(|\bFunction\s*\(\s*['"`]|vm\.(?:runIn\w+|compileFunction)\s*\(|\bexec\s*\(\s*compile\s*\(/,
|
|
20
|
+
severity: 'high',
|
|
21
|
+
confidence: 'strong',
|
|
22
|
+
category: 'permissions',
|
|
23
|
+
why: 'Evaluating strings as code is the most direct RCE primitive; if any tool input reaches it, the server executes attacker-chosen code.',
|
|
24
|
+
},
|
|
25
|
+
// ── Shell / command execution ───────────────────────────────────────────────
|
|
26
|
+
{
|
|
27
|
+
id: 'MTC-SRC-002',
|
|
28
|
+
title: 'Shell/command execution in server code',
|
|
29
|
+
lang: 'any',
|
|
30
|
+
pattern: /child_process|\bexecSync\s*\(|\bexec\s*\(|\bspawn(?:Sync)?\s*\(|\bos\.system\s*\(|\bos\.popen\s*\(|subprocess\.(?:run|call|Popen|check_output)\s*\(|\bshell\s*[:=]\s*True/,
|
|
31
|
+
severity: 'high',
|
|
32
|
+
confidence: 'strong',
|
|
33
|
+
category: 'permissions',
|
|
34
|
+
why: 'Spawning a shell/process is command-execution capability; with unsanitized tool input it is command injection / RCE.',
|
|
35
|
+
},
|
|
36
|
+
// ── Outbound egress to a hardcoded external host ────────────────────────────
|
|
37
|
+
{
|
|
38
|
+
id: 'MTC-SRC-003',
|
|
39
|
+
title: 'Hardcoded egress to an external endpoint',
|
|
40
|
+
lang: 'any',
|
|
41
|
+
// fetch/axios/requests/urllib to a literal http(s) URL that is NOT localhost.
|
|
42
|
+
pattern: /(?:fetch|axios(?:\.\w+)?|requests\.\w+|urllib\.request\.urlopen|https?\.request|got|node-fetch)\s*\(\s*['"`]https?:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)[^'"`]+['"`]/,
|
|
43
|
+
severity: 'medium',
|
|
44
|
+
confidence: 'strong',
|
|
45
|
+
category: 'exfiltration',
|
|
46
|
+
why: 'A hardcoded outbound call to a fixed external host inside server code is a classic exfiltration/telemetry channel — especially paired with reads of local data.',
|
|
47
|
+
},
|
|
48
|
+
// ── Obfuscation / hidden payloads ───────────────────────────────────────────
|
|
49
|
+
{
|
|
50
|
+
id: 'MTC-SRC-004',
|
|
51
|
+
title: 'Obfuscated / encoded payload in server code',
|
|
52
|
+
lang: 'any',
|
|
53
|
+
pattern: /(?:eval|exec|new Function|Function)\s*\(\s*(?:atob|Buffer\.from|base64\.b64decode)\b|atob\s*\([^)]*\)\s*\)?\s*(?:\)|;)?\s*.{0,20}\beval|Buffer\.from\s*\([^)]*,\s*['"`]base64['"`]\s*\)[^;]{0,40}(?:eval|Function|exec)|base64\.b64decode\s*\([^)]*\)[^;]{0,40}(?:exec|eval)|(?:\\x[0-9a-fA-F]{2}){8,}|String\.fromCharCode\s*\((?:\s*\d+\s*,){8,}/,
|
|
54
|
+
severity: 'high',
|
|
55
|
+
confidence: 'strong',
|
|
56
|
+
category: 'injection',
|
|
57
|
+
why: 'Decoding an encoded blob and executing it is how malicious logic is hidden from human review of the source.',
|
|
58
|
+
},
|
|
59
|
+
// ── Dynamic module loading ──────────────────────────────────────────────────
|
|
60
|
+
{
|
|
61
|
+
id: 'MTC-SRC-005',
|
|
62
|
+
title: 'Dynamic module load from a non-literal',
|
|
63
|
+
lang: 'any',
|
|
64
|
+
pattern: /\brequire\s*\(\s*(?!['"`])[A-Za-z_$][\w$.]*\s*\)|\bimport\s*\(\s*(?!['"`])[A-Za-z_$][\w$.]*\s*\)|__import__\s*\(\s*(?!['"`])/,
|
|
65
|
+
severity: 'medium',
|
|
66
|
+
confidence: 'heuristic',
|
|
67
|
+
category: 'permissions',
|
|
68
|
+
why: 'Loading a module chosen at runtime (from a variable) can pull in and run attacker-influenced code paths.',
|
|
69
|
+
},
|
|
70
|
+
// ── Sensitive filesystem / environment reads ────────────────────────────────
|
|
71
|
+
{
|
|
72
|
+
id: 'MTC-SRC-006',
|
|
73
|
+
title: 'Reads a sensitive credential path or dumps the environment',
|
|
74
|
+
lang: 'any',
|
|
75
|
+
pattern: /~\/\.ssh|id_rsa\b|\.aws\/credentials|\.config\/gcloud|\.netrc\b|\.docker\/config\.json|JSON\.stringify\s*\(\s*process\.env\s*\)|\bdict\s*\(\s*os\.environ\s*\)|json\.dumps\s*\(\s*(?:dict\s*\(\s*)?os\.environ/,
|
|
76
|
+
severity: 'high',
|
|
77
|
+
confidence: 'strong',
|
|
78
|
+
category: 'exfiltration',
|
|
79
|
+
why: 'Reading private keys / cloud credentials, or serializing the whole environment, is a sensitive-data source that becomes exfiltration when combined with any egress.',
|
|
80
|
+
},
|
|
81
|
+
// ── Unsafe deserialization ──────────────────────────────────────────────────
|
|
82
|
+
{
|
|
83
|
+
id: 'MTC-SRC-007',
|
|
84
|
+
title: 'Unsafe deserialization',
|
|
85
|
+
lang: 'any',
|
|
86
|
+
pattern: /\bpickle\.loads?\s*\(|yaml\.load\s*\((?![^)]*Loader\s*=\s*yaml\.SafeLoader)|node-serialize|\bunserialize\s*\(|marshal\.loads?\s*\(/,
|
|
87
|
+
severity: 'high',
|
|
88
|
+
confidence: 'strong',
|
|
89
|
+
category: 'permissions',
|
|
90
|
+
why: 'Deserializing untrusted data with these APIs can execute arbitrary code (a well-known RCE gadget class).',
|
|
91
|
+
},
|
|
92
|
+
];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Unicode threat data (pinned to Unicode 17.0.0).
|
|
4
|
+
*
|
|
5
|
+
* These are the codepoint families used to *smuggle* instructions into text an
|
|
6
|
+
* LLM reads but a human reviewer does not. MCP Trust Checker's rule is to **decode, not
|
|
7
|
+
* just strip** — where a family carries a payload (Tags block, variation
|
|
8
|
+
* selectors) we recover and surface it as evidence.
|
|
9
|
+
*
|
|
10
|
+
* References:
|
|
11
|
+
* - Unicode UAX #9 (BiDi), UTS #39 (Security Mechanisms), UAX #44 (Character DB)
|
|
12
|
+
* - BiDi override source attacks (CVE-2021-42574)
|
|
13
|
+
* - "Invisible prompt injection" via Tags block & variation-selector channels
|
|
14
|
+
*/
|
|
15
|
+
export type InvisibleFamily = 'tags' | 'variation-selector' | 'bidi-override' | 'bidi-isolate' | 'bidi-mark' | 'zero-width' | 'invisible-math' | 'default-ignorable' | 'unusual-whitespace' | 'control';
|
|
16
|
+
export interface CodepointRange {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
family: InvisibleFamily;
|
|
20
|
+
}
|
|
21
|
+
/** Ordered, non-overlapping ranges scanned by the Unicode detector. */
|
|
22
|
+
export declare const INVISIBLE_RANGES: CodepointRange[];
|
|
23
|
+
/** C0/C1 control ranges, excluding the benign tab/newline/CR. */
|
|
24
|
+
export declare const CONTROL_RANGES: CodepointRange[];
|
|
25
|
+
/** Whitespace codepoints that are legitimately ubiquitous and never flagged. */
|
|
26
|
+
export declare const BENIGN_CONTROL: Set<number>;
|
|
27
|
+
/** Classify a single codepoint into an invisible family, or null if ordinary. */
|
|
28
|
+
export declare function classifyCodepoint(cp: number): InvisibleFamily | null;
|
|
29
|
+
/** Decode a Tags-block codepoint to its mirrored ASCII char, or null. */
|
|
30
|
+
export declare function decodeTagCodepoint(cp: number): string | null;
|
|
31
|
+
/** Decode a variation selector to the byte value it encodes in the side channel. */
|
|
32
|
+
export declare function decodeVariationSelectorByte(cp: number): number | null;
|
|
33
|
+
/** Human labels for families, used in findings. */
|
|
34
|
+
export declare const FAMILY_LABELS: Record<InvisibleFamily, string>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Unicode threat data (pinned to Unicode 17.0.0).
|
|
4
|
+
*
|
|
5
|
+
* These are the codepoint families used to *smuggle* instructions into text an
|
|
6
|
+
* LLM reads but a human reviewer does not. MCP Trust Checker's rule is to **decode, not
|
|
7
|
+
* just strip** — where a family carries a payload (Tags block, variation
|
|
8
|
+
* selectors) we recover and surface it as evidence.
|
|
9
|
+
*
|
|
10
|
+
* References:
|
|
11
|
+
* - Unicode UAX #9 (BiDi), UTS #39 (Security Mechanisms), UAX #44 (Character DB)
|
|
12
|
+
* - BiDi override source attacks (CVE-2021-42574)
|
|
13
|
+
* - "Invisible prompt injection" via Tags block & variation-selector channels
|
|
14
|
+
*/
|
|
15
|
+
/** Ordered, non-overlapping ranges scanned by the Unicode detector. */
|
|
16
|
+
export const INVISIBLE_RANGES = [
|
|
17
|
+
// Tags block (ASCII mirror) — the highest-signal smuggling channel.
|
|
18
|
+
{ start: 0xe0000, end: 0xe007f, family: 'tags' },
|
|
19
|
+
// Variation selectors (VS1–VS16 and the supplementary VS17–VS256 side channel).
|
|
20
|
+
{ start: 0xfe00, end: 0xfe0f, family: 'variation-selector' },
|
|
21
|
+
{ start: 0xe0100, end: 0xe01ef, family: 'variation-selector' },
|
|
22
|
+
// BiDi overrides & embeddings.
|
|
23
|
+
{ start: 0x202a, end: 0x202e, family: 'bidi-override' },
|
|
24
|
+
// BiDi isolates.
|
|
25
|
+
{ start: 0x2066, end: 0x2069, family: 'bidi-isolate' },
|
|
26
|
+
// BiDi marks.
|
|
27
|
+
{ start: 0x200e, end: 0x200f, family: 'bidi-mark' },
|
|
28
|
+
{ start: 0x061c, end: 0x061c, family: 'bidi-mark' },
|
|
29
|
+
// Zero-width family.
|
|
30
|
+
{ start: 0x200b, end: 0x200b, family: 'zero-width' }, // ZWSP
|
|
31
|
+
{ start: 0x200c, end: 0x200d, family: 'zero-width' }, // ZWNJ, ZWJ
|
|
32
|
+
{ start: 0xfeff, end: 0xfeff, family: 'zero-width' }, // BOM / ZWNBSP
|
|
33
|
+
// Invisible math operators (used as 0/1 bits).
|
|
34
|
+
{ start: 0x2060, end: 0x2064, family: 'invisible-math' },
|
|
35
|
+
// Other default-ignorables.
|
|
36
|
+
{ start: 0x00ad, end: 0x00ad, family: 'default-ignorable' }, // soft hyphen
|
|
37
|
+
{ start: 0x180e, end: 0x180e, family: 'default-ignorable' }, // Mongolian vowel separator
|
|
38
|
+
{ start: 0x17b4, end: 0x17b5, family: 'default-ignorable' }, // Khmer inherent vowels
|
|
39
|
+
{ start: 0xfff9, end: 0xfffb, family: 'default-ignorable' }, // interlinear annotation
|
|
40
|
+
{ start: 0x2028, end: 0x2029, family: 'default-ignorable' }, // line / paragraph separator
|
|
41
|
+
// Unusual whitespace.
|
|
42
|
+
{ start: 0x00a0, end: 0x00a0, family: 'unusual-whitespace' }, // NBSP
|
|
43
|
+
{ start: 0x1680, end: 0x1680, family: 'unusual-whitespace' }, // Ogham space
|
|
44
|
+
{ start: 0x2000, end: 0x200a, family: 'unusual-whitespace' }, // en/em/thin spaces
|
|
45
|
+
{ start: 0x202f, end: 0x202f, family: 'unusual-whitespace' }, // narrow NBSP
|
|
46
|
+
{ start: 0x205f, end: 0x205f, family: 'unusual-whitespace' }, // medium math space
|
|
47
|
+
{ start: 0x3000, end: 0x3000, family: 'unusual-whitespace' }, // ideographic space
|
|
48
|
+
];
|
|
49
|
+
/** C0/C1 control ranges, excluding the benign tab/newline/CR. */
|
|
50
|
+
export const CONTROL_RANGES = [
|
|
51
|
+
{ start: 0x0000, end: 0x0008, family: 'control' },
|
|
52
|
+
{ start: 0x000b, end: 0x000c, family: 'control' },
|
|
53
|
+
{ start: 0x000e, end: 0x001f, family: 'control' },
|
|
54
|
+
{ start: 0x007f, end: 0x007f, family: 'control' },
|
|
55
|
+
{ start: 0x0080, end: 0x009f, family: 'control' },
|
|
56
|
+
];
|
|
57
|
+
/** Whitespace codepoints that are legitimately ubiquitous and never flagged. */
|
|
58
|
+
export const BENIGN_CONTROL = new Set([0x09, 0x0a, 0x0d]);
|
|
59
|
+
const ALL_RANGES = [...INVISIBLE_RANGES, ...CONTROL_RANGES];
|
|
60
|
+
/** Classify a single codepoint into an invisible family, or null if ordinary. */
|
|
61
|
+
export function classifyCodepoint(cp) {
|
|
62
|
+
if (BENIGN_CONTROL.has(cp))
|
|
63
|
+
return null;
|
|
64
|
+
for (const r of ALL_RANGES) {
|
|
65
|
+
if (cp >= r.start && cp <= r.end)
|
|
66
|
+
return r.family;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/** Decode a Tags-block codepoint to its mirrored ASCII char, or null. */
|
|
71
|
+
export function decodeTagCodepoint(cp) {
|
|
72
|
+
if (cp < 0xe0000 || cp > 0xe007f)
|
|
73
|
+
return null;
|
|
74
|
+
const ascii = cp - 0xe0000;
|
|
75
|
+
if (ascii === 0x7f)
|
|
76
|
+
return null; // CANCEL tag — a terminator, not printable
|
|
77
|
+
return String.fromCharCode(ascii);
|
|
78
|
+
}
|
|
79
|
+
/** Decode a variation selector to the byte value it encodes in the side channel. */
|
|
80
|
+
export function decodeVariationSelectorByte(cp) {
|
|
81
|
+
if (cp >= 0xfe00 && cp <= 0xfe0f)
|
|
82
|
+
return cp - 0xfe00; // VS1–VS16 → 0–15
|
|
83
|
+
if (cp >= 0xe0100 && cp <= 0xe01ef)
|
|
84
|
+
return cp - 0xe0100 + 16; // VS17–VS256 → 16–255
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/** Human labels for families, used in findings. */
|
|
88
|
+
export const FAMILY_LABELS = {
|
|
89
|
+
tags: 'Unicode Tags block (ASCII smuggling channel)',
|
|
90
|
+
'variation-selector': 'variation-selector byte channel',
|
|
91
|
+
'bidi-override': 'bidirectional override',
|
|
92
|
+
'bidi-isolate': 'bidirectional isolate',
|
|
93
|
+
'bidi-mark': 'bidirectional mark',
|
|
94
|
+
'zero-width': 'zero-width / joiner character',
|
|
95
|
+
'invisible-math': 'invisible math operator (steganographic bit)',
|
|
96
|
+
'default-ignorable': 'default-ignorable character',
|
|
97
|
+
'unusual-whitespace': 'unusual whitespace',
|
|
98
|
+
control: 'C0/C1 control character',
|
|
99
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Stage 3 — Capability findings. The capability *tags* are computed by the
|
|
4
|
+
* engine and shared via context; here we turn them into findings:
|
|
5
|
+
* - dangerous capabilities (command/code execution, filesystem mutation);
|
|
6
|
+
* - annotation-vs-behavior mismatch, open-world + sensitive read, and missing
|
|
7
|
+
* destructiveHint (the annotation-truthfulness story);
|
|
8
|
+
* - schema-level injection preconditions (unconstrained command / URL / path
|
|
9
|
+
* parameters — the advertised half of command-injection / SSRF / traversal);
|
|
10
|
+
* - declared server capabilities that are high-signal on their own (sampling,
|
|
11
|
+
* elicitation) — static proxies for classes rivals only catch at runtime.
|
|
12
|
+
*/
|
|
13
|
+
import type { Detector } from '../types.js';
|
|
14
|
+
export declare const capabilityDetector: Detector;
|