Skip to content

Commit 29802ed

Browse files
Merge pull request #104 from gemini-cli-extensions/mcp_audit_scope_tooling
feature: mcp server organization and audit scope tooling
2 parents 262229c + 7ecd59d commit 29802ed

File tree

8 files changed

+148
-47
lines changed

8 files changed

+148
-47
lines changed

GEMINI.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This document outlines your standard procedures, principles, and skillsets for c
88

99
You are a highly skilled senior security engineer. You are meticulous, an expert in identifying modern security vulnerabilities, and you follow a strict operational procedure for every task. You MUST adhere to these core principles:
1010

11+
* **Selective Action:** Only perform security analysis when the user explicitly requests for help with code security or vulnerabilities. Before starting an analysis, ask yourself if the user is requesting generic help, or specialized security assistance.
1112
* **Assume All External Input is Malicious:** Treat all data from users, APIs, or files as untrusted until validated and sanitized.
1213
* **Principle of Least Privilege:** Code should only have the permissions necessary to perform its function.
1314
* **Fail Securely:** Error handling should never expose sensitive information.

commands/security/analyze.toml

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,8 @@ Your first action is to create a `SECURITY_ANALYSIS_TODO.md` file with the follo
106106
You will now begin executing the plan. The following are your precise instructions to start with.
107107
108108
1. **To complete the 'Define the audit scope' task:**
109-
* You **MUST** run the exact command: `git rev-parse --is-inside-work-tree`.
110-
* If the above command succeeds, returning true: then proceed to step 1a.
111-
* If the above command fails, producing a fatal error: then proceed to step 1b.
112-
113-
1a. **To define the audit scope in a git repository**
114-
* You **MUST** run the exact command: `git diff --merge-base origin/HEAD`.
115-
* If this command fails and does not produce a changelist, use this exact command: `git diff`.
116-
* This is your only method for determining the changed files. Do not use any other commands for this purpose.
117-
* Once the command is executed and you have the list of changed files, you will mark this task as complete.
118-
119-
1b. **To define the audit scope in a non-git folder**
120-
* Let the user know that you were unable to generate an automatic changelist with git, so you **MUST** prompt the user for files to security scan.
121-
* Match the users response to files in the workspace and build a list of files to analyze.
122-
* This is your only method for determining the files to analyze. Do not use any other commands for this purpose.
123-
* Once you have a list of files to analyze you will mark this task as complete.
109+
* You **MUST** use the `get_audit_scope` tool and nothing else to get a list of changed files to perform a security scan on.
110+
* After using the tool, provide the user a list of changed files. If the list of files is empty, ask the user to provide files to be scanned.
124111
125112
2. **Immediately after defining the scope, you must refine your plan:**
126113
* You will rewrite the `SECURITY_ANALYSIS_TODO.md` file.

gemini-extension.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"securityServer": {
77
"command": "node",
88
"args": [
9-
"${extensionPath}/mcp-server/dist/security.js"
9+
"${extensionPath}/mcp-server/dist/index.js"
1010
]
1111
}
1212
}

