registry  /  loomctl  /  0.18.0

loomctl@0.18.0

Orchestrate a fleet of real Claude Code agents on your Claude subscription (Pro/Max), not per-token API bills — durable sessions, multi-agent orchestration, all local-first on your own machine.

AI Security Review

scanned 17h 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
One or more suspicious static signals were detected.; previous stored version diff introduced dangerous source
Trigger
User runs Loom and starts or resumes an orchestrated Claude session.
Impact
Claude may treat a Loom-managed project as trusted and load Loom-provided project skills without its usual onboarding prompt.
Mechanism
Runtime Claude configuration mutation and PTY-based agent orchestration.
Rationale
The static malicious label is not supported by the inspected install path: there are no lifecycle hooks or unattended install-time mutations. The package does perform user-invoked, security-relevant Claude configuration changes, so it warrants a warning rather than a clean or blocking verdict.
Evidence
package.jsonbin/loom.mjsbin/service.mjsdist/pty/host.jsdist/pty/claude-config.jsdist/skills/inject.js~/.claude.json<cwd>/.claude/skillsdist/index.js
Network endpoints1
127.0.0.1:<port>

Decision evidence

public snapshot
AI called this Suspicious at 90.0% confidence as Dangerous Capability with low false-positive risk.
Evidence for warning
  • `dist/pty/claude-config.js` atomically writes `~/.claude.json` trust/onboarding flags.
  • `dist/pty/host.js` calls `ensureTrusted(opts.cwd)` before spawning Claude.
  • `dist/skills/inject.js` mirrors Loom skills into `<cwd>/.claude/skills`.
  • `bin/service.mjs` installs user-login persistence only through explicit `loom service install`.
  • `dist/pty/host.js` launches Claude through a PTY for orchestrated agent sessions.
Evidence against
  • `package.json` has no `preinstall`, `install`, `postinstall`, or other lifecycle hook.
  • CLI behavior is user-invoked through `bin/loom.mjs`; no import-time daemon start found.
  • CLI HTTP probes target loopback `127.0.0.1`, not a hard-coded remote collector.
  • Reviewed config writes do not add external MCP servers or exfiltrate credentials.
  • Runtime package-manager use in `dist/git/worktrees.js` provisions user worktrees, not this package's install path.
Behavioral surface
Source
ChildProcessCryptoDynamicRequireEnvironmentVarsFilesystemNetworkShellWebSocket
Supply chain
HighEntropyStringsMinifiedObfuscatedUrlStrings
ManifestNo manifest risk signals triggered.
scanned 144 file(s), 4.27 MB of source, external domains: 127.0.0.1, accounts.google.com, api.anthropic.com, api.telegram.org, github.com, oauth2.googleapis.com, react.dev, registry.npmjs.org, schemas.microsoft.com, www.w3.org

Source & flagged code

9 flagged · loading source
bin/loom.mjsView file
22import http from "node:http"; L23: import { spawn, spawnSync } from "node:child_process"; L24: import { CHANNELS, isValidChannel, installSpecFor, readChannel, writeChannel } from "./update-config.mjs";
High
Child Process

Package source references child process execution.

bin/loom.mjsView on unpkg · L22
449// no shell-injection surface. L450: const r = spawnSync("npm", ["i", "-g", spec], { stdio: "inherit", shell: true }); L451: if (r.error || r.status !== 0) {
High
Shell

Package source references shell execution.

bin/loom.mjsView on unpkg · L449
21import fs from "node:fs"; L22: import http from "node:http"; L23: import { spawn, spawnSync } from "node:child_process"; L24: import { CHANNELS, isValidChannel, installSpecFor, readChannel, writeChannel } from "./update-config.mjs"; ... L33: // to resize it after the fact). Never overrides an operator's own explicit setting. L34: if (!process.env.UV_THREADPOOL_SIZE) process.env.UV_THREADPOOL_SIZE = "16"; L35:
High
Same File Env Network Execution

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

bin/loom.mjsView on unpkg · L21
270// readiness. L271: await import(pathToFileURL(daemonEntry).href); L272:
Medium
Dynamic Require

Package source references dynamic require/import behavior.

bin/loom.mjsView on unpkg · L270
dist/mcp/repo-read.jsView file
62for (let i = 0; i < s.length; i++) L63: if (s.charCodeAt(i) === 0) L64: return true;
Medium
Unsafe Vm Context

Package source executes code through a VM context API.

dist/mcp/repo-read.jsView on unpkg · L62
bin/service.mjsView file
10// - macOS : a launchd LaunchAgent plist (RunAtLoad + KeepAlive) under ~/Library/LaunchAgents, L11: // loaded via `launchctl`. L12: // - Windows: a Task Scheduler logon task (schtasks /create /xml) running at logon — no admin / no ... L22: import path from "node:path"; L23: import { spawnSync } from "node:child_process"; L24: ... L74: // launchd LaunchAgent plist. RunAtLoad starts it when the agent is loaded at login; KeepAlive restarts L75: // it if it exits (the keep-alive). Stdout/stderr go to a log under LOOM_HOME so a background boot stays L76: // debuggable. ... L118: return `<?xml version="1.0" encoding="UTF-16"?> L119: <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> L120: <RegistrationInfo>
Medium
Install Persistence

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

bin/service.mjsView on unpkg · L10
dist/git/worktrees.jsView file
92/** L93: * Run a BOUNDED, NON-INTERACTIVE `pnpm install --frozen-lockfile --prefer-offline` in `worktreePath`, L94: * killing the child if it exceeds `timeoutMs`. ASYNC (child_process.spawn, NOT spawnSync) on purpose: L95: * createWorktree is awaited on the worker-spawn hot path, and a synchronous spawnSync would freeze the
High
Runtime Package Install

Package source invokes a package manager install command at runtime.

dist/git/worktrees.jsView on unpkg · L92
assets/python/synthesize.pyView file
path = assets/python/synthesize.py kind = build_helper sizeBytes = 11311 magicHex = [redacted]
Medium
Ships Build Helper

Package ships non-JavaScript build or shell helper files.

assets/python/synthesize.pyView on unpkg
dist/pty/host.jsView file
matchType = previous_version_dangerous_delta matchedPackage = loomctl@0.17.0 matchedIdentity = npm:bG9vbWN0bA:0.17.0 similarity = 0.583 summary = stored previous version shares package body but lacks this dangerous source file
Critical
Previous Version Dangerous Delta

This package version adds a dangerous source file absent from the previous stored version; route for source-aware review.

dist/pty/host.jsView on unpkg

Findings

1 Critical4 High7 Medium4 Low
CriticalPrevious Version Dangerous Deltadist/pty/host.js
HighChild Processbin/loom.mjs
HighShellbin/loom.mjs
HighSame File Env Network Executionbin/loom.mjs
HighRuntime Package Installdist/git/worktrees.js
MediumDynamic Requirebin/loom.mjs
MediumUnsafe Vm Contextdist/mcp/repo-read.js
MediumNetwork
MediumEnvironment Vars
MediumInstall Persistencebin/service.mjs
MediumShips Build Helperassets/python/synthesize.py
MediumStructural Risk Force Deep Review
LowFilesystem
LowObfuscated
LowHigh Entropy Strings
LowUrl Strings