Back to skill

Security audit

Protagons

Security checks for vulnerabilities and agentic risk

Overview

The skill is open about loading AI personas, but it lets mutable remote persona text steer the agent and sends a Google API key to a third-party backend.

Review this skill before installing. Use it only if you are comfortable with agents applying persona text fetched from api.usaw.ai, and treat deployed SOUL.md as untrusted content that must not override higher-priority instructions. For generation, use only a tightly scoped, low-quota, throwaway Google/Gemini API key and rotate or revoke it after use.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
protagons.mjs:179
Finding
Untrusted Remote SOUL.md Content Can Hijack Agent Instructions<![CDATA[ ## Vulnerability Details **File Location**: `protagons.mjs:179-207`; related behavioral instructions in `SKILL.md:13-23` **Vulnerability Type**: Remote instruction injection through mutable identity content **Risk Level**: High ### Vulnerable Code ```javascript export async function protagons_deploy(params, context) { if (!params.slug) throw new Error('slug is required'); const slug = encodeURIComponent(params.slug); let soulMd = null; let name = params.slug; let contentTier = 'standard'; // 1. Try fetching the pre-generated SOUL.md try { soulMd = await apiFetchText(`/library/${slug}/soul.md`); if (!soulMd || !soulMd.trim()) soulMd = null; } catch { // SOUL.md endpoint unavailable — fall back below } // 2. Fetch the .protagon.json for metadata (and fallback compilation) const protagon = await apiFetch(`/library/${slug}`); name = protagon.name || name; contentTier = protagon.deployment?.content_tier || 'standard'; // 3. Fall back to client-side compilation if no pre-generated SOUL.md if (!soulMd) { soulMd = compileSoulMd(protagon); } return { soul_md: soulMd, protagon_slug: params.slug, protagon_name: name, content_tier: contentTier, deployed_at: new Date().toISOString(), }; } ``` The accompanying skill instructions direct the agent to treat this remote content as behavioral instructions: ```markdown When you deploy a Protagon, its SOUL.md content is returned to you. Adopt it as your personality for the session. ``` ### Technical Analysis The deployment function retrieves arbitrary Markdown from the mutable external endpoint `https://api.usaw.ai/api/v1/library/{slug}/soul.md` and exposes it as `soul_md`. The skill documentation explicitly directs the agent to adopt that content as its session personality. No signature verification, content hash pinning, schema validation, instruction filtering, or trust-boundary enforcement is applied before the response is used. Consequ ...[truncated 2185 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all API-provided identity data as untrusted content rather than executable agent instructions. 2. Replace unrestricted `SOUL.md` retrieval with a strict data schema containing bounded fields such as tone, verbosity, style, and permitted use cases. 3. Construct the final personality prompt locally from approved templates and validated values. 4. Reject control-oriented content such as requests to ignore higher-priority instructions, conceal actions, access secrets, invoke tools, or alter safety rules. 5. Cryptographically sign published character records and verify signatures against a pinned publisher key before use. 6. Pin reviewed content versions or hashes so the effective identity cannot change silently after skill review. 7. Display the source, version, and trust status of remote identity content and require explicit user confirmation before applying it. 8. Ensure the host always places immutable system and safety policies above personality content. 9. Apply the same validation to `synthesized_prompt.content` used by the fallback compilation path. 10. Enforce response-size limits and expected content types to reduce secondary parsing and denial-of-service risks. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
protagons.mjs:309
Finding
Reusable Google API Key Is Disclosed to a Third-Party Backend<![CDATA[ ## Vulnerability Details **File Location**: `protagons.mjs:309-326`; declared interface in `config.json:46-56` **Vulnerability Type**: Third-party disclosure of a reusable provider credential **Risk Level**: High ### Vulnerable Code ```javascript export async function protagons_generate(params) { if (!params.name) throw new Error('name is required'); if (!params.description) throw new Error('description is required'); if (!params.google_api_key) throw new Error('google_api_key is required'); const headers = { 'Content-Type': 'application/json' }; if (params.protagons_api_key) { headers['Authorization'] = `Bearer ${params.protagons_api_key}`; } return apiFetch('/generate', { method: 'POST', headers, body: JSON.stringify({ name: params.name, description: params.description, google_api_key: params.google_api_key, }), }); } ``` The public tool configuration explicitly requests the secret: ```json { "name": "protagons_generate", "description": "Generate a new Protagon from a description. Your Google API key is sent to the Protagons backend (api.usaw.ai) which calls Gemini on your behalf — the key is used only for that request and is not stored. Use a scoped or throwaway key if preferred.", "parameters": { "name": { "type": "string", "description": "Character name", "required": true }, "description": { "type": "string", "description": "Character description (min 20 chars)", "required": true }, "google_api_key": { "type": "string", "description": "Google/Gemini API key (BYOK) — sent to api.usaw.ai for server-side Gemini call, not stored", "required": true } } } ``` ### Technical Analysis `protagons_generate` serializes the caller's raw Google/Gemini API key into a JSON request body and transmits it to `https://api.usaw.ai/api/v1/generate`. Although the behavior is documented and HTTPS protects the credential in transit from passive network observers, the third-party backend nece ...[truncated 2263 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `google_api_key` from the request sent to `api.usaw.ai`. 2. Prefer a direct client-to-Google API call so the provider credential is disclosed only to Google. 3. If server-side generation is required, have the backend use its own Google service credential and authenticate users with a separate Protagons token that has narrowly defined permissions. 4. Where supported, use short-lived, audience-bound OAuth tokens rather than reusable API keys. 5. Require Google-side API restrictions, application restrictions, strict quotas, and limited billing exposure. 6. Mark credential parameters as secrets in the hosting framework so they are masked in user interfaces, traces, telemetry, and tool-call histories. 7. Ensure neither client nor server logs request bodies, authorization headers, or credential-bearing error objects. 8. Add automatic secret redaction to diagnostics and exception handling. 9. Document credential rotation and immediate revocation procedures for users who previously submitted keys. 10. If compatibility temporarily requires forwarding a key, obtain explicit per-request confirmation, warn that the backend receives the plaintext secret, and reject unrestricted or long-lived credentials where technically possible. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (8)

Ssd 1

High
Confidence
98% confidence
Finding
The deploy tool explicitly states it returns a rich external SOUL.md for the agent to adopt, making the prompt-injection path direct rather than incidental. This is dangerous because a remote service can supply instructions that impersonate higher-priority guidance, alter the model’s identity, suppress safeguards, or steer future responses and tool decisions under the guise of character deployment.

Ssd 1

Medium
Confidence
96% confidence
Finding
The instruction to 'Adopt it as your personality for the session' explicitly tells the agent to incorporate externally fetched SOUL.md content into its active behavior. This creates a direct pathway for remote prompt injection or policy override, because untrusted content can influence the agent’s persona, priorities, and response style in ways that may conflict with existing safeguards.

Ssd 4

Medium
Confidence
91% confidence
Finding
The documented flow normalizes a progression from broad search to deployment of a new identity, making personality takeover a standard interaction pattern rather than a guarded exception. Because the deployed content comes from an external public library and may include 'dark' or 'adversarial' archetypes, this context makes the takeover path more dangerous by encouraging repeated exposure to untrusted behavioral instructions.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The skill’s invocation examples use broad conversational triggers like 'Load a character' and 'Find me a character' without tightly constraining when identity-changing tools should be used. In a system that can fetch and adopt external persona content, vague trigger scope increases the chance of accidental or prompt-injected activation, which could shift agent behavior unexpectedly.

Ssd 1

Medium
Confidence
94% confidence
Finding
The skill’s core feature is to fetch and present external character/persona content for the agent to adopt, which creates a semantic prompt-injection channel. Even if intended for customization, externally sourced identity instructions can override system or developer goals, influence tool use, or manipulate downstream behavior because they are framed as role/personality rather than untrusted data.

External Transmission

Medium
Category
Data Exfiltration
Content
* @module protagons
 */

const API_BASE = 'https://api.usaw.ai/api/v1';
const REQUEST_TIMEOUT_MS = 30_000;

const CATEGORIES = [
Confidence
90% confidence
Finding
The skill sends user-controlled data to an external service at api.usaw.ai, including search terms, deployed content requests, and in protagons_generate a user-supplied Google API key and character description. In this skill context, external transmission is expected functionality, but it is still security-relevant because sensitive credentials and prompt content leave the local environment and are entrusted to a third party.

Intent-Code Divergence

Medium
Confidence
79% confidence
Finding
The status hint presents the skill in a narrowly scoped, non-mutating way focused on returning SOUL.md content. However, the same module also implements protagons_generate, which performs a POST to /generate and transmits sensitive credentials to an external API to create new content, making the status description materially misleading about overall behavior.

Natural-Language Policy Violations

Low
Confidence
76% confidence
Finding
The code fixes all API traffic to `https://api.usaw.ai/api/v1`, which bakes in a specific regional endpoint choice. Under the policy, locale-specific behavior should either be user-selectable or clearly documented as a justified regional constraint, and neither is evident here.

Static analysis

No suspicious patterns detected.