mcp-server/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "gemini-cli-security-mcp-server",
33
"type": "module",
4-
"main": "dist/security.js",
4+
"main": "dist/index.js",
55
"scripts": {
66
"build": "tsc",
7-
"dev": "tsc --watch",
8-
"start": "node dist/security.js",
7+
"dev": "tsc --watch",
8+
"start": "node dist/index.js",
99
"test": "vitest",
1010
"typecheck": "tsc --noEmit",
1111
"prepare": "npm run build"

mcp-server/src/filesystem.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { expect, describe, it, beforeAll, afterAll } from 'vitest';
8+
import { isGitHubRepository, getAuditScope } from './filesystem';
9+
import { execSync } from 'child_process';
10+
import * as fs from 'fs';
11+
12+
describe('filesystem', () => {
13+
beforeAll(() => {
14+
execSync('git init');
15+
execSync('git remote add origin https://github.com/gemini-testing/gemini-test-repo.git');
16+
fs.writeFileSync('test.txt', 'hello');
17+
execSync('git add .');
18+
execSync('git commit -m "initial commit"');
19+
});
20+
21+
afterAll(() => {
22+
fs.unlinkSync('test.txt');
23+
execSync('rm -rf .git');
24+
});
25+
26+
it('should return true if the directory is a github repository', () => {
27+
expect(isGitHubRepository()).toBe(true);
28+
});
29+
30+
it('should return a diff of the current changes', () => {
31+
fs.writeFileSync('test.txt', 'hello world');
32+
const diff = getAuditScope();
33+
expect(diff).toContain('hello world');
34+
});
35+
});

mcp-server/src/filesystem.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { spawnSync } from 'node:child_process';
8+
9+
/**
10+
* Checks if the current directory is a GitHub repository.
11+
* @returns True if the current directory is a GitHub repository, false otherwise.
12+
*/
13+
export const isGitHubRepository = (): boolean => {
14+
try {
15+
const remotes = (
16+
spawnSync('git', ['remote', '-v'], {
17+
encoding: 'utf-8',
18+
}).stdout || ''
19+
).trim();
20+
21+
const pattern = /github\.com/;
22+
23+
return pattern.test(remotes);
24+
} catch (_error) {
25+
return false;
26+
}
27+
};
28+
29+
/**
30+
* Gets a changelist of the repository
31+
*/
32+
export function getAuditScope(): string {
33+
let command = isGitHubRepository() ? 'git diff --merge-base origin/HEAD' : 'git diff';
34+
try {
35+
const diff = (
36+
spawnSync('git', command.split(' ').slice(1), {
37+
encoding: 'utf-8',
38+
}).stdout || ''
39+
).trim();
40+
41+
return diff;
42+
} catch (_error) {
43+
return "";
44+
}
45+
}

mcp-server/src/index.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* @license
5+
* Copyright 2025 Google LLC
6+
* SPDX-License-Identifier: Apache-2.0
7+
*/
8+
9+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11+
import { z } from 'zod';
12+
import { promises as fs } from 'fs';
13+
import path from 'path';
14+
import { getAuditScope } from './filesystem.js';
15+
import { findLineNumbers } from './security.js';
16+
17+
const server = new McpServer({
18+
name: 'gemini-cli-security',
19+
version: '0.1.0',
20+
});
21+
22+
server.tool(
23+
'find_line_numbers',
24+
'Finds the line numbers of a code snippet in a file.',
25+
{
26+
filePath: z
27+
.string()
28+
.describe('The path to the file to with the security vulnerability.'),
29+
snippet: z
30+
.string()
31+
.describe('The code snippet to search for inside the file.'),
32+
},
33+
(input) => findLineNumbers(input, { fs, path })
34+
);
35+
36+
server.tool(
37+
'get_audit_scope',
38+
'Checks if the current directory is a GitHub repository.',
39+
{},
40+
() => {
41+
const diff = getAuditScope();
42+
return {
43+
content: [
44+
{
45+
type: 'text',
46+
text: diff,
47+
},
48+
],
49+
};
50+
}
51+
);
52+
53+
async function startServer() {
54+
const transport = new StdioServerTransport();
55+
await server.connect(transport);
56+
}
57+
58+
startServer();

mcp-server/src/security.ts

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
1-
#!/usr/bin/env node
2-
31
/**
42
* @license
53
* Copyright 2025 Google LLC
64
* SPDX-License-Identifier: Apache-2.0
75
*/
86

9-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
107
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
11-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12-
import { z } from 'zod';
138
import { promises as fs } from 'fs';
149
import path from 'path';
1510

16-
const server = new McpServer({
17-
name: 'gemini-cli-security',
18-
version: '0.1.0',
19-
});
20-
2111
export async function findLineNumbers(
2212
{
2313
filePath,
@@ -38,7 +28,9 @@ export async function findLineNumbers(
3828
content: [
3929
{
4030
type: 'text',
41-
text: JSON.stringify({ error: 'File path is outside of the current working directory.' }),
31+
text: JSON.stringify({
32+
error: 'File path is outside of the current working directory.',
33+
}),
4234
},
4335
],
4436
};
@@ -126,20 +118,3 @@ export async function findLineNumbers(
126118
};
127119
}
128120
}
129-
130-
server.tool(
131-
'find_line_numbers',
132-
'Finds the line numbers of a code snippet in a file.',
133-
{
134-
filePath: z.string().describe('The path to the file to with the security vulnerability.'),
135-
snippet: z.string().describe('The code snippet to search for inside the file.'),
136-
},
137-
(input) => findLineNumbers(input, { fs, path })
138-
);
139-
140-
async function startServer() {
141-
const transport = new StdioServerTransport();
142-
await server.connect(transport);
143-
}
144-
145-
startServer();

0 commit comments

Comments
 (0)