registry  /  @geraldmaron/construct  /  1.4.2

@geraldmaron/construct@1.4.2

Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces

AI Security Review

scanned 8d ago · by lpm-firewall-ai

LPM blocks this version under the AI-agent control-surface policy. The package mutates AI-agent control surfaces during npm postinstall in a consumer project. It stages launchers and generated agent/hook/MCP configuration without an explicit command invocation by the user.

Static reason
High-risk behavior combination matched malicious policy.; previous stored version diff introduced dangerous source
Trigger
npm install in a project with package.json
Impact
A dependency install can alter Claude Code, Codex, Copilot, OpenCode, VS Code, Cursor, and project hook behavior for future agent sessions.
Mechanism
postinstall AI-agent configuration and hook staging
Policy narrative
On install, npm runs bin/construct-postinstall.mjs. For a normal consumer project, it stages .construct launchers, then spawns scripts/sync-specialists.mjs --project, which writes agent files, settings, MCP wiring, and Claude hook commands into project AI-agent control directories. Those hooks later execute through the staged .construct/run.mjs launcher during agent sessions.
Rationale
Static source inspection confirms unconsented lifecycle mutation of AI-agent control surfaces during postinstall, which is blocking behavior for the firewall even though much of the package functionality is otherwise aligned with an agent orchestration tool. I did not find install-time credential exfiltration or a hidden remote payload. Product guard normalized a non-low false-positive publish_block request to warn-only suspicious.
Evidence
package.jsonbin/construct-postinstall.mjslib/install/stage-project.mjsscripts/sync-specialists.mjslib/mcp-platform-config.mjslib/hooks/scan-secrets.mjslib/mcp/tools/memory.mjsplatforms/opencode/sync-config.mjs.construct/run.mjs.construct/bootstrap.sh.construct/bootstrap.ps1.construct/version.construct/stage-state.json.construct/install-manifest.json.gitignore.claude/settings.json.claude/agents.codex.github.opencode.vscode

Decision evidence

public snapshot
AI called this Suspicious at 90.0% confidence as Dangerous Capability with medium false-positive risk.
Evidence for policy block
  • package.json defines postinstall: node ./bin/construct-postinstall.mjs
  • bin/construct-postinstall.mjs runs stageProjectAdapters for consumer installs unless CONSTRUCT_SKIP_POSTINSTALL=1
  • lib/install/stage-project.mjs writes .construct launcher files and spawns scripts/sync-specialists.mjs --project
  • scripts/sync-specialists.mjs project mode writes .claude/settings.json, .claude/agents, .codex, .github, .opencode, .vscode, and Cursor agent config
  • scripts/sync-specialists.mjs installs Claude hooks that execute node "${CLAUDE_PROJECT_DIR:-.}/.construct/run.mjs" hook <name>
Evidence against
  • Global install path only prints opt-in guidance and exits
  • lib/hooks/scan-secrets.mjs is a defensive hook that reads a TOOL_INPUT_FILE_PATH and blocks detected secrets
  • lib/mcp/tools/memory.mjs stores/searches local observations and sessions; Rovo network calls are user-invoked tool behavior
  • MCP config builder emits env references for secrets rather than literal credential values
  • OpenRouter/Ollama network in platforms/opencode/sync-config.mjs is user-invoked config sync, not install-time exfiltration
Behavioral surface
Source
ChildProcessCryptoDynamicRequireEnvironmentVarsEvalFilesystemNetworkShell
Supply chain
HighEntropyStringsUrlStrings
ManifestNo manifest risk signals triggered.
scanned 619 file(s), 4.21 MB of source, external domains: 127.0.0.1, api.anthropic.com, api.atlassian.com, api.github.com, api.githubcopilot.com, api.linear.app, api.openai.com, app.slack.com, astral.sh, d2lang.com, docs.docker.com, example.com, fonts.googleapis.com, generativelanguage.googleapis.com, github.com, graphviz.org, huggingface.co, json-schema.org, nodejs.org, ollama.com, opencode.ai, openrouter.ai, pandoc.org, raw.githubusercontent.com, schemas.openxmlformats.org, slack.com, www.apple.com, www.libreoffice.org, your-domain.atlassian.net

Source & flagged code

18 flagged · loading source
package.jsonView file
scripts.postinstall = node ./bin/construct-postinstall.mjs
High
Install Time Lifecycle Scripts

Package defines install-time lifecycle scripts.

package.jsonView on unpkg
lib/hooks/scan-secrets.mjsView file
47patternName = private_key_rsa severity = critical line = 47 matchedText = { name: .../ },
Critical
Critical Secret

Package contains a critical-looking secret pattern.

lib/hooks/scan-secrets.mjsView on unpkg · L47
47patternName = private_key_rsa severity = critical line = 47 matchedText = { name: .../ },
Critical
Secret Pattern

RSA private key in lib/hooks/scan-secrets.mjs

lib/hooks/scan-secrets.mjsView on unpkg · L47
51patternName = private_key_openssh severity = critical line = 51 matchedText = { name: .../ },
Critical
Secret Pattern

OpenSSH private key in lib/hooks/scan-secrets.mjs

lib/hooks/scan-secrets.mjsView on unpkg · L51
platforms/opencode/sync-config.mjsView file
6import fs from "node:fs"; L7: import { spawnSync } from "node:child_process"; L8: import { readOpenCodeConfig, writeOpenCodeConfig, findOpenCodeConfigPath } from "../../lib/opencode-config.mjs";
High
Child Process

Package source references child process execution.

platforms/opencode/sync-config.mjsView on unpkg · L6
templates/distribution/run.mjsView file
218' - Bootstrap a binary: ./.construct/bootstrap.sh (POSIX)\n' + L219: ' powershell -File .construct/bootstrap.ps1 (Windows)\n' L220: );
High
Shell

Package source references shell execution.

templates/distribution/run.mjsView on unpkg · L218
lib/deck-export-pptx.mjsView file
1323missing: ['pptxgenjs'], L1324: message: 'Install pptxgenjs to enable PPTX export (`npm install pptxgenjs` in the Construct package).', L1325: }; ... L1328: const payload = Buffer.from(JSON.stringify(opts)).toString('base64'); L1329: const child = spawnSync(process.execPath, [ L1330: '--input-type=module',
High
Runtime Package Install

Package source invokes a package manager install command at runtime.

lib/deck-export-pptx.mjsView on unpkg · L1323
19L20: const require = createRequire(import.meta.url); L21: const MODULE_URL = pathToFileURL(fileURLToPath(import.meta.url)).href;
Medium
Dynamic Require

Package source references dynamic require/import behavior.

lib/deck-export-pptx.mjsView on unpkg · L19
lib/roles/event-bus.mjsView file
22function eventsPath() { L23: if (process.env.CONSTRUCT_ROLES_ROOT) { L24: return join(process.env.CONSTRUCT_ROLES_ROOT, 'events.jsonl'); ... L78: let entry; L79: try { entry = JSON.parse(raw[i]); } catch { continue; } L80: if (since && entry.ts < since) break;
Low
Weak Crypto

Package source references weak cryptographic algorithms.

lib/roles/event-bus.mjsView on unpkg · L22
lib/embed/supervision.mjsView file
9* Linux — systemd user unit at ~/.config/systemd/user/construct-embed.service L10: * Windows — Task Scheduler (schtasks) entry named "Construct.Embed" L11: * ... L21: import path from 'node:path'; L22: import { spawnSync } from 'node:child_process'; L23: ... L25: L26: const HOME = os.homedir(); L27: const PLATFORM = process.platform; L28: const NODE_BIN = process.execPath; ... L34: const result = spawnSync('which', ['construct'], { encoding: 'utf8' }); L35: return result.status === 0 ? result.stdout.trim() : 'construct';
Medium
Install Persistence

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

lib/embed/supervision.mjsView on unpkg · L9
lib/runtime/whisper-bootstrap.mjsView file
22import { existsSync, mkdirSync, statSync, createWriteStream } from 'node:fs'; L23: import { spawnSync } from 'node:child_process'; L24: import { pipeline } from 'node:stream/promises'; ... L27: L28: const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main'; L29: const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en'; L30: const KNOWN_MODELS = new Set(['tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en', 'medium', 'medium.en', 'large-v3', 'large-v3-turbo']);
High
Same File Env Network Execution

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

lib/runtime/whisper-bootstrap.mjsView on unpkg · L22
lib/mcp/tools/memory.mjsView file
14export async function memorySearch(args) { L15: const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd(); L16: const project = args.project || null; ... L28: try { L29: const hybrid = await buildHybridSearchResultsAsync(cwd, query, { limit, env: process.env }); L30: hybridResults = (hybrid.results || []).map((r) => ({ ... L177: const cloudId = process.env.ATLASSIAN_CLOUD_ID || 'hashicorp.atlassian.net'; L178: const res = await fetch(`https://api.atlassian.com/rovo/v1/search`, { L179: method: 'POST', ... L183: }, L184: body: JSON.stringify({ query, limit: topK }), L185: });
Critical
Credential Exfiltration

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

lib/mcp/tools/memory.mjsView on unpkg · L14
lib/libreoffice-export.mjsView file
9import path from 'node:path'; L10: import { spawnSync } from 'node:child_process'; L11: ... L18: function whichBin(name) { L19: const cmd = process.platform === 'win32' ? 'where' : 'which'; L20: const result = spawnSync(cmd, [name], { encoding: 'utf8' }); L21: if (result.status !== 0) return null; L22: return (result.stdout || '').trim().split('\n')[0] || null; L23: } L24: L25: export function resolveLibreOfficeBin(env = process.env) { L26: const fromEnv = (env.CONSTRUCT_LIBREOFFICE_BIN || env.SOFFICE_BIN || '').trim();
High
Sandbox Evasion Gated Capability

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

lib/libreoffice-export.mjsView on unpkg · L9
lib/document-extract/docling-sidecar.pyView file
path = lib/document-extract/docling-sidecar.py kind = build_helper sizeBytes = 5192 magicHex = [redacted]
Medium
Ships Build Helper

Package ships non-JavaScript build or shell helper files.

lib/document-extract/docling-sidecar.pyView on unpkg
templates/distribution/construct-reference.docxView file
path = [redacted]-reference.docx kind = high_entropy_blob sizeBytes = 11466 magicHex = [redacted]
High
Ships High Entropy Blob

Package ships high-entropy non-source blobs.

templates/distribution/construct-reference.docxView on unpkg
path = [redacted]-reference.docx kind = compressed_blob sizeBytes = 11466 magicHex = [redacted]
Medium
Ships Compressed Blob

Package ships compressed or archive-like blobs.

templates/distribution/construct-reference.docxView on unpkg
path = [redacted]-reference.docx kind = nested_archive_needs_inspection sizeBytes = 11466 magicHex = [redacted]
Low
Nested Archive Needs Inspection

Package ships a nested archive or MCP bundle that was inventoried but not recursively analyzed.

templates/distribution/construct-reference.docxView on unpkg
lib/export-validate.mjsView file
matchType = previous_version_dangerous_delta matchedPackage = @geraldmaron/construct@1.4.1 matchedIdentity = npm:QGdlcmFsZG1hcm9uL2NvbnN0cnVjdA:1.4.1 similarity = 0.867 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.

lib/export-validate.mjsView on unpkg

Findings

5 Critical7 High7 Medium7 Low
CriticalCritical Secretlib/hooks/scan-secrets.mjs
CriticalCredential Exfiltrationlib/mcp/tools/memory.mjs
CriticalPrevious Version Dangerous Deltalib/export-validate.mjs
CriticalSecret Patternlib/hooks/scan-secrets.mjs
CriticalSecret Patternlib/hooks/scan-secrets.mjs
HighInstall Time Lifecycle Scriptspackage.json
HighChild Processplatforms/opencode/sync-config.mjs
HighShelltemplates/distribution/run.mjs
HighSame File Env Network Executionlib/runtime/whisper-bootstrap.mjs
HighSandbox Evasion Gated Capabilitylib/libreoffice-export.mjs
HighRuntime Package Installlib/deck-export-pptx.mjs
HighShips High Entropy Blobtemplates/distribution/construct-reference.docx
MediumDynamic Requirelib/deck-export-pptx.mjs
MediumNetwork
MediumEnvironment Vars
MediumInstall Persistencelib/embed/supervision.mjs
MediumShips Build Helperlib/document-extract/docling-sidecar.py
MediumShips Compressed Blobtemplates/distribution/construct-reference.docx
MediumStructural Risk Force Deep Review
LowScripts Present
LowEval
LowWeak Cryptolib/roles/event-bus.mjs
LowFilesystem
LowHigh Entropy Strings
LowUrl Strings
LowNested Archive Needs Inspectiontemplates/distribution/construct-reference.docx