Back to skill

Security audit

frugal-subagents

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed cost-control plugin for Claude subagents that runs a local guard hook and bundled file-writing workers, with some implementation hardening caveats but no evidence of malicious behavior.

Before installing, understand that this plugin runs a local Node.js hook whenever Claude Code is about to spawn a subagent, changes missing subagent models to the configured cheaper default, may deny nested or over-budget spawns, and writes small state files in the OS temp directory. The bundled workers can also write research or extraction results to disk, so specify output paths and avoid delegating secrets or sensitive personal data unless you are comfortable storing the results locally.

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
hooks/guard.js:87
Finding

Non-Atomic Session Counter Allows Concurrent Spawn-Limit Bypass

Content
View full analysis
0 && state.count + 1 > max) { deny(`frugal-subagents: this session has already spawned ${max} subagents (FRUGAL_SUBAGENTS_MAX_SPAWNS). ` + 'Continue with the results you have, or send a follow-up to an agent that is still running ' + 'instead of starting a new one; if more spawns are genuinely needed, ask the user to raise the limit.'); } // Allowed: count it, then either pass through untouched or inject the default. if (state) { state.count += 1; } if (!defaulted) { saveState(state); process.exit(0); } const result = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', updatedInput: { ...input, model }, additionalContext: `frugal-subagents: this subagent runs on "${model}" because no model was named. ` + 'Name the model explicitly when a different tier is justified.', }, }; if (state && !state.notified) { state.notified = true; result.systemMessage = `frugal-subagents: subagents spawned without an explicit model run on "${model}" ` + '(FRUGAL_SUBAGENTS_DEFAULT_MODEL). Nested spawns are blocked; ' + `budget ${Number.isFinite(max) && max > 0 ? max : 'unlimited'} spawns per session.`; } saveState(state); out(result); ``` ### Technical Analysis The per-session spawn limit is implemented as a filesystem-backed counter. Each hook process independently: 1. Reads the current counter. 2. Checks whether the next spawn would exceed the limit. 3. Increments its in-m ...[truncated 1831 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
hooks/guard.js:39
Finding

Predictable Temporary State Files Permit Local State Manipulation and Symlink Attacks

Content
View full analysis
{ const dir = path.join(os.tmpdir(), 'frugal-subagents'); fs.mkdirSync(dir, { recursive: true }); // Best-effort sweep of state files from sessions older than a week. try { const cutoff = Date.now() - STATE_TTL_MS; for (const f of fs.readdirSync(dir)) { const p = path.join(dir, f); try { if (fs.statSync(p).mtimeMs < cutoff) fs.unlinkSync(p); } catch { /* ignore */ } } } catch { /* ignore */ } return path.join(dir, `${String(sessionId).replace(/[^A-Za-z0-9_-]/g, '_')}.json`); }; const loadState = (sessionId) => { if (!sessionId) return null; try { return { file: stateFile(sessionId), ...JSON.parse(fs.readFileSync(stateFile(sessionId), 'utf8')) }; } catch { try { return { file: stateFile(sessionId), count: 0, notified: false }; } catch { return null; } } }; const saveState = (state) => { if (!state) return; try { fs.writeFileSync(state.file, JSON.stringify({ count: state.count, notified: state.notified, updated: Date.now() })); } catch { /* ignore */ } }; ``` ### Technical Analysis Session state is placed in a fixed directory under the shared operating-system temporary directory: ```text /frugal-subagents/.json ``` The implementation does not explicitly establish restrictive permissions, validate directory or file ownership, reject symbolic links, or open files with exclusive/no-follow semantics. The state filename is also predictable when the session identifier is known or guessed. These properties create several local attack opportunities: - A local account can pre-create the shared directory before the p ...[truncated 2504 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
Every `Agent` call passes through `hooks/guard.js` before it runs. Nothing is silent: when the hook fills in a model it says so in the tool result, and a blocke

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

md
The hook is a Node.js script. If Node.js is not installed, Claude Code shows a hook error when a subagent is spawned (typically `node` not found / not recognized) and the spawn proceeds unguarded. When you see that:

1. Tell the user in one plain sentence: the plugin's guard needs Node.js, it isn't installed, so the limits aren't enforced yet — the guidance still applies and you'll follow it manually.
2. Offer to install Node.js LTS and wait for a yes. Use the platform's package manager: Windows `winget install OpenJS.NodeJS.LTS`; macOS `brew install node` (or the installer from nodejs.org if Homebrew is absent); Debian/Ubuntu via the NodeSource setup script or `sudo apt install nodejs npm`; otherwise point to nodejs.org. Don't install without consent, and don't use `sudo` without saying so.
3. After installation, the user starts a new Claude Code session; the guard is active from then on. Until then, apply this skill's rules yourself: cheap tier, no nesting, results in files.

## Companion settings (first-party, optional)

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
The hook is a Node.js script. If Node.js is not installed, Claude Code shows a hook error when a subagent is spawned (typically `node` not found / not recognized) and the spawn proceeds unguarded. When you see that:

1. Tell the user in one plain sentence: the plugin's guard needs Node.js, it isn't installed, so the limits aren't enforced yet — the guidance still applies and you'll follow it manually.
2. Offer to install Node.js LTS and wait for a yes. Use the platform's package manager: Windows `winget install OpenJS.NodeJS.LTS`; macOS `brew install node` (or the installer from nodejs.org if Homebrew is absent); Debian/Ubuntu via the NodeSource setup script or `sudo apt install nodejs npm`; otherwise point to nodejs.org. Don't install without consent, and don't use `sudo` without saying so.
3. After installation, the user starts a new Claude Code session; the guard is active from then on. Until then, apply this skill's rules yourself: cheap tier, no nesting, results in files.

## Companion settings (first-party, optional)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The description says to use the skill whenever the task is "turn these files into that shape," which is a very broad natural-language trigger for many ordinary file transformation requests. It does not provide explicit trigger phrases, exclusions beyond "Not for judgment calls," or negative examples to clearly bound when this skill should activate.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The README states that bundled agents save their full findings to files, but it does not clearly warn users that collected web research or extracted data will persist on disk. This can lead to unintentional storage of sensitive or regulated information, especially when users delegate broad searches over third-party sites and assume results remain only in transient chat context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The skill instructs the agent to write results to a path from the brief, or to create a new file next to the source by default, which affects user files on disk. The markdown description does not include a clear warning that the skill will create or modify files, which could impact user data or workspace integrity.

Content

No source excerpt is available for this finding.

Bundled hooks can execute when matching lifecycle events occur.

Low
Category
Bundled Execution Surface
Confidence
95% confidence
Finding

Bundled lifecycle hooks can run automatically when their configured events occur, so their reach and handler capability require review before installation.

Content

No source excerpt is available for this finding.