T01 · Skill Instruction Hijacking
- Location
scripts/soul.mjs:373- Finding
Untrusted Remote Persona Content Is Installed as Agent Instructions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/soul.mjs:7, 118-141, 205-215, 217-224, 373-395
Vulnerability Type: Remote agent-instruction payload installation
Risk Level: CriticalVulnerable Code
js const DEFAULT_CATALOG_URL = 'https://raw.githubusercontent.com/mergisi/awesome-openclaw-agents/refs/heads/main/agents.json';js export async function fetchRemoteText(url) { const parsed = parseCatalogUrl(url, 'URL'); if (!parsed) { throw new Error(`Expected remote URL, got local path: ${url}`); } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const res = await fetch(parsed.toString(), { headers: { 'user-agent': USER_AGENT, 'accept': 'application/json, text/plain;q=0.9, */*;q=0.8' }, signal: controller.signal }); if (!res.ok) throw new Error(`Fetch failed: ${res.status} ${res.statusText} for ${parsed}`); return await res.text(); } catch (err) { if (err?.name === 'AbortError') { throw new Error(`Fetch timed out after ${FETCH_TIMEOUT_MS}ms for ${parsed}`); } throw err; } finally { clearTimeout(timeout); } }js export function validateSoulContent(content, sourceUrl) { const text = typeof content === 'string' ? content.trim() : ''; if (!text) throw new Error(`Fetched soul content is empty: ${sourceUrl}`); if (!(text.startsWith('#') || text.startsWith('---') || text.includes('\n# '))) { throw new Error(`Fetched content does not look like a SOUL.md file: ${sourceUrl}`); } return content; }js if (subcommand === 'apply') { const id = rest[0] || ''; if (!id) return print('Usage: soul apply <id>'); const agent = findAgent(catalog, id); if (!agent) return print(`Soul not found: ${id}`); const sourceUrl = buildRawSoulUrl(state, agent); const content = validateSoulContent(await fetchText(sourceUrl), sourceUrl); const checksum = sha1(conten ...[truncated 3221 chars]- Remediation
View remediation
Remediation Suggestions
- Do not write remotely retrieved free-form text directly into a privileged agent instruction file.
- Pin the catalog and every persona to immutable commit hashes rather than mutable branches.
- Require a signed manifest containing each persona's canonical source, digest, version, and approved capabilities.
- Verify a modern cryptographic digest such as SHA-256 before installation. The expected digest must come from an authenticated source.
- Display the complete persona content and require explicit user confirmation before replacing
SOUL.md. - Parse personas through a restrictive schema and reject directives concerning secrets, tool execution, external communication, instruction precedence, policy bypass, or persistence.
- Prefer a constrained persona format containing only stylistic attributes, then generate trusted
SOUL.mdcontent locally. - Record and expose immutable provenance, including repository, commit, signer, and verified digest.
- Add adversarial tests using Markdown-formatted prompt-injection payloads to confirm they are rejected.
