Is Fission-AI/OpenSpec safe?
- Node.js shell/command execution
- Node.js filesystem read
- Node.js filesystem write/delete
What to do: Nothing here argues against installing it. Grant the capabilities it lists only if you expect the tool to need them.
Fission-AI/OpenSpec is an npm package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators. It can: filesystem read, filesystem write, install time execution, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 0/100 (low).
@fission-ai/openspec 1.14.1
Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of Fission-AI/OpenSpec's authors. Report a false positive.
Behavioral traits
How this component maps to the CSA agentic threat model. Descriptive — it never affects the risk score.
Findings (5)
The component can run operating-system commands or spawn processes.
import { execFileSync } from 'child_process';import spawn from 'cross-spawn';
import type { ChildProcess, spawn as nodeSpawn } from 'node:child_process';import { execFileSync } from 'child_process';import type { spawn as nodeSpawn } from 'node:child_process';cachedSpawn = require('cross-spawn') as typeof nodeSpawn;import { execFile } from 'node:child_process';import { execFile } from 'node:child_process';cachedSpawn = require('cross-spawn') as typeof import('child_process').spawn;} from 'node:child_process';
import { execFileSync } from 'node:child_process';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 contentForTitle = await fs.readFile(proposalPath, 'utf-8');
const content = await fs.readFile(proposalPath, 'utf-8');
const deltaContent = await fs.readFile(deltaSpecPath, 'utf-8');
mainContent = await fs.readFile(mainSpecPath, 'utf-8');
const content = await fs.readFile(proposalPath, 'utf-8');
const content = await fs.readFile(proposalPath, 'utf-8');
const parsed: unknown = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
const rawConfig = fs.readFileSync(configPath, 'utf-8');
content = fs.readFileSync(schemaPath, 'utf-8');
const contents = fs.readFileSync(abs);
const originalContent = fs.readFileSync(configPath);
fs.readFileSync(prepared.path).equals(prepared.originalContent)
fs.readFileSync(path.join(trustedSourceDir, 'schema.yaml'), 'utf-8')
const schemaContent = fs.readFileSync(stagedSchemaPath, 'utf-8');
parseSchema(fs.readFileSync(stagedSchemaPath, 'utf-8'));
const contents = await fs.readFile(claimPath, 'utf8');
createHash('sha256').update(await fs.readFile(filePath)).digest('hex');.update(await fs.readFile(filePath))
.update(await fs.readFile(filePath))
content = await fs.readFile(update.target);
...(stat.isFile() ? { content: await fs.readFile(update.target) } : {}),(await fs.readFile(snapshot.target)).equals(snapshot.content);
const currentContent = await fs.readFile(snapshot.target);
const content = await fs.readFile(specFile, 'utf-8');
return fs.readFileSync(fullPath, '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.writeFileSync(resolved, buildCodeWorkspaceJson(workingSet, rootName));
fs.writeFileSync(stagedSchemaPath, doc.toString());
fs.rmSync(backupDir, { recursive: true, force: true });fs.rmSync(stagingDir, { recursive: true, force: true });fs.writeFileSync(
fs.writeFileSync(templatePath, createDefaultTemplate(artifact.id));
fs.writeFileSync(stagedConfigPath, preparedConfig.content);
fs.rmSync(preparedConfig.path, { force: true });fs.rmSync(schemaDir, { recursive: true, force: true });fs.rmSync(backup, { recursive: true, force: true });fs.rmSync(schemaStagingDir, { recursive: true, force: true });fs.rmSync(configStagingDir, { recursive: true, force: true });await fs.writeFile(readmePath, `# ${name}\n\n${options.description}\n`, 'utf-8');await fs.rm(claimed, { force: true });await fs.rm(dest, { recursive: true, force: true }).catch(() => undefined);await fs.rm(dest, { recursive: true, force: true }).catch(() => undefined);await fs.unlink(claimPath);
await fs.rm(snapshot.target, { force: true });await fs.writeFile(snapshot.target, snapshot.content!);
await fs.rm(referent, { force: true });await fs.writeFile(snapshot.target, snapshot.content);
await fs.writeFile(bashrcPath, lines.join('\n'), 'utf-8');await fs.writeFile(targetPath, completionScript, 'utf-8');
await fs.unlink(targetPath);
await fs.writeFile(targetPath, completionScript, 'utf-8');
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.
package.json has a 'prepare' script (runs on git/local installs and before publishing).
"prepare": "node build.js",
Why it matters: Usually a build step, but confirm it doesn't fetch or run remote code.
Fix: Usually a legitimate build step; confirm it only builds and does not fetch or execute remote code.
The component makes outbound network requests.
const response = await fetch(url, options);
return fetch(request);
const response = await fetch(upstream.toString(), init);
Why it matters: Usually legitimate, but confirm the destinations are expected and no sensitive data leaves.
Fix: Confirm the destination hosts are expected and that no sensitive data is sent off-host.
How attackers abuse these capabilities
Interactive labs on the attack class behind the rules above. They show the technique, not anything found in Fission-AI/OpenSpec.
Check your own component
Run the same evidence-backed scan on any MCP server, agent skill, or package.
Scan your own componentHow we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →