Skip to content

Commit 6774c73

Browse files
cklinCopilot
andcommitted
Probe authenticated MCP discovery
Test the Actions token explicitly, then inspect an initialized CLI session through discovery-only RPCs. Never send a prompt or message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d63bcb34-3969-4d14-b422-33728176ec03
1 parent 1a2eb43 commit 6774c73

1 file changed

Lines changed: 146 additions & 1 deletion

File tree

‎.github/dependabot-web-search-diagnostic.cjs‎

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const fs = require("node:fs");
22
const path = require("node:path");
3-
const { spawnSync } = require("node:child_process");
3+
const { spawn, spawnSync } = require("node:child_process");
44

55
const secrets = new Set([process.env.GITHUB_TOKEN]);
66
const report = { cliVersion: "1.0.104", inferenceRequests: 0, attempts: [] };
@@ -104,6 +104,8 @@ async function discoverTools(config) {
104104
}
105105
const headers = { ...config.headers };
106106
keepHeaderSecrets(headers);
107+
// Listing omits runtime-injected credentials; test this token explicitly.
108+
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
107109
const initialized = await rpc(
108110
url,
109111
headers,
@@ -116,6 +118,7 @@ async function discoverTools(config) {
116118
1,
117119
);
118120
const result = {
121+
authentication: "explicit Actions GITHUB_TOKEN",
119122
endpoint: `${url.origin}${url.pathname}`,
120123
headerNames: Object.keys(headers).filter(
121124
(name) => name.toLowerCase() !== "mcp-session-id",
@@ -150,6 +153,143 @@ async function discoverTools(config) {
150153
return result;
151154
}
152155

156+
async function inspectSession(config) {
157+
const child = spawn("copilot", [...flags, "--server", "--stdio"], {
158+
stdio: ["pipe", "pipe", "pipe"],
159+
});
160+
let buffer = Buffer.alloc(0);
161+
let nextId = 0;
162+
const pending = new Map();
163+
const result = { stderr: "" };
164+
report.session = result;
165+
child.stderr.setEncoding("utf8").on("data", (chunk) => {
166+
result.stderr += chunk;
167+
});
168+
function send(message) {
169+
const body = JSON.stringify(message);
170+
child.stdin.write(
171+
`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`,
172+
);
173+
}
174+
child.stdout.on("data", (chunk) => {
175+
try {
176+
buffer = Buffer.concat([buffer, chunk]);
177+
while (true) {
178+
const end = buffer.indexOf("\r\n\r\n");
179+
if (end < 0) return;
180+
const length = Number(
181+
/Content-Length:\s*(\d+)/i.exec(
182+
buffer.subarray(0, end).toString(),
183+
)?.[1],
184+
);
185+
if (!Number.isFinite(length))
186+
throw new Error("Missing JSON-RPC frame length");
187+
if (buffer.length < end + 4 + length) return;
188+
const message = JSON.parse(
189+
buffer.subarray(end + 4, end + 4 + length).toString(),
190+
);
191+
buffer = buffer.subarray(end + 4 + length);
192+
if (message.method && message.id !== undefined) {
193+
send({
194+
jsonrpc: "2.0",
195+
id: message.id,
196+
error: { code: -32601, message: "No client callbacks installed" },
197+
});
198+
} else if (pending.has(message.id)) {
199+
const request = pending.get(message.id);
200+
pending.delete(message.id);
201+
clearTimeout(request.timer);
202+
if (message.error)
203+
request.reject(new Error(JSON.stringify(message.error)));
204+
else request.resolve(message.result);
205+
}
206+
}
207+
} catch (error) {
208+
rejectPending(error);
209+
}
210+
});
211+
function rejectPending(error) {
212+
for (const request of pending.values()) {
213+
clearTimeout(request.timer);
214+
request.reject(error);
215+
}
216+
pending.clear();
217+
}
218+
child.on("error", rejectPending);
219+
child.stdin.on("error", rejectPending);
220+
child.on("exit", (code) =>
221+
rejectPending(new Error(`CLI server exited with ${code}`)),
222+
);
223+
function request(method, params) {
224+
if (
225+
![
226+
"ping",
227+
"session.create",
228+
"session.mcp.list",
229+
"session.mcp.listTools",
230+
"session.destroy",
231+
].includes(method)
232+
) {
233+
throw new Error("Refusing non-discovery RPC");
234+
}
235+
return new Promise((resolve, reject) => {
236+
const id = ++nextId;
237+
const timer = setTimeout(() => {
238+
pending.delete(id);
239+
reject(new Error(`Timed out waiting for ${method}`));
240+
}, 90000);
241+
pending.set(id, { resolve, reject, timer });
242+
send({ jsonrpc: "2.0", id, method, params });
243+
});
244+
}
245+
try {
246+
result.ping = await request("ping", { message: "no-inference-diagnostic" });
247+
const session = await request("session.create", {
248+
model: "gpt-6.1-terra",
249+
workingDirectory: process.cwd(),
250+
availableTools: [
251+
"apply_patch",
252+
"bash",
253+
"glob",
254+
"rg",
255+
"view",
256+
"web_fetch",
257+
"web_search",
258+
],
259+
mcpServers: config.mcpServers,
260+
disabledMcpServers: ["githubiq"],
261+
});
262+
result.sessionId = session.sessionId;
263+
const listed = await request("session.mcp.list", {
264+
sessionId: session.sessionId,
265+
});
266+
result.servers = listed.servers.map((server) => ({
267+
name: server.name,
268+
status: server.status,
269+
error: server.error,
270+
toolCount: server.toolCount,
271+
}));
272+
const tools = await request("session.mcp.listTools", {
273+
sessionId: session.sessionId,
274+
serverName: "github-mcp-server",
275+
});
276+
result.toolNames = tools.tools.map((tool) => tool.name).sort();
277+
result.webSearchPresent = result.toolNames.includes("web_search");
278+
await request("session.destroy", { sessionId: session.sessionId });
279+
result.complete = true;
280+
} catch (error) {
281+
result.complete = false;
282+
result.error = error.stack;
283+
throw error;
284+
} finally {
285+
const exited = new Promise((resolve) => child.once("exit", resolve));
286+
child.kill("SIGTERM");
287+
const timer = setTimeout(() => child.kill("SIGKILL"), 3000);
288+
if (child.exitCode === null) await exited;
289+
clearTimeout(timer);
290+
}
291+
}
292+
153293
function logFiles(directory) {
154294
if (!fs.existsSync(directory)) return [];
155295
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
@@ -163,6 +303,7 @@ function logFiles(directory) {
163303
}
164304

165305
async function main() {
306+
let lastConfig;
166307
for (let attempt = 1; attempt <= 2; attempt++) {
167308
const child = spawnSync("copilot", [...flags, "mcp", "list", "--json"], {
168309
cwd: process.cwd(),
@@ -175,6 +316,7 @@ async function main() {
175316
if (child.error) throw child.error;
176317
if (child.status !== 0) throw new Error("copilot mcp list failed");
177318
const config = JSON.parse(child.stdout);
319+
lastConfig = config;
178320
for (const server of Object.values(config.mcpServers || {}))
179321
keepHeaderSecrets(server.headers);
180322
result.servers = Object.fromEntries(
@@ -185,13 +327,16 @@ async function main() {
185327
type: server.type,
186328
source: server.source,
187329
selectedTools: server.tools,
330+
configurationKeys: Object.keys(server),
331+
toolSelector: server.headers?.["X-MCP-Tools"],
188332
},
189333
]),
190334
);
191335
const github = config.mcpServers?.["github-mcp-server"];
192336
if (github && github.enabled !== false)
193337
result.github = await discoverTools(github);
194338
}
339+
await inspectSession(lastConfig);
195340
report.complete = true;
196341
}
197342

0 commit comments

Comments
 (0)