chewcw/adx-mcp-server
criticalNo description
MCP server (purpose undetermined)
119 private registerConfigResource() {
120 this.server.resource(
121 "config",
122 "config://azure-data-explorer-creds",
123 async (_: { href: string }) => {
124 return {
125 contents: [
126 {
127 uri: "config://azure-data-explorer-creds/cluster-name",
128 name: "Cluster Name",
129 text: `${process.env.ADX_CLUSTER_NAME}`,
130 },
131 {
132 uri: "config://azure-data-explorer-creds/client-id",
133 name: "Client ID",
134 text: `${process.env.ADX_CLIENT_ID}`,
135 },
136 {
137 uri: "config://azure-data-explorer-creds/client-secret",
138 name: "Client Secret",
139 text: `${process.env.ADX_CLIENT_SECRET ? "******" : "Not set"}`,
140 },
141 {
142 uri: "config://azure-data-explorer-creds/tenant-id",
143 name: "Tenant ID",
144 text: `${process.env.ADX_TENANT_ID}`,
145 },
146 ],
147 }
148 }
149 )
150 }// Exploitable if MCP is exposed to untrusted prompts or if a compromised LLM can read resources.
The config resource exposes environment variables ADX_CLUSTER_NAME, ADX_CLIENT_ID, and ADX_TENANT_ID in plaintext. While the client secret is masked, the other credentials are leaked. This resource is accessible to any client that can read resources from the MCP server.
ImpactAn attacker with access to the MCP server can retrieve the cluster name, client ID, and tenant ID, which are sensitive credentials for Azure Data Explorer. This could facilitate further attacks on the Azure resource.
FixRemove the config resource or mask all sensitive fields. Consider using a secure credential store and never expose credentials via MCP resources.
55 this.server.setRequestHandler(ReadResourceRequestSchema, async (request: z.infer<typeof ReadResourceRequestSchema>) => {
56 const uri = request.params.uri
57
58 if (uri.startsWith("config://azure-data-explorer-creds")) {
59 return {
60 contents: [
61 {
62 uri: "config://azure-data-explorer-creds/cluster-name",
63 name: "Cluster Name",
64 text: `${process.env.ADX_CLUSTER_NAME}`,
65 },
66 {
67 uri: "config://azure-data-explorer-creds/client-id",
68 name: "Client ID",
69 text: `${process.env.ADX_CLIENT_ID}`,
70 },
71 {
72 uri: "config://azure-data-explorer-creds/client-secret",
73 name: "Client Secret",
74 text: `${process.env.ADX_CLIENT_SECRET ? "******" : "Not set"}`,
75 },
76 {
77 uri: "config://azure-data-explorer-creds/tenant-id",
78 name: "Tenant ID",
79 text: `${process.env.ADX_TENANT_ID}`,
80 },
81 ],
82 }
83 }
84 })// Exploitable if MCP is exposed to untrusted prompts or if a compromised LLM can read resources.
Same as above, but in the alternative server implementation. The config resource exposes ADX_CLUSTER_NAME, ADX_CLIENT_ID, and ADX_TENANT_ID in plaintext.
ImpactAn attacker can retrieve sensitive Azure credentials, enabling further attacks on the Azure Data Explorer instance.
FixRemove the config resource or mask all sensitive fields. Use a secure credential store.
152 private registerQueryTool() {
153 this.server.tool(
154 "query",
155 { query: z.string(), db: z.string() },
156 async (data: z.infer<typeof this.QueryToolSchema>) => {
157 if (!this.client) {
158 throw new Error("Client is not initialized")
159 }
160 const query = data.query
161 const db = data.db
162 const response = await this.client.execute(String(db), String(query))
163 const result = response.primaryResults[0].toString()
164 return {
165 content: [{
166 type: "text",
167 text: result,
168 }],
169 }
170 }
171 )
172 }// Exploitable if MCP is exposed to untrusted prompts or if a compromised LLM can call tools.
The query tool accepts arbitrary Kusto Query Language (KQL) queries from the user without any validation or sanitization. The 'db' parameter is also user-controlled and used directly in the execute call. This allows an attacker to run any KQL query, including administrative commands like '.show operations' or '.drop tables'.
ImpactAn attacker could execute arbitrary KQL queries, potentially reading, modifying, or deleting data in the Azure Data Explorer database. This could lead to data exfiltration, data loss, or privilege escalation.
FixImplement strict input validation: restrict allowed queries to a whitelist of safe operations, or use parameterized queries if supported. Validate the 'db' parameter against a list of allowed databases.
40 private registerSchemaResourceTemplate() {
41 this.server.resource(
42 "Schema of the db",
43 new ResourceTemplate("schema://adx/{db}", { list: undefined }),
44 async (uri: URL, variables: Variables, _extra: RequestHandlerExtra) => {
45 if (!this.client) {
46 throw new Error("Client is not initialized")
47 }
48
49 const query = `.show tables`
50 const response = await this.client.execute(String(variables.db), query)
51 const result = response.primaryResults[0].toString()
52 ...
53 },
54 )
55
56 this.server.resource(
57 "Schema of the table",
58 new ResourceTemplate("schema://adx/{db}/{table}", { list: undefined }),
59 async (uri: URL, variables: Variables, _extra: RequestHandlerExtra) => {
60 ...
61 const query = `${variables.table} | getschema`
62 const response = await this.client.execute(String(variables.db), query)
63 ...
64 },
65 )
66
67 this.server.resource(
68 "Functions of the db",
69 new ResourceTemplate("functions://adx/{db}/functions", { list: undefined }),
70 async (uri: URL, variables: Variables, extra: RequestHandlerExtra) => {
71 ...
72 const query = `.show functions`
73 const response = await this.client.execute(String(variables.db), query)
74 ...
75 },
76 )
77 }// Exploitable if MCP is exposed to untrusted prompts or if a compromised LLM can read resources.
The resource templates accept user-controlled 'db' and 'table' parameters from the URI. These are used directly in KQL queries without validation. While the queries are hardcoded (except for the table name in the second template), the 'db' parameter is used to select the database context, and an attacker could potentially access databases they should not have access to. The table name is directly interpolated into the query, which could allow injection if the table name contains special characters.
ImpactAn attacker could potentially access or enumerate databases and tables beyond the intended scope. The table name injection could allow arbitrary KQL execution if the table name is crafted maliciously.
FixValidate the 'db' parameter against a whitelist of allowed databases. Sanitize or parameterize the 'table' parameter to prevent injection.
110 this.server.setRequestHandler(ReadResourceRequestSchema, async (request: z.infer<typeof ReadResourceRequestSchema>) => {
111 const uri = request.params.uri
112 const showTablesRegex = /schema:\/\/adx\/\w+$/
113 if (showTablesRegex.test(uri)) {
114 const db = uri.split("/")[3]
115 ...
116 const query = `.show tables`
117 const response = await this.client.execute(db, query)
118 ...
119 }
120 const showTableSchemaRegex = /schema:\/\/adx\/\w+\/\w+$/
121 if (showTableSchemaRegex.test(uri)) {
122 const db = uri.split("/")[3]
123 const table = uri.split("/")[4]
124 ...
125 const query = `${table} | getschema`
126 const response = await this.client.execute(db, query)
127 ...
128 }
129 const showFunctionsRegex = /schema:\/\/adx\/\w+\/functions$/
130 if (showFunctionsRegex.test(uri)) {
131 const db = uri.split("/")[3]
132 ...
133 const query = `.show functions`
134 const response = await this.client.execute(db, query)
135 ...
136 }
137 })// Exploitable if MCP is exposed to untrusted prompts or if a compromised LLM can read resources.
Same as above, but in the alternative implementation. The regex patterns only allow alphanumeric characters (\w+), which reduces injection risk but still does not validate the database name against a whitelist. The table name is directly interpolated into the query.
ImpactAn attacker could potentially access databases beyond the intended scope. The table name injection is limited by the regex but still possible if the regex is bypassed (e.g., using Unicode).
FixValidate the 'db' parameter against a whitelist of allowed databases. Use parameterized queries for the table name.