Back to skill

Security audit

ronin

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its marketplace purpose, but it gives agents high-impact signing and worker automation with weak defaults and under-scoped network/install controls.

Install only if you are comfortable with Ronin holding marketplace state under a local signing key. Prefer using the bundled local client rather than curling a live remote copy, protect RONIN_KEY, remove --auto-accept unless you have clear limits, set the worker's auto_receive to false unless payment verification is automated, and run the worker in a sandboxed account or container with restricted network access.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
ronin-worker.mjs:167
Finding

Unrestricted Marketplace Attachment URLs Enable Blind SSRF

Content
View full analysis

Vulnerability Details

File Location: ronin-worker.mjs, lines 167–175 and 190–204
Vulnerability Type: Server-Side Request Forgery through untrusted attachment URLs
Risk Level: Medium

Vulnerable Code

js
async function fetchVerified(ref, path) {
  const url = /^https?:/.test(ref.url) ? ref.url : HUB + ref.url;
  const r = await fetch(url);
  if (!r.ok) throw new Error(`could not fetch ${url}: ${r.status}`);
  const bytes = Buffer.from(await r.arrayBuffer());
  const got = createHash('sha256').update(bytes).digest('hex');
  if (got !== ref.hash) throw new Error(`hash mismatch on ${url}: signed ${ref.hash}, got ${got}`);
  writeFileSync(path, bytes);
  return bytes;
}

The function is reached for both skill bundles and input attachments:

js
if (t.attachments.skill.kind === 'bundle') {
  const bytes = await fetchVerified(t.attachments.skill, join(tmp, 'skill.zip'));
  for (const [name, data] of readBundle(bytes).files) {
    const dest = join(vars.skill_dir, ...name.split('/'));
    mkdirSync(dirname(dest), { recursive: true });
    writeFileSync(dest, data);
  }
} else await fetchVerified(t.attachments.skill, join(vars.skill_dir, 'SKILL.md'));

if (t.attachments?.inputs?.length) {
  vars.inputs_dir = join(tmp, 'inputs');
  mkdirSync(vars.inputs_dir, { recursive: true });
  for (const [i, f] of t.attachments.inputs.entries())
    await fetchVerified(f, join(vars.inputs_dir, (f.name ?? `input-${i + 1}`).replace(/[^\w.-]/g, '_')));
}

Technical Analysis

The worker accepts an absolute HTTP or HTTPS URL from an accepted marketplace task and passes it directly to fetch. It does not restrict the URL to the configured Ronin hub, validate the resolved IP address, reject private or loopback networks, or validate redirect destinations.

The SHA-256 comparison verifies the response body only after the network request has occurred. It therefore protects artifact integrity but does not prevent SSRF. A failed hash check also does not un ...[truncated 1661 chars]

Remediation
View remediation

Remediation Suggestions

  • Permit relative artifact paths under the configured Ronin hub by default.
  • If external artifact hosting is required, enforce an explicit origin allowlist.
  • Parse URLs with the platform URL API and allow only expected protocols, ports, and origins.
  • Resolve hostnames before connecting and reject loopback, link-local, private, multicast, unspecified, and cloud metadata address ranges for both IPv4 and IPv6.
  • Disable redirects or repeat full destination validation for every redirect hop.
  • Consider downloading attachments through a trusted artifact service rather than allowing workers to contact arbitrary origins.
  • Apply response-size and request-time limits in addition to the existing post-download ZIP limits.

T09 · Insecure Skill Coding Practices

Warning
Location
ronin-worker.mjs:127
Finding

Default Automatic Receipt Trusts Buyer-Controlled Settlement Metadata

Content
View full analysis

Vulnerability Details

File Location: ronin-worker.mjs, lines 127, 258–270, and 317–319
Vulnerability Type: Improper authorization of off-platform payment acknowledgment
Risk Level: Medium

Vulnerable Code

Automatic receipt acknowledgment is enabled by default:

js
return { concurrency: 1, auto_receive: true, attempts: 3, retry_backoff_s: 5, ...cfg };

The receipt function compares only the amount and currency represented in the task template:

js
async function receive(key, t) {
  const tpl = (t.next ?? []).find((n) => n.kind === K.RECEIVED);
  if (!tpl) return;
  const amount = tpl.tags.find((x) => x[0] === 'amount')?.[1];
  const currency = tpl.tags.find((x) => x[0] === 'currency')?.[1];
  if (String(amount) !== String(t.price) || String(currency).toUpperCase() !== String(t.currency).toUpperCase()) {
    human(`task ${t.task}: buyer recorded ${amount} ${currency}, offer was ${t.price} ${t.currency}; not acknowledging, check your rail`);
    log('receive_skipped', { task: t.task, paid: `${amount} ${currency}`, offer: `${t.price} ${t.currency}` });
    return;
  }
  const r = await post(sign({ kind: tpl.kind, tags: tpl.tags, content: '' }, key.secret));
  log(r.body.ok ? 'received' : 'receive_refused', { task: t.task, amount, currency, error: r.body.error });
}

The worker invokes it automatically for pending settlements:

js
} else if (t.status === 'complete' && t.settlement === 'pending' && cfg.auto_receive) {
  queue.push(() => receive(key, t));
}

Technical Analysis

The project states that the hub records settlement but does not move money. Consequently, marketplace state indicating that a payment is pending or recorded is not proof that funds reached the seller’s external payment rail.

The worker nevertheless defaults auto_receive to true and signs a RECEIVED event after checking only that the template’s amount and currency match the offer. It does not verify a payment transaction, recipient address, c ...[truncated 1473 chars]

Remediation
View remediation

Remediation Suggestions

  • Change the default to auto_receive: false.
  • Require explicit operator confirmation before posting a RECEIVED event when no payment-rail integration is configured.
  • If automation is required, integrate with the relevant payment rail and verify:
    • transaction identifier;
    • recipient account or address;
    • asset and network;
    • exact amount;
    • sufficient confirmation or finality;
    • uniqueness to prevent transaction reuse.
  • Store verified transaction evidence with the settlement record.
  • Treat hub-provided amount and currency as claims to validate, not as payment proof.
  • Make the worker refuse automatic acknowledgment when the payout rail cannot be independently queried.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 35)May include surrounding context.

md
- `ronin.mjs`: the reference client. One file, needs `@noble/curves` and `@noble/hashes`.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 38)May include surrounding context.

md
- `ronin-worker.mjs` and `ronin-worker.example.json`: optional. Map a listing slug to a

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The setup instructs users to fetch a JavaScript client directly via curl from a remote host and then use it for key-management and registration, but provides no checksum, signature verification, pinning, or review guidance. Because this client handles identity material and can be swapped server-side or via transport/DNS compromise, a malicious or tampered download could steal Ronin keys, alter registrations, or execute arbitrary logic under the user's account.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The registration command enables --auto-accept by default in the documented setup flow without clearly warning that the agent will automatically accept matching work. In a marketplace skill, this materially increases risk because users may unknowingly commit their identity/account to jobs, spam, abusive requests, or unfavorable obligations with no manual review step.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill explicitly requires network access (curl, remote hub URLs) and reads sensitive environment/config state (RONIN_KEY, RONIN_URL), but it declares no permissions or allowed-tools scope. That mismatch weakens sandboxing and review controls, making it easier for an agent platform to grant broader capabilities than users expect when the skill performs remote registration, hiring, and task operations.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 28)May include surrounding context.

md
A market where keys hire keys. You register a secp256k1 key, list a service or a packaged
skill, get hired by another key (an agent or a person), deliver, record a two-sided
settlement the hub only counts and never touches, and earn a trust score that follows the
key. Every write is a signed event; the hub verifies the signature and the chain.

This skill is free. Listings on the hub carry their own prices between the two parties;
the hub does not move money and this skill does not either.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest describes a skill for hiring/getting hired on Ronin, registering/listing skills or services, delivering work, and settling peer-to-peer. This worker configuration instead defines two unrelated execution slugs: an identity-check dossier generator and a generic summarizer, with no visible Ronin-specific marketplace, registry, hiring, or settlement behavior. These operations appear broader than, and semantically disconnected from, the stated Ronin platform purpose.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The successor command generates a new secret key and writes both the old and new key material to disk, including saving the previous private key to a .old file. Although the code performs the write directly, there is no confirmation prompt, log message, or nearby comment warning users that additional secret-bearing files will be created.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
ronin-worker.mjs:135

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
ronin-worker.mjs:51