T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/vaibot-guard-service.mjs:81
- Finding
- Path Traversal Through Client-Controlled Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vaibot-guard-service.mjs:81-82, 179-180, 789-790, 824-825, 840-841, 870-871, 877-878, 947-948, 1031-1033` **Vulnerability Type**: Unvalidated path construction and directory traversal **Risk Level**: High ### Vulnerable Code ```js function approvalPath(approvalId) { return path.join(APPROVAL_DIR, `${approvalId}.json`); } function runCtxPath(runId) { return path.join(RUNCTX_DIR, `${runId}.json`); } function loadMerkleState(sessionId) { const p = path.join(LOG_DIR, `${sessionId}.merkle.json`); // ... } function saveMerkleState(sessionId, st) { const p = path.join(LOG_DIR, `${sessionId}.merkle.json`); fs.writeFileSync(p, JSON.stringify(st, null, 2) + "\n"); } function appendLeaf(sessionId, leaf) { const p = path.join(LOG_DIR, `${sessionId}.leaves.jsonl`); fs.appendFileSync(p, stableStringify({ leaf }) + "\n"); } function loadCheckpoints(sessionId) { const cpPath = path.join(LOG_DIR, `${sessionId}.checkpoints.jsonl`); // ... } function loadLeaves(sessionId, count) { const p = path.join(LOG_DIR, `${sessionId}.leaves.jsonl`); // ... } function appendCheckpoint(sessionId, checkpoint) { const p = path.join(LOG_DIR, `${sessionId}.checkpoints.jsonl`); fs.appendFileSync(p, stableStringify(checkpoint) + "\n"); } function appendAudit(event) { const sessionId = event.sessionId || "unknown-session"; const logPath = path.join(LOG_DIR, `${sessionId}.jsonl`); const prevHashPath = path.join(LOG_DIR, `${sessionId}.prevhash`); // ... } ``` ### Technical Analysis The service accepts `sessionId`, `approvalId`, and `runId` from HTTP request bodies and directly interpolates them into filesystem paths. It does not reject path separators, `..` components, absolute-path syntax, or identifiers exceeding an expected format. `path.join()` normalizes traversal components. An identifier such as `../../target` can therefore escape `LOG_DIR`, `APPROVAL_DIR`, or `RUNCTX_DIR`. Depending o ...[truncated 1691 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply strict syntax validation to every externally supplied identifier: ```js function validateIdentifier(value, name) { const s = String(value || ""); if (!/^[A-Za-z0-9_-]{1,128}$/.test(s)) { throw new Error(`Invalid ${name}`); } return s; } ``` 2. Resolve every generated path and confirm it remains inside its intended root: ```js function safeChildPath(root, filename) { const rootResolved = path.resolve(root); const candidate = path.resolve(rootResolved, filename); const rel = path.relative(rootResolved, candidate); if (rel.startsWith("..") || path.isAbsolute(rel)) { throw new Error("Path escapes storage directory"); } return candidate; } ``` 3. Generate opaque server-side identifiers instead of accepting arbitrary path-related identifiers from clients. 4. Apply the validation consistently to approval, run-context, audit, Merkle, checkpoint, leaf, proof, and wrapper log paths. 5. Add regression tests containing `../`, `..\`, encoded separators, absolute paths, long identifiers, and platform-specific path syntax. 6. Run the service as a dedicated unprivileged account with access limited to its own data directory. ]]>
