registry  /  @tokenfactory/acc-runner  /  0.37.3

@tokenfactory/acc-runner@0.37.3

Agent Control Center local runner. Spawns Claude Code sessions assigned via ACC.

AI Security Review

scanned 2h ago · by lpm-firewall-ai

LPM treats this as warn-only first-party agent extension lifecycle risk. When an operator runs `watch`, server-assigned work triggers a privileged Claude/Codex session in a repository worktree. The runner temporarily installs first-party MCP configuration containing task credentials and can autonomously replace its global npm package.

Static reason
One or more suspicious static signals were detected.
Trigger
Explicit `acc-runner watch` operation followed by a task assignment or self-upgrade check.
Impact
A compromised ACC service, task stream, or later npm release could direct high-privilege changes in configured repositories.
Mechanism
Remote task dispatch to permission-skipping agent execution with temporary MCP setup and automatic npm upgrade.
Rationale
Source establishes a high-risk but advertised remote agent runner with first-party MCP lifecycle setup and autonomous updating. No concrete malicious chain, install-time foreign control-surface mutation, or unrelated exfiltration was found.
Evidence
package.jsondist/task-runner.jsdist/mcp-spawn.jsdist/engines/claude-code.jsdist/runtime/self-upgrade.jsdist/secrets/inject.jsdist/keychain.js.mcp.json
Network endpoints3
agent-control-center-sepia.vercel.appctknjrtevjrzwpuojghx.supabase.coregistry.npmjs.org/@tokenfactory/acc-runner/latest

Decision evidence

public snapshot
AI called this Suspicious at 92.0% confidence as Dangerous Capability with low false-positive risk.
Evidence for warning
  • `dist/engines/claude-code.js` launches Claude with `--dangerously-skip-permissions`.
  • `dist/task-runner.js` writes token-bearing `.mcp.json` in an assigned task worktree before agent launch.
  • `dist/mcp-spawn.js` can configure `npx -y @tokenfactory/acc-mcp-server` for that MCP entry.
  • `dist/runtime/self-upgrade.js` fetches npm latest, globally installs it, then detached re-execs.
  • `dist/secrets/inject.js` obtains runtime secret plaintext from the configured ACC API for tool arguments.
Evidence against
  • `package.json` has no preinstall, install, postinstall, or prepare lifecycle hook.
  • CLI actions are invoked through the `acc-runner` bin; import-time execution is not established.
  • `dist/mcp-spawn.js` restores a prior `.mcp.json` or deletes the temporary file after use.
  • `dist/keychain.js` stores session tokens with OS keychain APIs rather than plaintext files.
  • No source evidence shows credential exfiltration to an unrelated endpoint or destructive behavior.
Behavioral surface
Source
ChildProcessCryptoDynamicRequireEnvironmentVarsFilesystemNetworkShellWebSocket
Supply chain
HighEntropyStringsUrlStrings
Manifest
NoLicense
scanned 52 file(s), 636 KB of source, external domains: agent-control-center-sepia.vercel.app, claude.com, cli.github.com, ctknjrtevjrzwpuojghx.supabase.co, git-scm.com, nodejs.org, registry.npmjs.org

Source & flagged code

7 flagged · loading source
dist/config.jsView file
74patternName = supabase_service_key severity = critical line = 74 matchedText = const DE...lQ";
Critical
Critical Secret

Package contains a critical-looking secret pattern.

dist/config.jsView on unpkg · L74
74patternName = supabase_service_key severity = critical line = 74 matchedText = const DE...lQ";
Critical
Secret Pattern

Supabase service role key (JWT) in dist/config.js

dist/config.jsView on unpkg · L74
dist/gh.jsView file
74chargeGithubCall(); L75: return exec(args, { cwd: opts.cwd }); L76: };
High
Child Process

Package source references child process execution.

dist/gh.jsView on unpkg · L74
dist/task-runner.jsView file
21import { fileURLToPath, pathToFileURL } from "node:url"; L22: import { execa } from "execa"; L23: import { canServeRepo, loadProfile as defaultLoadProfile } from "./config.js";
High
Shell

Package source references shell execution.

dist/task-runner.jsView on unpkg · L21
355} L356: const mod = (await import(/* @vite-ignore */ pathToFileURL(resolved).href)); L357: const def = mod.default;
Medium
Dynamic Require

Package source references dynamic require/import behavior.

dist/task-runner.jsView on unpkg · L355
dist/runtime/singleton.jsView file
41*/ L42: export function singletonRunnerId(env = process.env) { L43: return env.ACC_RUNNER_ID?.trim() || ""; ... L93: try { L94: const parsed = JSON.parse(raw); L95: if (typeof parsed.pid !== "number" || !Number.isFinite(parsed.pid)) ... L133: * same (canonical repo path, runner identity) pair. A stale lock (dead pid, L134: * our own pid, or a corrupt file) is reclaimed with a stderr breadcrumb and L135: * then re-acquired. A different `runnerId` keys a different lock file, so two ... L170: // Stale: dead holder, our own leftover pid, or unparseable. Reclaim. L171: process.stderr.write(`[acc-runner] reclaiming stale runner lock ${file} ` + L172: `(held by pid ${holder?.pid ?? "?"}, not alive)\n`);
Low
Weak Crypto

Package source references weak cryptographic algorithms.

dist/runtime/singleton.jsView on unpkg · L41
dist/login.jsView file
66try { L67: await execa(bin, ["--version"], { env: process.env }); L68: if (bin === "claude") { ... L81: async function postJson(url, body) { L82: const res = await fetch(url, { L83: method: "POST",
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/login.jsView on unpkg · L66

Findings

2 Critical3 High3 Medium6 Low
CriticalCritical Secretdist/config.js
CriticalSecret Patterndist/config.js
HighChild Processdist/gh.js
HighShelldist/task-runner.js
HighSame File Env Network Executiondist/login.js
MediumDynamic Requiredist/task-runner.js
MediumNetwork
MediumEnvironment Vars
LowScripts Present
LowWeak Cryptodist/runtime/singleton.js
LowFilesystem
LowHigh Entropy Strings
LowUrl Strings
LowNo License