registry  /  doable-cli  /  0.1.5

doable-cli@0.1.5

Official CLI for Doable — deploy projects, manage domains and addons, stream logs.

AI Security Review

scanned 2h ago · by lpm-firewall-ai

Review flagged AI-agent configuration or capability changes. This remains warn-only unless evidence shows foreign-agent hijack through preinstall/install/postinstall, hidden persistence, exfiltration, remote code execution, or other concrete malicious behavior.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
User runs `doable setup` or a successful `doable deploy`.
Impact
A user-approved command can add a Doable MCP server entry that later launches `doable-mcp@latest` through npx; deploy can update agent instruction files.
Mechanism
Explicit project AI-agent/MCP configuration and deployment metadata writes.
Rationale
No concrete malicious install-time or covert execution chain was found. Warn because explicit agent configuration and generated AI guidance can extend an AI coding environment through a floating npx dependency.
Evidence
package.jsondist/index.jsREADME.md.mcp.json.cursor/mcp.jsonCLAUDE.mdAGENTS.mdGEMINI.md~/.doable/config.json
Network endpoints2
api.doable.dodoable.do

Decision evidence

public snapshot
AI called this Suspicious at 86.0% confidence as Dangerous Capability with low false-positive risk.
Evidence for warning
  • `dist/index.js` exposes an explicit `doable setup` command that writes `.mcp.json` and `.cursor/mcp.json`.
  • The written MCP configuration invokes `npx -y doable-mcp@latest`, creating a project AI-tool extension lifecycle.
  • Successful deploy flows write managed blocks into `CLAUDE.md`, `AGENTS.md`, and `GEMINI.md`.
  • The CLI reads provider tokens only in explicit server/backup commands and sends authenticated requests to its configured API.
  • `dist/index.js` supports remote project shell/exec endpoints after user authentication.
Evidence against
  • `package.json` has only `prepublishOnly`; no install-time lifecycle hook runs for consumers.
  • The executable is a user-invoked `doable` CLI; import/install does not execute commands.
  • No environment enumeration, shell-profile persistence, dynamic eval, or hidden payload loader was found.
  • `spawn` usage is limited to opening URLs and explicit `doable upgrade --install` npm invocation.
  • Deployment archives explicitly exclude `.env` and related files before upload.
  • AI configuration mutations are confined to explicit setup/deploy commands and project-local files.
Behavioral surface
Source
ChildProcessCryptoEnvironmentVarsFilesystemNetworkShellWebSocket
Supply chain
HighEntropyStringsUrlStrings
ManifestNo manifest risk signals triggered.
scanned 1 file(s), 336 KB of source, external domains: api.doable.do, cloud.digitalocean.com, cloud.linode.com, console.hetzner.cloud, console.neon.tech, console.upstash.com, doable.do, github.com, my.vultr.com, railway.app, registry.npmjs.org, s3.us-east-1.amazonaws.com, supabase.com, www.mongodb.com, www.namecheap.com

Source & flagged code

7 flagged · loading source
dist/index.jsView file
19import qrcode from 'qrcode-terminal'; L20: import { spawn } from 'child_process'; L21: import readline from 'readline'; L22: import WebSocket2 from 'ws'; L23: L24: var FLAG = process.env.DOABLE_TELEMETRY?.toLowerCase(); L25: var DISABLED = FLAG === "0" || FLAG === "false" || FLAG === "off" || FLAG === "no"; ... L32: const candidates = [ L33: path7__default.join(here, "..", "package.json"), L34: path7__default.join(here, "..", "..", "package.json") ... L38: const raw = await readFile(c, "utf8"); L39: const pkg = JSON.parse(raw);
Critical
Credential Exfiltration

Source appears to send environment or credential material to an external endpoint.

dist/index.jsView on unpkg · L19
19Trigger-reachable chain: manifest.bin -> dist/index.js L19: import qrcode from 'qrcode-terminal'; L20: import { spawn } from 'child_process'; L21: import readline from 'readline'; L22: import WebSocket2 from 'ws'; L23: L24: var FLAG = process.env.DOABLE_TELEMETRY?.toLowerCase(); L25: var DISABLED = FLAG === "0" || FLAG === "false" || FLAG === "off" || FLAG === "no"; ... L32: const candidates = [ L33: path7__default.join(here, "..", "package.json"), L34: path7__default.join(here, "..", "..", "package.json") ... L38: const raw = await readFile(c, "utf8"); L39: const pkg = JSON.parse(raw);
Critical
Trigger Reachable Dangerous Capability

A package entrypoint or install-time lifecycle script reaches a source file with blocking dangerous behavior.

dist/index.jsView on unpkg · L19
19import qrcode from 'qrcode-terminal'; L20: import { spawn } from 'child_process'; L21: import readline from 'readline';
High
Child Process

Package source references child process execution.

dist/index.jsView on unpkg · L19
9711}); L9712: cron.command("add <project>").description("Add a new cron job").requiredOption("-n, --name <name>", "Display name").requiredOption("-c, --command <cmd>", "Shell command to run via ... L9713: async (project, opts) => {
High
Shell

Package source references shell execution.

dist/index.jsView on unpkg · L9711
19import qrcode from 'qrcode-terminal'; L20: import { spawn } from 'child_process'; L21: import readline from 'readline'; L22: import WebSocket2 from 'ws'; L23: L24: var FLAG = process.env.DOABLE_TELEMETRY?.toLowerCase(); L25: var DISABLED = FLAG === "0" || FLAG === "false" || FLAG === "off" || FLAG === "no";
High
Same File Env Network Execution

A single source file combines environment access, network access, and code or shell execution; review context before blocking.

dist/index.jsView on unpkg · L19
19import qrcode from 'qrcode-terminal'; L20: import { spawn } from 'child_process'; L21: import readline from 'readline'; L22: import WebSocket2 from 'ws'; L23: L24: var FLAG = process.env.DOABLE_TELEMETRY?.toLowerCase(); L25: var DISABLED = FLAG === "0" || FLAG === "false" || FLAG === "off" || FLAG === "no"; ... L32: const candidates = [ L33: path7__default.join(here, "..", "package.json"), L34: path7__default.join(here, "..", "..", "package.json") ... L38: const raw = await readFile(c, "utf8"); L39: const pkg = JSON.parse(raw);
High
Sandbox Evasion Gated Capability

Source gates dangerous network, credential, or execution behavior behind CI, host, platform, time, or geo fingerprint checks.

dist/index.jsView on unpkg · L19
19import qrcode from 'qrcode-terminal'; L20: import { spawn } from 'child_process'; L21: import readline from 'readline'; L22: import WebSocket2 from 'ws'; L23: L24: var FLAG = process.env.DOABLE_TELEMETRY?.toLowerCase(); L25: var DISABLED = FLAG === "0" || FLAG === "false" || FLAG === "off" || FLAG === "no"; ... L32: const candidates = [ L33: path7__default.join(here, "..", "package.json"), L34: path7__default.join(here, "..", "..", "package.json") ... L38: const raw = await readFile(c, "utf8"); L39: const pkg = JSON.parse(raw);
Medium
Install Persistence

Source writes installer persistence such as shell profile or service configuration.

dist/index.jsView on unpkg · L19

Findings

2 Critical4 High4 Medium5 Low
CriticalCredential Exfiltrationdist/index.js
CriticalTrigger Reachable Dangerous Capabilitydist/index.js
HighChild Processdist/index.js
HighShelldist/index.js
HighSame File Env Network Executiondist/index.js
HighSandbox Evasion Gated Capabilitydist/index.js
MediumNetwork
MediumEnvironment Vars
MediumInstall Persistencedist/index.js
MediumStructural Risk Force Deep Review
LowNon Install Lifecycle Scripts
LowScripts Present
LowFilesystem
LowHigh Entropy Strings
LowUrl Strings