registry  /  @swirls/cli  /  0.1.102

@swirls/cli@0.1.102

Swirls command line application

AI Security Review

scanned 2h ago · by lpm-firewall-ai

A global npm install triggers a lifecycle script that executes a mutable remote shell installer from swirls.ai. The script then replaces the package bin stub with a binary placed in ~/.local/bin by that remote installer.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
npm install -g @swirls/cli with lifecycle scripts enabled
Impact
Remote installer can execute arbitrary shell commands during package installation and determine the CLI binary copied into the package bin path.
Mechanism
install-time curl-to-bash remote code execution
Attack narrative
On global install, npm runs package.json postinstall, which executes scripts/install.js. That script invokes curl against https://swirls.ai/install and pipes the response directly to bash with shell:true, then copies ~/.local/bin/swirls over bin/swirls. This gives a mutable remote endpoint install-time code execution and control over the installed CLI binary.
Rationale
Direct source inspection confirms unpinned remote shell execution from an npm lifecycle hook, which is concrete install-time remote code execution even though the endpoint is package-aligned and guarded to global installs. No additional exfiltration or AI-agent hijack behavior was found in the package files.
Evidence
package.jsonscripts/install.jsbin/swirlsREADME.md~/.local/bin/swirls
Network endpoints2
swirls.ai/installswirls.ai

Decision evidence

public snapshot
AI called this Malicious at 92.0% confidence as Dangerous Capability with low false-positive risk.
Evidence for warning
  • package.json defines postinstall: node scripts/install.js
  • scripts/install.js runs execSync with shell:true on curl -fsSL https://swirls.ai/install | bash
  • postinstall copies ~/.local/bin/swirls into package bin/swirls after remote script runs
  • bin/swirls tells users to run curl -fsSL https://swirls.ai/install | bash if postinstall was skipped
Evidence against
  • scripts/install.js skips CI, Windows, and non-global installs
  • No source evidence of credential harvesting, AI-agent control-surface writes, persistence hooks, or destructive file operations
  • Network endpoint is package-aligned: swirls.ai
Behavioral surface
Source
ChildProcessEnvironmentVarsFilesystemShell
Supply chain
UrlStrings
Manifest
NoLicense
scanned 1 file(s), 1.35 KB of source, external domains: swirls.ai

Source & flagged code

7 flagged · loading source
package.jsonView file
scripts.postinstall = node scripts/install.js
High
Install Time Lifecycle Scripts

Package defines install-time lifecycle scripts.

package.jsonView on unpkg
scripts.postinstall = node scripts/install.js
Medium
Ambiguous Install Lifecycle Script

Install-time lifecycle script is not statically allowlisted and needs review.

package.jsonView on unpkg
scripts/install.jsView file
13L14: import { execSync } from 'node:child_process' L15: import { chmodSync, copyFileSync, existsSync } from 'node:fs' ... L18: L19: const INSTALL_URL = 'https://swirls.ai/install' L20: ... L28: const INSTALLED_BINARY = join( L29: process.env.HOME || process.env.USERPROFILE || '', L30: '.local', ... L36: L37: if (process.env.CI === 'true' || process.env.CI === '1') { L38: process.exit(0)
Critical
Download Execute

Source downloads or fetches remote code and executes it.

scripts/install.jsView on unpkg · L13
13Trigger-reachable chain: scripts.postinstall -> scripts/install.js L13: L14: import { execSync } from 'node:child_process' L15: import { chmodSync, copyFileSync, existsSync } from 'node:fs' ... L18: L19: const INSTALL_URL = 'https://swirls.ai/install' L20: ... L28: const INSTALLED_BINARY = join( L29: process.env.HOME || process.env.USERPROFILE || '', L30: '.local', ... L36: L37: if (process.env.CI === 'true' || process.env.CI === '1') { L38: process.exit(0)
Critical
Trigger Reachable Dangerous Capability

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

scripts/install.jsView on unpkg · L13
13L14: import { execSync } from 'node:child_process' L15: import { chmodSync, copyFileSync, existsSync } from 'node:fs'
High
Child Process

Package source references child process execution.

scripts/install.jsView on unpkg · L13
49stdio: 'inherit', L50: shell: true, L51: })
High
Shell

Package source references shell execution.

scripts/install.jsView on unpkg · L49
13L14: import { execSync } from 'node:child_process' L15: import { chmodSync, copyFileSync, existsSync } from 'node:fs' ... L18: L19: const INSTALL_URL = 'https://swirls.ai/install' L20: ... L28: const INSTALLED_BINARY = join( L29: process.env.HOME || process.env.USERPROFILE || '', L30: '.local', ... L36: L37: if (process.env.CI === 'true' || process.env.CI === '1') { L38: process.exit(0)
High
Sandbox Evasion Gated Capability

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

scripts/install.jsView on unpkg · L13

Findings

2 Critical4 High3 Medium4 Low
CriticalDownload Executescripts/install.js
CriticalTrigger Reachable Dangerous Capabilityscripts/install.js
HighInstall Time Lifecycle Scriptspackage.json
HighChild Processscripts/install.js
HighShellscripts/install.js
HighSandbox Evasion Gated Capabilityscripts/install.js
MediumAmbiguous Install Lifecycle Scriptpackage.json
MediumEnvironment Vars
MediumStructural Risk Force Deep Review
LowScripts Present
LowFilesystem
LowUrl Strings
LowNo License