Skip to content

Commit f75a31b

Browse files
authored
Merge pull request #5 from fpr1m3/sentinel/input-validation-16303121858403540985
🛡️ Sentinel: Add input validation for agent metadata extraction
2 parents ff10814 + f89f579 commit f75a31b

File tree

2 files changed

+35
-9
lines changed

2 files changed

+35
-9
lines changed

.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+
## 2025-12-20 - [Log Injection Prevention]
4+
**Vulnerability:** Agent instance IDs extracted from user prompts were not validated, allowing potential injection of path traversal characters or scripts into logs.
5+
**Learning:** Parsing metadata from unstructured text (prompts) is risky without strict validation, as prompts are fully user-controlled.
6+
**Prevention:** Implemented strict allowlist validation (`^[a-zA-Z0-9\-_]+$`) for all extracted IDs before using them.

src/lib/metadata-extraction.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ export interface AgentInstanceMetadata {
1818
parent_task_id?: string; // Task ID that spawned this agent
1919
}
2020

21+
/**
22+
* Validate that an ID string contains only safe characters
23+
* Allows alphanumeric, hyphens, and underscores.
24+
* Prevents path traversal and injection attacks.
25+
*/
26+
function isValidId(id: string): boolean {
27+
return /^[a-zA-Z0-9\-_]+$/.test(id);
28+
}
29+
2130
/**
2231
* Extract agent instance ID from Task tool input
2332
*
@@ -52,13 +61,18 @@ export function extractAgentInstanceId(
5261
if (!result.agent_instance_id && toolInput?.prompt && typeof toolInput.prompt === 'string') {
5362
const promptMatch = toolInput.prompt.match(/\[AGENT_INSTANCE:\s*([^\]]+)\]/);
5463
if (promptMatch) {
55-
result.agent_instance_id = promptMatch[1].trim();
56-
57-
// Parse agent type and instance number from ID
58-
const parts = result.agent_instance_id!.match(/^([a-z-]+)-(\d+)$/);
59-
if (parts) {
60-
result.agent_type = parts[1];
61-
result.instance_number = parseInt(parts[2], 10);
64+
const extractedId = promptMatch[1].trim();
65+
66+
// Security: Validate ID format to prevent injection
67+
if (isValidId(extractedId)) {
68+
result.agent_instance_id = extractedId;
69+
70+
// Parse agent type and instance number from ID
71+
const parts = result.agent_instance_id!.match(/^([a-z-]+)-(\d+)$/);
72+
if (parts) {
73+
result.agent_type = parts[1];
74+
result.instance_number = parseInt(parts[2], 10);
75+
}
6276
}
6377
}
6478
}
@@ -68,14 +82,20 @@ export function extractAgentInstanceId(
6882
if (toolInput?.prompt && typeof toolInput.prompt === 'string') {
6983
const parentSessionMatch = toolInput.prompt.match(/\[PARENT_SESSION:\s*([^\]]+)\]/);
7084
if (parentSessionMatch) {
71-
result.parent_session_id = parentSessionMatch[1].trim();
85+
const extractedId = parentSessionMatch[1].trim();
86+
if (isValidId(extractedId)) {
87+
result.parent_session_id = extractedId;
88+
}
7289
}
7390

7491
// Extract parent task from prompt
7592
// Example: "[PARENT_TASK: research_1731445892345]"
7693
const parentTaskMatch = toolInput.prompt.match(/\[PARENT_TASK:\s*([^\]]+)\]/);
7794
if (parentTaskMatch) {
78-
result.parent_task_id = parentTaskMatch[1].trim();
95+
const extractedId = parentTaskMatch[1].trim();
96+
if (isValidId(extractedId)) {
97+
result.parent_task_id = extractedId;
98+
}
7999
}
80100
}
81101

0 commit comments

Comments
 (0)