Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ For every potential finding, you must perform a quick "So What?" test. If a theo

* **Example:** A piece of code might use a slightly older, but not yet broken, cryptographic algorithm for a non-sensitive, internal cache key. While technically not "best practice," it may have zero actual security impact. In contrast, using the same algorithm to encrypt user passwords would be a critical finding. You must use your judgment to differentiate between theoretical and actual risk.

* **YOU MUST** 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.

---
### Your Final Review Filter
Before you add a vulnerability to your final report, it must pass every question on this checklist:
Expand Down
17 changes: 2 additions & 15 deletions commands/security/analyze.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,8 @@ Your first action is to create a `SECURITY_ANALYSIS_TODO.md` file with the follo
You will now begin executing the plan. The following are your precise instructions to start with.

1. **To complete the 'Define the audit scope' task:**
* You **MUST** run the exact command: `git rev-parse --is-inside-work-tree`.
* If the above command succeeds, returning true: then proceed to step 1a.
* If the above command fails, producing a fatal error: then proceed to step 1b.

1a. **To define the audit scope in a git repository**
* You **MUST** run the exact command: `git diff --merge-base origin/HEAD`.
* If this command fails and does not produce a changelist, use this exact command: `git diff`.
* This is your only method for determining the changed files. Do not use any other commands for this purpose.
* Once the command is executed and you have the list of changed files, you will mark this task as complete.

1b. **To define the audit scope in a non-git folder**
* 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.
* Match the users response to files in the workspace and build a list of files to analyze.
* This is your only method for determining the files to analyze. Do not use any other commands for this purpose.
* Once you have a list of files to analyze you will mark this task as complete.
* You **MUST** use the `get_audit_scope` tool and nothing else to get a list of changed files to perform a security scan on.
* After using the tool, provide the user a list of changed files, and ask if there are any additional files they would like to scan, or if there are any they would like to exclude from this scan.

2. **Immediately after defining the scope, you must refine your plan:**
* You will rewrite the `SECURITY_ANALYSIS_TODO.md` file.
Expand Down
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"securityServer": {
"command": "node",
"args": [
"${extensionPath}/mcp-server/dist/security.js"
"${extensionPath}/mcp-server/dist/index.js"
]
}
}
Expand Down
8 changes: 4 additions & 4 deletions mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "gemini-cli-security-mcp-server",
"type": "module",
"main": "dist/security.js",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/security.js",
"dev": "tsc --watch",
"start": "node dist/index.js",
"test": "vitest",
"typecheck": "tsc --noEmit",
"prepare": "npm run build"
Expand All @@ -19,4 +19,4 @@
"@modelcontextprotocol/sdk": "^1.18.0",
"zod": "^3.24.2"
}
}
}
35 changes: 35 additions & 0 deletions mcp-server/src/filesystem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { expect, describe, it, beforeAll, afterAll } from 'vitest';
import { isGitHubRepository, getAuditScope } from './filesystem';
import { execSync } from 'child_process';
import * as fs from 'fs';

describe('filesystem', () => {
beforeAll(() => {
execSync('git init');
execSync('git remote add origin https://github.com/gemini-testing/gemini-test-repo.git');
fs.writeFileSync('test.txt', 'hello');
execSync('git add .');
execSync('git commit -m "initial commit"');
});

afterAll(() => {
fs.unlinkSync('test.txt');
execSync('rm -rf .git');
});

it('should return true if the directory is a github repository', () => {
expect(isGitHubRepository()).toBe(true);
});

it('should return a diff of the current changes', () => {
fs.writeFileSync('test.txt', 'hello world');
const diff = getAuditScope();
expect(diff).toContain('hello world');
});
});
45 changes: 45 additions & 0 deletions mcp-server/src/filesystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { execSync } from 'node:child_process';

/**
* Checks if the current directory is a GitHub repository.
* @returns True if the current directory is a GitHub repository, false otherwise.
*/
export const isGitHubRepository = (): boolean => {
try {
const remotes = (
execSync('git remote -v', {
encoding: 'utf-8',
}) || ''
).trim();

const pattern = /github\.com/;

return pattern.test(remotes);
} catch (_error) {
return false;
}
};

/**
* Gets a changelist of the repository
*/
export function getAuditScope(): string {
let command = isGitHubRepository() ? 'git diff --merge-base origin/HEAD' : 'git diff';
try {
const diff = (
execSync(command, {
encoding: 'utf-8',
}) || ''
).trim();

return diff;
} catch (_error) {
return "error generating diff for audit scope";
}
}
58 changes: 58 additions & 0 deletions mcp-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env node

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { promises as fs } from 'fs';
import path from 'path';
import { getAuditScope } from './filesystem.js';
import { findLineNumbers } from './security.js';

const server = new McpServer({
name: 'gemini-cli-security',
version: '0.1.0',
});

server.tool(
'find_line_numbers',
'Finds the line numbers of a code snippet in a file.',
{
filePath: z
.string()
.describe('The path to the file to with the security vulnerability.'),
snippet: z
.string()
.describe('The code snippet to search for inside the file.'),
},
(input) => findLineNumbers(input, { fs, path })
);

server.tool(
'get_audit_scope',
'Checks if the current directory is a GitHub repository.',
{},
() => {
const diff = getAuditScope();
return {
content: [
{
type: 'text',
text: diff,
},
],
};
}
);

async function startServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}

startServer();
32 changes: 4 additions & 28 deletions mcp-server/src/security.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
#!/usr/bin/env node

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { promises as fs } from 'fs';
import path from 'path';

const server = new McpServer({
name: 'gemini-cli-security',
version: '0.1.0',
});
import { isGitHubRepository } from './filesystem.js';

export async function findLineNumbers(
{
Expand All @@ -38,7 +29,9 @@ export async function findLineNumbers(
content: [
{
type: 'text',
text: JSON.stringify({ error: 'File path is outside of the current working directory.' }),
text: JSON.stringify({
error: 'File path is outside of the current working directory.',
}),
},
],
};
Expand Down Expand Up @@ -126,20 +119,3 @@ export async function findLineNumbers(
};
}
}

server.tool(
'find_line_numbers',
'Finds the line numbers of a code snippet in a file.',
{
filePath: z.string().describe('The path to the file to with the security vulnerability.'),
snippet: z.string().describe('The code snippet to search for inside the file.'),
},
(input) => findLineNumbers(input, { fs, path })
);

async function startServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}

startServer();
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading