registry  /  @dv.nghiem/flowdeck  /  0.6.1

@dv.nghiem/flowdeck@0.6.1

FlowDeck — structured planning and execution workflows for OpenCode

AI Security Review

scanned 2h ago · by lpm-firewall-ai

LPM treats this as warn-only first-party agent extension lifecycle risk. Npm installation automatically mutates the user's OpenCode configuration. Because this package omits `crates/fdx`, its postinstall clones the package repository and may compile/install its current remote contents.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
`npm install @dv.nghiem/flowdeck` with no `fdx` on PATH
Impact
A mutable package-aligned remote source can execute build code during installation and the package becomes active in OpenCode.
Mechanism
postinstall self-registration plus unpinned repository clone and Cargo build
Rationale
The source establishes a real postinstall supply-chain and agent-extension lifecycle risk, but not a concrete malicious objective. It should be warned rather than blocked.
Evidence
package.jsonpostinstall.mjsbin/flowdeck.jsdist/index.jsdocs/configuration/opencode-settings.md~/.local/share/flowdeck~/.config/opencode/opencode.json
Network endpoints3
github.com/DVNghiem/FlowDeck.gitwin.rustup.rs/x86_64sh.rustup.rs

Decision evidence

public snapshot
AI called this Suspicious at 90.0% confidence as Dangerous Capability with low false-positive risk.
Evidence for warning
  • `package.json` runs `postinstall.mjs` on npm install.
  • `postinstall.mjs` clones `https://github.com/DVNghiem/FlowDeck.git` when bundled `crates/fdx` is absent.
  • The installer runs `cargo install --path .` against the cloned repository during install.
  • The installer writes `~/.config/opencode/opencode.json` (or configured equivalent), adding its plugin and default agent.
Evidence against
  • No credential or secret collection found in inspected installer/runtime paths.
  • No exfiltration endpoint or hidden payload source found.
  • Runtime filesystem tools appear project-scoped workflow, audit, planning, and editing features.
  • The CLI documents equivalent explicit plugin configuration and uninstall behavior.
Behavioral surface
Source
ChildProcessCryptoEnvironmentVarsFilesystemNetworkShell
Supply chain
HighEntropyStringsUrlStrings
ManifestNo manifest risk signals triggered.
scanned 3 file(s), 408 KB of source, external domains: api.githubcopilot.com, expressjs.com, github.com, mcp.context7.com, mcp.exa.ai, mcp.grep.app, opencode.ai, rustup.rs, sh.rustup.rs, win.rustup.rs, zod.dev

Source & flagged code

13 flagged · loading source
package.jsonView file
scripts.postinstall = node postinstall.mjs
High
Install Time Lifecycle Scripts

Package defines install-time lifecycle scripts.

package.jsonView on unpkg
scripts.postinstall = node postinstall.mjs
Medium
Ambiguous Install Lifecycle Script

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

package.jsonView on unpkg
dist/index.jsView file
7466import { fileURLToPath } from "url"; L7467: import { execFileSync } from "node:child_process"; L7468:
High
Child Process

Package source references child process execution.

dist/index.jsView on unpkg · L7466
1044patternName = generic_password severity = medium line = 1044 matchedText = const in...' };
Medium
Secret Pattern

Package contains a possible secret pattern.

dist/index.jsView on unpkg · L1044
1101npx jest # jest L1102: npm test # package.json test script L1103: \`\`\` ... L1196: // ✅ OK L1197: const API_KEY = process.env.API_KEY; L1198: \`\`\` ... L1386: ✅ Correct citation format: L1387: - \`express@4.18\` — \`res.json()\` automatically sets Content-Type to application/json L1388: Source: https://expressjs.com/en/api.html#res.json L1389: ... L4345: function getGlobalConfigDir() { L4346: return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join2(process.env.XDG_CONFIG_HOME, "opencode") : join2(homedir(), ".config", "opencode"));
Medium
Install Persistence

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

dist/index.jsView on unpkg · L1101
1101npx jest # jest L1102: npm test # package.json test script L1103: \`\`\` ... L1196: // ✅ OK L1197: const API_KEY = process.env.API_KEY; L1198: \`\`\` ... L1386: ✅ Correct citation format: L1387: - \`express@4.18\` — \`res.json()\` automatically sets Content-Type to application/json L1388: Source: https://expressjs.com/en/api.html#res.json L1389: ... L4345: function getGlobalConfigDir() { L4346: return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join2(process.env.XDG_CONFIG_HOME, "opencode") : join2(homedir(), ".config", "opencode"));
Low
Weak Crypto

Package source references weak cryptographic algorithms.

dist/index.jsView on unpkg · L1101
postinstall.mjsView file
1Install-time AI-agent control hijack evidence: L4: L5: import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, createWriteStream } from "node:fs"; L6: import { join, dirname } from "node:path"; ... L53: return ( L54: process.env.OPENCODE_CONFIG_DIR || L55: (process.env.XDG_CONFIG_HOME ... L85: try { L86: mkdirSync(dirname(FLOWDECK_INSTALL_DIR), { recursive: true }); L87: execSync(`git clone --depth 1 --quiet "${FLOWDECK_REPO_URL}" "${FLOWDECK_INSTALL_DIR}"`, { ... L315: L316: mkdirSync(configDir, { recursive: true }); L317: Payload evidence from src/skills/plan-task/SKILL.md: L1: --- L2: name: plan-task
Critical
Ai Agent Control Hijack

Install-time source drops package-supplied AI-agent/MCP control files or instructions.

postinstall.mjsView on unpkg · L1
241stdio: ["pipe", "pipe", "pipe"], L242: shell: true, L243: timeout: 300_000,
High
Shell

Package source references shell execution.

postinstall.mjsView on unpkg · L241
8import { fileURLToPath } from "node:url"; L9: import { execSync, execFileSync } from "node:child_process"; L10: import { get as httpsGet } from "node:https"; L11: import { createInterface } from "node:readline"; ... L16: const FLOWDECK_REPO_URL = "https://github.com/DVNghiem/FlowDeck.git"; L17: const FLOWDECK_INSTALL_DIR = process.env.FLOWDECK_INSTALL_DIR || join(homedir(), ".local", "share", "flowdeck"); L18:
High
Same File Env Network Execution

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

postinstall.mjsView on unpkg · L8
8import { fileURLToPath } from "node:url"; L9: import { execSync, execFileSync } from "node:child_process"; L10: import { get as httpsGet } from "node:https"; L11: import { createInterface } from "node:readline"; L12: L13: const __dirname = dirname(fileURLToPath(import.meta.url)); L14: ... L16: const FLOWDECK_REPO_URL = "https://github.com/DVNghiem/FlowDeck.git"; L17: const FLOWDECK_INSTALL_DIR = process.env.FLOWDECK_INSTALL_DIR || join(homedir(), ".local", "share", "flowdeck"); L18: ... L93: } catch (err) { L94: console.warn("⚠️ Failed to clone FlowDeck repo:", String(err.stderr || err.message || err).split("\n")[0]);
High
Sandbox Evasion Gated Capability

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

postinstall.mjsView on unpkg · L8
src/rules/common/testing.mdView file
51patternName = generic_password severity = medium line = 51 matchedText = const in...' };
Medium
Secret Pattern

Hardcoded password in src/rules/common/testing.md

src/rules/common/testing.mdView on unpkg · L51
src/skills/django-tdd/SKILL.mdView file
66patternName = generic_password severity = medium line = 66 matchedText = password...123'
Medium
Secret Pattern

Hardcoded password in src/skills/django-tdd/SKILL.md

src/skills/django-tdd/SKILL.mdView on unpkg · L66
115patternName = generic_password severity = medium line = 115 matchedText = self.cli...23')
Medium
Secret Pattern

Hardcoded password in src/skills/django-tdd/SKILL.md

src/skills/django-tdd/SKILL.mdView on unpkg · L115

Findings

1 Critical5 High9 Medium6 Low
CriticalAi Agent Control Hijackpostinstall.mjs
HighInstall Time Lifecycle Scriptspackage.json
HighChild Processdist/index.js
HighShellpostinstall.mjs
HighSame File Env Network Executionpostinstall.mjs
HighSandbox Evasion Gated Capabilitypostinstall.mjs
MediumAmbiguous Install Lifecycle Scriptpackage.json
MediumSecret Patterndist/index.js
MediumNetwork
MediumEnvironment Vars
MediumInstall Persistencedist/index.js
MediumStructural Risk Force Deep Review
MediumSecret Patternsrc/rules/common/testing.md
MediumSecret Patternsrc/skills/django-tdd/SKILL.md
MediumSecret Patternsrc/skills/django-tdd/SKILL.md
LowNon Install Lifecycle Scripts
LowScripts Present
LowWeak Cryptodist/index.js
LowFilesystem
LowHigh Entropy Strings
LowUrl Strings