Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ server.registerPrompt(
role: 'user' as const,
content: {
type: 'text' as const,
text: `You are a security expert. Your task is to generate a Proof-of-Concept (PoC) for a vulnerability.
text: `You are a security expert. Your task is to generate a Proof-of-Concept (PoC) for a vulnerability for Node.js, Python or Go projects. If the project is not for one of these languages, let the user know that you cannot generate a PoC for this project type.

Problem Statement: ${problemStatement || 'No problem statement provided, if you need more information to generate a PoC, ask the user.'}
Source Code Location: ${sourceCodeLocation || 'No source code location provided, try to derive it from the Problem Statement. If you cannot derive it, ask the user for the source code location.'}
Expand All @@ -170,7 +170,7 @@ server.registerPrompt(

1. **Generate PoC:**
* Create a '${POC_DIR_NAME}' directory in '${SECURITY_DIR_NAME}' if it doesn't exist.
* Generate a Node.js script that demonstrates the vulnerability under the '${SECURITY_DIR_NAME}/${POC_DIR_NAME}/' directory.
* Based on the user's project language, generate a script for Python/Go/Node that demonstrates the vulnerability under the '${SECURITY_DIR_NAME}/${POC_DIR_NAME}/' directory.
* Based on the vulnerability type certain criteria must be met in our script, otherwise generate the PoC to the best of your ability:
* If the vulnerability is a Path Traversal Vulnerability:
* **YOU MUST** Use the 'write_file' tool to create a temporary file '../gcli_secext_temp.txt' directly outside of the project directory.
Expand Down
56 changes: 40 additions & 16 deletions mcp-server/src/poc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,59 @@ describe('runPoc', () => {
if (p2) return p1 + '/' + p2;
return p1;
},
extname: (p: string) => {
const idx = p.lastIndexOf('.');
return idx !== -1 ? p.substring(idx) : '';
},
sep: '/',
};

it('should execute the file at the given path', async () => {
const mockExecAsync = vi.fn(async (cmd: string) => {
if (cmd.startsWith('npm install')) {
return { stdout: '', stderr: '' };
}
return { stdout: 'output', stderr: '' };
});
const mockExecFileAsync = vi.fn(async (file: string, args?: string[]) => {
return { stdout: 'output', stderr: '' };
});
it('should execute a Node.js file', async () => {
const mockExecAsync = vi.fn(async () => { return { stdout: '', stderr: '' }; });
const mockExecFileAsync = vi.fn(async () => { return { stdout: 'output', stderr: '' }; });

const result = await runPoc(
{ filePath: `${POC_DIR}/test.js` },
{ fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any, execFileAsync: mockExecFileAsync as any }
);

expect(mockExecAsync).toHaveBeenCalledTimes(1);
expect(mockExecAsync).toHaveBeenCalledWith(
'npm install --registry=https://registry.npmjs.org/',
{ cwd: POC_DIR }
);
expect(mockExecAsync).toHaveBeenCalledWith('npm install --registry=https://registry.npmjs.org/', { cwd: POC_DIR });
expect(mockExecFileAsync).toHaveBeenCalledTimes(1);
expect(mockExecFileAsync).toHaveBeenCalledWith('node', [`${POC_DIR}/test.js`]);
expect((result.content[0] as any).text).toBe(
JSON.stringify({ stdout: 'output', stderr: '' })
expect((result.content[0] as any).text).toBe(JSON.stringify({ stdout: 'output', stderr: '' }));
});

it('should execute a Python file', async () => {
const mockExecAsync = vi.fn(async () => { return { stdout: '', stderr: '' }; });
const mockExecFileAsync = vi.fn(async () => { return { stdout: 'output', stderr: '' }; });

const result = await runPoc(
{ filePath: `${POC_DIR}/test.py` },
{ fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any, execFileAsync: mockExecFileAsync as any }
);

expect(mockExecAsync).toHaveBeenCalledTimes(1);
expect(mockExecAsync).toHaveBeenCalledWith('pip3 install -r requirements.txt', { cwd: POC_DIR });
expect(mockExecFileAsync).toHaveBeenCalledTimes(1);
expect(mockExecFileAsync).toHaveBeenCalledWith('python3', [`${POC_DIR}/test.py`]);
expect((result.content[0] as any).text).toBe(JSON.stringify({ stdout: 'output', stderr: '' }));
});

it('should execute a Go file', async () => {
const mockExecAsync = vi.fn(async () => { return { stdout: '', stderr: '' }; });
const mockExecFileAsync = vi.fn(async () => { return { stdout: 'output', stderr: '' }; });

const result = await runPoc(
{ filePath: `${POC_DIR}/test.go` },
{ fs: {} as any, path: mockPath as any, execAsync: mockExecAsync as any, execFileAsync: mockExecFileAsync as any }
);

expect(mockExecAsync).toHaveBeenCalledTimes(1);
expect(mockExecAsync).toHaveBeenCalledWith('go mod tidy', { cwd: POC_DIR });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 These tests are great! It's good to see that you've covered the new languages. You could consider creating a helper function to reduce code duplication in the tests, since the structure of the tests for each language is very similar.

expect(mockExecFileAsync).toHaveBeenCalledTimes(1);
expect(mockExecFileAsync).toHaveBeenCalledWith('go', ['run', `${POC_DIR}/test.go`]);
expect((result.content[0] as any).text).toBe(JSON.stringify({ stdout: 'output', stderr: '' }));
});

it('should handle execution errors', async () => {
Expand Down
37 changes: 30 additions & 7 deletions mcp-server/src/poc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export async function runPoc(
try {
const pocDir = dependencies.path.dirname(filePath);

// 🛡️ Validate that the filePath is within the safe PoC directory
// Validate that the filePath is within the safe PoC directory
Comment thread
QuinnDACollins marked this conversation as resolved.
Outdated
const resolvedFilePath = dependencies.path.resolve(filePath);
const safePocDir = dependencies.path.resolve(process.cwd(), POC_DIR);

Expand All @@ -43,13 +43,36 @@ export async function runPoc(
};
}

try {
await dependencies.execAsync('npm install --registry=https://registry.npmjs.org/', { cwd: pocDir });
} catch (error) {
// 📦 Ignore errors from npm install, as it might fail if no package.json exists,
// but we still want to attempt running the PoC.
const ext = dependencies.path.extname(filePath).toLowerCase();

let installCmd: string | null = null;
let runCmd: string;
let runArgs: string[];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The `toLowerCase()` call is redundant because you are already checking against lowercase extensions in the `if` and `else if` conditions.
Suggested change
let runArgs: string[];
const ext = dependencies.path.extname(filePath);


if (ext === '.py') {
runCmd = 'python3';
runArgs = [filePath];
installCmd = 'pip3 install -r requirements.txt';
} else if (ext === '.go') {
runCmd = 'go';
runArgs = ['run', filePath];
installCmd = 'go mod tidy';
} else {
runCmd = 'node';
runArgs = [filePath];
installCmd = 'npm install --registry=https://registry.npmjs.org/';
}
const { stdout, stderr } = await dependencies.execFileAsync('node', [filePath]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 For better readability and maintainability, you could consider using a switch statement here instead of if/else if.
Suggested change
runCmd = 'python3';
runArgs = [filePath];
installCmd = 'pip3 install -r requirements.txt';
} else if (ext === '.go') {
runCmd = 'go';
runArgs = ['run', filePath];
installCmd = 'go mod tidy';
} else {
runCmd = 'node';
runArgs = [filePath];
installCmd = 'npm install --registry=https://registry.npmjs.org/';
}
const { stdout, stderr } = await dependencies.execFileAsync('node', [filePath]);
let installCmd: string | null = null;
let runCmd: string;
let runArgs: string[];
switch (ext) {
case '.py':
runCmd = 'python3';
runArgs = [filePath];
installCmd = 'pip3 install -r requirements.txt';
break;
case '.go':
runCmd = 'go';
runArgs = ['run', filePath];
installCmd = 'go mod tidy';
break;
default:
runCmd = 'node';
runArgs = [filePath];
installCmd = 'npm install --registry=https://registry.npmjs.org/';
}

if (installCmd) {
try {
await dependencies.execAsync(installCmd, { cwd: pocDir });
} catch (error) {
// Ignore errors from install step, as it might fail if no config file exists,
// but we still want to attempt running the PoC.
}
Comment thread
QuinnDACollins marked this conversation as resolved.
}

const { stdout, stderr } = await dependencies.execFileAsync(runCmd, runArgs);

return {
content: [
Expand Down
Loading