Skip to content

Commit 0e02f8b

Browse files
authored
Merge pull request #20 from LLMTooling/sentinel-fix-fail-open-paths-771577714099202422
🛡️ Sentinel: [CRITICAL] Fix fail-open file system access
2 parents 1ef122d + ef1a755 commit 0e02f8b

5 files changed

Lines changed: 49 additions & 5 deletions

File tree

.jules/sentinel.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Sentinel Journal
2+
3+
## 2026-01-20 - Default File System Exposure
4+
**Vulnerability:** The MCP server defaulted to allowing access to the entire file system (path traversal) when no `--allowed-workspace` arguments were provided.
5+
**Learning:** "Fail open" defaults are dangerous, especially for tools exposed to LLMs which might explore the system. The developers likely intended this for ease of use but underestimated the risk.
6+
**Prevention:** Always implement "fail closed" security. If configuration is missing, default to the most restrictive safe option (cwd) or deny access completely, rather than allowing everything.

src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ function parseArgs(args: string[]): { allowedWorkspaces: string[] } {
3737

3838
async function main() {
3939
const { allowedWorkspaces } = parseArgs(process.argv.slice(2));
40+
41+
// If no allowed workspaces are specified, default to the current working directory
42+
if (allowedWorkspaces.length === 0) {
43+
const cwd = process.cwd();
44+
console.error(`⚠️ No allowed workspaces specified. Defaulting to current working directory: ${cwd}`);
45+
console.error('To allow other directories, use --allowed-workspace <path>');
46+
allowedWorkspaces.push(cwd);
47+
}
48+
4049
const server = new CodeSearchMCPServer({ allowedWorkspaces });
4150
await server.start();
4251
}

src/utils/workspace-path.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ export function validateAllowedPath(
5353
): string {
5454
const normalized = path.resolve(requestedPath);
5555

56-
// If no allowed workspaces are configured, allow all paths
56+
// If no allowed workspaces are configured, deny all access
5757
if (allowedWorkspaces.length === 0) {
58-
return normalized;
58+
throw new Error(
59+
`Access denied: No allowed workspaces configured. Access to ${normalized} is denied.`
60+
);
5961
}
6062

6163
const isAllowed = allowedWorkspaces.some(allowed => {

tests/unit/error-handling.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,10 @@ describe('Error Handling and Edge Cases', () => {
104104
).toThrow(/Access denied/);
105105
});
106106

107-
it('should allow any path when no workspaces are configured', () => {
108-
const result = validateAllowedPath('/any/path', []);
109-
expect(result).toBeDefined();
107+
it('should deny any path when no workspaces are configured', () => {
108+
expect(() =>
109+
validateAllowedPath('/any/path', [])
110+
).toThrow(/Access denied/);
110111
});
111112

112113
it('should allow exact match of allowed workspace', () => {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
import { describe, it, expect } from '@jest/globals';
3+
import { validateAllowedPath } from '../../src/utils/workspace-path.js';
4+
import path from 'path';
5+
6+
describe('Workspace Path Security', () => {
7+
it('should DENY access when allowedWorkspaces is empty', () => {
8+
// Secure behavior: empty array means allow nothing
9+
expect(() => {
10+
validateAllowedPath('/etc/passwd', []);
11+
}).toThrow(/Access denied/);
12+
});
13+
14+
it('should ALLOW access to explicitly allowed workspace', () => {
15+
const cwd = process.cwd();
16+
const result = validateAllowedPath(cwd, [cwd]);
17+
expect(result).toBe(cwd);
18+
});
19+
20+
it('should DENY access to path outside allowed workspace', () => {
21+
const cwd = process.cwd();
22+
expect(() => {
23+
validateAllowedPath('/etc/passwd', [cwd]);
24+
}).toThrow(/Access denied/);
25+
});
26+
});

0 commit comments

Comments
 (0)