registry  /  @geraldmaron/construct  /  1.5.3

@geraldmaron/construct@1.5.3

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

AI Security Review

scanned 3h ago · by lpm-firewall-ai

LPM blocks this version under the AI-agent control-surface policy. Confirmed unconsented npm postinstall mutation of broad AI-agent control surfaces in the installing project. The package stages Construct launchers and agent/MCP configuration for multiple editors without an explicit user command.

Static reason
High-risk behavior combination matched malicious policy.; previous stored version diff introduced dangerous source
Trigger
npm install of @geraldmaron/construct in a consumer project
Impact
Can alter Claude Code, Codex, VS Code, Cursor, Copilot, OpenCode agent routing and MCP tool availability for the project immediately after install.
Mechanism
postinstall stages project launchers and syncs AI-agent adapters/configs
Policy narrative
On npm install, package.json invokes bin/construct-postinstall.mjs. For a consumer project with package.json, the hook calls stageProjectAdapters, which writes .construct launchers and spawns scripts/sync-specialists.mjs --project. That script creates or mutates project AI-agent control files for Claude Code, Codex, VS Code, Cursor, Copilot/OpenCode and MCP wiring. This is package-aligned functionality and includes skip/global safeguards, but it is still unconsented install-time mutation of broad AI-agent control surfaces.
Rationale
Source inspection confirms install-time project AI-agent configuration mutation, which matches the firewall block policy even though scanner exfiltration hints were noisy. No concrete credential exfiltration or destructive payload was found, but the postinstall control-surface write is sufficient for a block verdict. Product guard normalized a concrete AI-agent control hijack publish_block to the blockable dangerous-capability shape.
Evidence
package.jsonbin/construct-postinstall.mjslib/install/stage-project.mjsscripts/sync-specialists.mjslib/hooks/scan-secrets.mjslib/mcp/tools/memory.mjs<project>/.construct/run.mjs<project>/.construct/bootstrap.sh<project>/.construct/bootstrap.ps1<project>/.construct/version<project>/.construct/stage-state.json<project>/.construct/install-manifest.json<project>/.gitignore<project>/.claude/agents/construct.md<project>/.claude/settings.json<project>/.claude/skills/*/SKILL.md<project>/.mcp.json<project>/.codex/config.toml<project>/.codex/agents/*.toml

Decision evidence

public snapshot
AI called this Malicious at 90.0% confidence as Dangerous Capability with low false-positive risk.
Evidence for policy block
  • package.json defines postinstall: node ./bin/construct-postinstall.mjs.
  • bin/construct-postinstall.mjs runs stageProjectAdapters for consumer projects during npm install.
  • lib/install/stage-project.mjs spawns scripts/sync-specialists.mjs --project in the consumer project.
  • scripts/sync-specialists.mjs project mode writes broad AI-agent configs: .claude, .codex, .mcp.json, .vscode, .cursor, .github/Copilot adapters.
  • Postinstall also writes .construct launchers/state/install-manifest and appends Construct ignore patterns to .gitignore.
Evidence against
  • Global install exits with guidance instead of writing user-scope agent config.
  • CONSTRUCT_SKIP_POSTINSTALL=1 disables the hook.
  • lib/hooks/scan-secrets.mjs is a defensive local secret scanner, not credential harvesting.
  • lib/mcp/tools/memory.mjs stores/searches local observations; Rovo search is user-invoked and credential-gated.
  • Network endpoints found are package-aligned provider/model/telemetry APIs, not install-time exfiltration.
Behavioral surface
Source
ChildProcessCryptoDynamicRequireEnvironmentVarsEvalFilesystemNetworkShell
Supply chain
HighEntropyStringsUrlStrings
ManifestNo manifest risk signals triggered.
scanned 752 file(s), 5.23 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, developer.1password.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
23import { existsSync, mkdirSync, statSync, createWriteStream } from 'node:fs'; L24: import { spawnSync } from 'node:child_process'; L25: import { pipeline } from 'node:stream/promises'; ... L30: L31: const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main'; L32: const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en'; L33: 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 · L23
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, env) { L19: const cmd = process.platform === 'win32' ? 'where' : 'which'; L20: const result = spawnSync(cmd, [name], { encoding: 'utf8', env }); 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, { existsSyncFn = fs.existsSync } = {}) { 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 = 6406 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/doctor/watchers/mcp-protocol.mjsView file
matchType = previous_version_dangerous_delta matchedPackage = @geraldmaron/construct@1.4.2 matchedIdentity = npm:QGdlcmFsZG1hcm9uL2NvbnN0cnVjdA:1.4.2 similarity = 0.408 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.

lib/doctor/watchers/mcp-protocol.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/doctor/watchers/mcp-protocol.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