Is obra/superpowers safe?
superpowers is an AI npm_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 4 risky constructs are reported for review. It can: filesystem read, filesystem write and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 20/100 (low).
superpowers 6.1.0
- Node.js shell/command execution
- Possible command injection (exec with dynamic command)
- Node.js filesystem read
No malicious indicators found by static analysis.
Findings (4)
The code builds an OS command out of values that can change at runtime, then runs it through a shell.
try { cp.exec(process.env.BRAINSTORM_OPEN_CMD + ' ' + JSON.stringify(url), () => {}); } catch (e) { /* best effort */ }Why it matters: If any of those values come from untrusted input, an attacker can run their own commands on the machine.
Fix: Use execFile/spawn with an argument array instead of exec; never build a shell command string from external input.
The component can run operating-system commands or spawn processes.
const cp = require('child_process');try { cp.exec(process.env.BRAINSTORM_OPEN_CMD + ' ' + JSON.stringify(url), () => {}); } catch (e) { /* best effort */ }const { execSync } = require('child_process');while ((match = regex.exec(markdown)) !== null) {return execSync('dot -Tsvg', {execSync('which dot', { encoding: 'utf-8' });Why it matters: Powerful and often legitimate — confirm the commands aren't built from untrusted input.
Fix: Confirm the command and its arguments are fully controlled and not derived from untrusted input; prefer execFile with an argument array.
The component reads files from disk.
const fullContent = fs.readFileSync(skillPath, 'utf8');
const p = Number(fs.readFileSync(PORT_FILE, 'utf-8').trim());
const t = fs.readFileSync(TOKEN_FILE, 'utf-8').trim();
const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8');
const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
const data = JSON.parse(fs.readFileSync(manifest, 'utf-8'));
? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8'))
res.end(fs.readFileSync(filePath));
const markdown = fs.readFileSync(skillFile, 'utf-8');
Why it matters: Usually legitimate, but worth confirming it can't be steered into reading sensitive files.
Fix: Confirm which files are read and that paths cannot be influenced by untrusted input to reach sensitive locations.
The component writes or deletes files on disk.
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
fs.writeFileSync(
try { fs.writeFileSync(PORT_FILE, String(PORT)); } catch (e) { /* best effort */ }fs.writeFileSync(TOKEN_FILE, TOKEN, { mode: 0o600 });fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n', { mode: 0o600 });fs.writeFileSync(outputPath, svg);
fs.writeFileSync(dotPath, combined);
fs.writeFileSync(outputPath, svg);
Why it matters: Usually legitimate, but worth confirming the paths can't be controlled by untrusted input.
Fix: Confirm which files are written/deleted and that paths cannot be influenced by untrusted input.
Check your own component
Run the same evidence-backed scan on any MCP server, agent skill, or package.
Scan your own componentOr get notified if this component's risk changes:
How we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →