Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## 2026-04-27 - Path Traversal Vulnerability in File System Tools
**Vulnerability:** The `FileTool` operations (`read`, `write`, `patch`) allowed arbitrary file system access because user-provided paths were used directly without sanitization, exposing a path traversal risk.
**Learning:** File path inputs to SDK operations must be properly resolved and validated to prevent unauthorized access outside the intended working directory. Unrestricted access breaks the security boundaries of an agentic execution environment.
**Prevention:** Always use `path.resolve(process.cwd(), unsafePath)` and verify that the resulting absolute path begins with `process.cwd()`. Implement secure failure paths and never expose raw internal file structures on error.
**Prevention:** Always use `path.resolve(process.cwd(), unsafePath)` and verify that the resulting absolute path begins with `process.cwd()`. Implement secure failure paths and never expose raw internal file structures on error.
## 2026-05-25 - Prevent Denial of Service (DoS) via Execution and Network Timeouts
**Vulnerability:** The `ShellTool` and `AgentConnector` lacked execution and network timeouts. Long-running shell commands or non-responsive LLM API endpoints could cause unbounded execution or hang indefinitely, leading to memory and system resource exhaustion (DoS) during agentic loops.
**Learning:** In autonomous systems, assuming external processes or network calls will complete quickly is dangerous. Without explicit boundaries, a rogue or stalled execution will lock up the self-healing engine and block further operations.
**Prevention:** Always enforce strict boundaries on external interactions. Apply a 30-second timeout and max buffer limits (e.g., 10MB) for `execAsync` operations, and set explicit timeouts for network requests (e.g., using `axios`'s `timeout` config) to prevent resource exhaustion.
1 change: 1 addition & 0 deletions dist/src/agents/connector.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ToolDefinition {
export declare class AgentConnector {
private core;
private config;
private client;
constructor(core: CoreEngine, config?: {
provider?: string;
apiKey?: string;
Expand Down
42 changes: 41 additions & 1 deletion dist/src/agents/connector.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentConnector = void 0;
const axios_1 = __importDefault(require("axios"));
const https = __importStar(require("https"));
class AgentConnector {
constructor(core, config = {}) {
this.core = core;
this.config = config;
// Optimize repeated API calls by enabling keep-alive for HTTP/HTTPS connections.
// This avoids expensive TCP/TLS handshakes (~100-200ms per request) on consecutive LLM calls.
this.client = axios_1.default.create({
httpsAgent: new https.Agent({ keepAlive: true }),
timeout: 30000 // Prevent network resource exhaustion by enforcing a strict 30s timeout
});
}
/**
* Standard protocol to expose oneshotsx tools to any LLM
Expand Down Expand Up @@ -39,7 +79,7 @@ class AgentConnector {
return "Agent running in simulation mode. No API key provided.";
}
try {
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
const response = await this.client.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'meta-llama/llama-3.1-70b-instruct',
messages: [{ role: 'user', content: message }]
}, {
Expand Down
7 changes: 4 additions & 3 deletions dist/src/tools/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export declare class ShellTool {
export declare class FileTool {
private core;
constructor(core: CoreEngine);
read(path: string): Promise<ExecutionResult<string>>;
write(path: string, content: string): Promise<ExecutionResult<void>>;
patch(path: string, search: string, replace: string): Promise<ExecutionResult<void>>;
private resolveAndValidatePath;
read(unsafePath: string): Promise<ExecutionResult<string>>;
write(unsafePath: string, content: string): Promise<ExecutionResult<void>>;
patch(unsafePath: string, search: string, replace: string): Promise<ExecutionResult<void>>;
}
export declare class SearchTool {
private core;
Expand Down
40 changes: 28 additions & 12 deletions dist/src/tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ exports.SearchTool = exports.FileTool = exports.ShellTool = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const execAsync = (0, util_1.promisify)(child_process_1.exec);
class ShellTool {
constructor(core) {
Expand All @@ -45,7 +46,8 @@ class ShellTool {
async run(command) {
this.core.log(`Executing shell command: ${command}`);
return this.core.execute(async () => {
const { stdout, stderr } = await execAsync(command);
// Apply strict boundaries to prevent Denial of Service (DoS) and resource exhaustion
const { stdout, stderr } = await execAsync(command, { timeout: 30000, maxBuffer: 10 * 1024 * 1024 });
return { stdout, stderr };
});
}
Expand All @@ -55,23 +57,37 @@ class FileTool {
constructor(core) {
this.core = core;
}
async read(path) {
this.core.log(`Reading file: ${path}`);
return this.core.execute(() => fs.readFile(path, 'utf-8'));
resolveAndValidatePath(unsafePath) {
const resolvedPath = path.resolve(process.cwd(), unsafePath);
if (!resolvedPath.startsWith(process.cwd() + path.sep) && resolvedPath !== process.cwd()) {
throw new Error('Access denied: Invalid path');
}
return resolvedPath;
}
async write(path, content) {
this.core.log(`Writing file: ${path}`);
return this.core.execute(() => fs.writeFile(path, content));
async read(unsafePath) {
this.core.log(`Reading file: ${unsafePath}`);
return this.core.execute(() => {
const safePath = this.resolveAndValidatePath(unsafePath);
return fs.readFile(safePath, 'utf-8');
});
}
async write(unsafePath, content) {
this.core.log(`Writing file: ${unsafePath}`);
return this.core.execute(() => {
const safePath = this.resolveAndValidatePath(unsafePath);
return fs.writeFile(safePath, content);
});
}
async patch(path, search, replace) {
this.core.log(`Patching file: ${path}`);
async patch(unsafePath, search, replace) {
this.core.log(`Patching file: ${unsafePath}`);
return this.core.execute(async () => {
const content = await fs.readFile(path, 'utf-8');
const safePath = this.resolveAndValidatePath(unsafePath);
const content = await fs.readFile(safePath, 'utf-8');
const newContent = content.replace(search, replace);
if (content === newContent) {
throw new Error(`Search string not found in ${path}`);
throw new Error(`Search string not found in file`);
}
await fs.writeFile(path, newContent);
await fs.writeFile(safePath, newContent);
});
}
}
Expand Down
111 changes: 111 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.0.0",
"jest": "^29.5.0",
"jest-util": "^30.4.1",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.0",
"typescript": "^5.0.0"
Expand Down
3 changes: 2 additions & 1 deletion src/agents/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export class AgentConnector {
// Optimize repeated API calls by enabling keep-alive for HTTP/HTTPS connections.
// This avoids expensive TCP/TLS handshakes (~100-200ms per request) on consecutive LLM calls.
this.client = axios.create({
httpsAgent: new https.Agent({ keepAlive: true })
httpsAgent: new https.Agent({ keepAlive: true }),
timeout: 30000 // Prevent network resource exhaustion by enforcing a strict 30s timeout
});
}

Expand Down
3 changes: 2 additions & 1 deletion src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export class ShellTool {
async run(command: string): Promise<ExecutionResult<{ stdout: string; stderr: string }>> {
this.core.log(`Executing shell command: ${command}`);
return this.core.execute(async () => {
const { stdout, stderr } = await execAsync(command);
// Apply strict boundaries to prevent Denial of Service (DoS) and resource exhaustion
const { stdout, stderr } = await execAsync(command, { timeout: 30000, maxBuffer: 10 * 1024 * 1024 });
return { stdout, stderr };
});
}
Expand Down