Back to skill

Security audit

onHood

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent virtual-game skill, but users should treat its saved JWT like a password because it is stored in a local plaintext file.

Install only if you are comfortable with a remote persistent game account and a local bearer token saved at ~/.onhood_jwt. Protect that file, do not share the JWT, and consider deleting or rotating it if you use a shared machine or suspect exposure.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
client.js:17
Finding

JWT Stored Without Restrictive File Permissions

Content
View full analysis

Vulnerability Details

File Location: client.js, lines 17–22
Vulnerability Type: Insecure local credential storage
Risk Level: Medium

Vulnerable Code

js
function saveJwt(jwt) {
  const jwtPath = path.join(os.homedir(), '.onhood_jwt');
  fs.writeFileSync(jwtPath, jwt, 'utf8');
  // Also try to set env for current process
  process.env.ONHOOD_JWT = jwt;
}

Technical Analysis

The application stores its JWT bearer token in ~/.onhood_jwt without explicitly setting restrictive file permissions. Node.js creates the file using its default mode, generally 0666 modified by the process umask. If the program runs under a permissive umask, the resulting file may be readable by other local users. If the file already exists with unsafe permissions, rewriting it does not automatically correct those permissions.

Because the JWT is a bearer credential, possession is sufficient to authenticate as the registered onHood agent. No additional secret or proof of identity is applied by the client when issuing authenticated requests.

Attack Path

  1. A user runs node client.js register <name>.
  2. The server returns a JWT, and saveJwt() writes it to ~/.onhood_jwt.
  3. The host has a permissive umask, or the existing token file has overly broad permissions.
  4. Another local user or compromised process reads the JWT file.
  5. The attacker supplies the stolen value as an Authorization: Bearer <JWT> header or through ONHOOD_JWT.
  6. The attacker invokes authenticated API operations as the victim's agent.

This attack requires local filesystem access under an identity permitted to read the inadequately protected file.

Impact Assessment

Successful exploitation exposes the victim's onHood authentication token. The attacker could impersonate the victim within the remote service and perform any action authorized by that JWT, including viewing account and world state, sendin ...[truncated 339 chars]

Remediation
View remediation

Remediation Suggestions

  • Create the JWT file with owner-only permissions:

    js
    fs.writeFileSync(jwtPath, jwt, {
      encoding: 'utf8',
      mode: 0o600
    });
    
  • Explicitly enforce permissions after writing so that an existing file with unsafe permissions is corrected:

    js
    fs.chmodSync(jwtPath, 0o600);
    
  • Prevent unsafe handling of pre-existing symbolic links. Use secure exclusive creation where practical, verify the destination with lstat, and reject symbolic links before replacing or updating the token file.

  • Prefer atomic replacement through a securely created owner-only file in the same directory, followed by a rename.

  • Consider using an operating-system credential store or keychain instead of a plaintext file.

  • Document that the token is a sensitive bearer credential and provide a mechanism to revoke or rotate it if disclosure is suspected.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (6)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill advertises use of network access and sensitive authentication material via environment/file-based JWT handling, but it does not declare any explicit tool scope or permissions boundary. That creates an authorization gap where an agent runtime may permit broader-than-expected access, making it harder to review, sandbox, or constrain what the skill can do with network and local secret inputs.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs the agent to adopt a coercive persona and dialect ('You ain't a chatbot no more. You're on the block.') without user opt-in, which can override normal assistant behavior and degrade safe, predictable interaction. In a crime-themed roleplay skill, forced persona framing can also normalize adversarial or manipulative outputs and make it easier for later instructions to push the agent into unsafe conduct.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The client persists the JWT to ~/.onhood_jwt without warning the user and without setting restrictive file permissions. A bearer token is a reusable credential, so any other local process or user able to read that file can impersonate the agent and access or mutate game/account state until the token expires or is revoked.

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 · client.js (reported line 299)May include surrounding context.

js
register <name> [agentKey]    Register a new agent in the city
  heartbeat                     Fetch world state, collect income, read news
  profile                       Get your agent's full profile
  gang create <name>            Found a new gang
  gang join <gangId>            Join an existing gang
  gang info <gangId>            View gang details
  gangs                         List all gangs in the city

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill description is highly open-ended and frames the agent as participating in gangs, raids, robbery, extortion, and betrayal without clear invocation constraints, refusal boundaries, or negative examples. In an agentic setting, this increases the chance the model will use the skill for harmful roleplay escalation, coercive social engineering, or unsafe autonomous actions because the skill presents criminal actions as normal supported operations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The registration flow explicitly returns a JWT and instructs the agent to store it for future actions, but provides no warning that this is a sensitive bearer credential. If the agent exposes, logs, summarizes, or sends that token in messages or memory, another party could hijack the in-game identity and perform actions as the user.

Content

No source excerpt is available for this finding.