T09 · Insecure Skill Coding Practices
Error
- Location
- patches/ollama-stream.ts:337
- Finding
- Untrusted Markdown Content Is Promoted to an Executable Tool Call<![CDATA[ ## Vulnerability Details **File Location**: `patches/ollama-stream.ts:337-365` and `patches/ollama-stream.ts:558-577` **Vulnerability Type**: Unvalidated model-output conversion across a tool-execution trust boundary **Risk Level**: High ### Vulnerable Code ```ts const MARKDOWN_TOOL_CALL_RE = /```(?:json)?\s*\n?\s*\{[\s\S]*?"name"\s*:\s*"([^"]+)"[\s\S]*?\}\s*\n?```/g; export function extractMarkdownToolCalls(content: string): OllamaToolCall[] { const results: OllamaToolCall[] = []; let match: RegExpExecArray | null; MARKDOWN_TOOL_CALL_RE.lastIndex = 0; while ((match = MARKDOWN_TOOL_CALL_RE.exec(content)) !== null) { const raw = match[0] .replace(/^```(?:json)?\s*/i, "") .replace(/\s*```$/, "") .trim(); try { const parsed = parseJsonPreservingUnsafeIntegers(raw) as Record<string, unknown>; const name = typeof parsed.name === "string" ? parsed.name : undefined; if (!name) { continue; } const args = parsed.arguments != null && typeof parsed.arguments === "object" ? (parsed.arguments as Record<string, unknown>) : parsed.parameters != null && typeof parsed.parameters === "object" ? (parsed.parameters as Record<string, unknown>) : {}; results.push({ function: { name, arguments: args } }); } catch { log.warn(`[manusilized] Failed to parse Markdown tool call: ${raw.slice(0, 120)}`); } } return results; } ``` The resulting calls are subsequently attached to the assistant response as structured tool calls: ```ts if (accumulatedToolCalls.length === 0 && accumulatedContent) { const markdownCalls = extractMarkdownToolCalls(accumulatedContent); if (markdownCalls.length > 0) { log.debug( `[manusilized] Extracted ${markdownCalls.length} tool call(s) from Markdown fallback`, ); accumulatedToolCalls.push(...markdownCalls); // Strip the tool-call JSON blocks from the visible content so the ...[truncated 3252 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Restrict calls to offered tools** - Build an immutable map from `context.tools`. - Reject every fallback call whose name is not an exact match for a tool offered in the current request. - Do not rely solely on downstream dispatchers for this validation. 2. **Validate arguments** - Validate parsed arguments against the selected tool's declared JSON schema. - Reject unknown properties, invalid types, missing required values, excessive nesting, and oversized inputs. - Reject `null`, arrays, and other values where an argument object is required. 3. **Make fallback behavior opt-in** - Disable Markdown tool-call conversion by default. - Enable it only for explicitly trusted model configurations known not to support native structured calls. - Document that enabling it expands the model-output trust boundary. 4. **Use an unambiguous protocol** - Require a unique sentinel or envelope emitted under constrained generation rather than accepting arbitrary fenced JSON. - Ensure quoted examples and retrieved content cannot satisfy the protocol. - Prefer native structured tool calling whenever it is supported. 5. **Add authorization controls** - Require user confirmation or a policy-engine decision for shell, filesystem-write, credential, external-network, and other consequential tools. - Run tools with least privilege and sandbox command execution where possible. 6. **Preserve audit visibility** - Do not silently remove the source block before authorization. - Record the original model output, parsed tool name, validated arguments, and approval result in security audit logs without exposing secrets. 7. **Add adversarial tests** - Test fenced JSON embedded in quoted documentation, retrieved webpages, user messages, and tool results. - Test unknown tool names, schema-invalid arguments, duplicate calls, malformed JSON, oversized payloads, and tool names with Unicode or case variatio ...[truncated 7 chars]
