Warn by default; block when configured. Changes a foreign AI-agent control surface without an explicit user setup command, causing package code to run for every matching tool use.
Installable DevFlow distribution containing the CLI, runtime hooks, and Claude plugin.
AI Security Review
scanned 19d ago · by lpm-firewall-ai
LPM flags this version as an AI-agent control-surface risk. npm installation modifies the consuming project's Claude Code hook configuration. The resulting wildcard PreToolUse hook executes package code on future tool calls.
Automatic npm postinstall during package installation.
Impact
Changes a foreign AI-agent control surface without an explicit user setup command, causing package code to run for every matching tool use.
Mechanism
Writes a wildcard Claude PreToolUse command hook into the consumer project.
Policy narrative
The package's automatic postinstall script walks from the install working directory to locate a consumer project, creates .claude if needed, merges a PreToolUse entry matching every tool call, and writes .claude/hooks.json. That entry executes the package's pre-tool-use handler in future Claude Code sessions. This is an unconsented install-time mutation of a foreign AI-agent control surface.
AI rationale
Automatic installation persists a wildcard Claude Code command hook in the consumer project. This meets the blocking policy for unconsented postinstall mutation of a broad AI-agent control surface.
Avoid installing @devflow-tools/devflow@0.18.6. Remove it from direct dependencies and check your lockfile for transitive copies.
Choose an independently verified alternative or release. This report does not establish that other versions are safe.
If this version ran, investigate the affected machine and build environment. Rotate credentials it could access and rebuild from a trusted environment.
1import {createServer,createConnection}from'net';import {createHash,randomUUID as randomUUID$1}from'node:crypto';import {execFileSync,spawn}from'node:child_process';import {dirname,join as join$1}from'path';import {mkdirSync,lstatSync,openSync,writeFileSync,fstatSync,closeSync,readFileSync,unlinkSync,existsSync
2import {LogCleanupService}from'@devflow-tools/telemetry';import {existsSync as existsSync$1,mkdirSync as mkdirSync$1,renameSync,readFileSync as readFileSync$1,readdirSync,rmSync,cpSync,lstatSync,writeFileSync as writeFileSync$1,statSync as statSync$1,realpathSync,mkdtempSync,rmdirSync,openSync,constants,fstatSync,readSync,closeSync}from'node:fs';import {randomUUID,createHash,createHmac}from
4`)}function Ki(e,t){return {candidateId:e.id,version:e.overlayVersion,state:t,instruction:e.instruction,trigger:e.trigger,confidence:e.confidence,evidenceReceipt:e.graderReceipt??`candidate:${e.id}:v${e.overlayVersion}`,expiresAt:e.expiresAt}}function Gi(e,t){let n=Math.max(0,Math.min(t.maxLearnedEntries??3,3)),o=Math.max(0,Math.min(t.learnedCharacterBudget??1600,4e3)),r=[],i=0;for(let s of e){if(r.length>=n)break;let a=s.instruction.length+4;i+a>o||(r.push(s),i+=a);}return r}function Yi(e,t,n){let o=e.trigger,r=t.replace(/^devflow:/,"").split(":").pop()??t,i=De(o.skills);if(i.length>0&&!i.som ...
5`,{mode:384});}function as(e){let t=createRequire(import.meta.url),o=[process.env.DEVFLOW_SERVER_ENTRY,cs(()=>t.resolve("@devflow-tools/server/dist/main.js")),join(e,"node_modules","@devflow-tools","server","dist","main.js"),join(e,"apps","server","dist","main.js")].filter(r=>!!r).find(existsSync);if(!o)throw new Error("DevFlow server entry point is not installed or built");return {command:process.execPath,args:[o],cwd:e}}function cs(e){try{return e()}catch{return}}var Oe=class{constructor(t){this.projectRoot=t;this.runtime=createRuntimeManager();}addRef(t){let n=mt();n.sessions.includes(t)||n ...
6
7`)}function bo(e){let t=e.runtimeStore??new q(e.projectRoot),n=t.listEvidenceObligations(e.sessionId);if(n.length===0)return ra(e,t),null;let o=wt(e.transcriptPath,n);if(!o.answer){let s=o.degradationReason??"transcript_answer_unavailable";for(let a of n)t.degradeEvidenceObligation(e.sessionId,a.obligationId,s);return null}let r=o.fingerprint;if(!r){for(let s of n)t.degradeEvidenceObligation(e.sessionId,s.obligationId,"transcript_identity_unavailable");return null}let i=[];for(let s of n){let a=Ro(o.answer,[s]);if(a.length===0){t.resolveEvidenceObligation(e.sessionId,s.obligationId),Ne(e,s,"sa ...
8
9---
10
11`)].join(`
12
13`)}}function Ro(e,t){let n=new Set,o=new Set(t.flatMap(c=>c.contract.requiredSections)),r=So(e.split(/\r?\n/)),i=xo(r.headingLines);for(let c of o)i.found.has(c)||n.add(`missing_section:${c}`);if(t.every(c=>c.contract.runtimeProfilingOccurred))return [...n];let a=t.every(c=>c.contract.samplingEvidenceOccurred);for(let c=0;c<r.claimLines.length;c++){let d=r.claimLines[c].trim();if(!d||aa(d))continue;let u=i.byLine.get(c);for(let l of ma(d))da(l,u,a,n);}return [...n]}function So(e){let t=false,n=[],o=[];for(let r of e){if(/^\s*```/.test(r)){t=!t,n.push(""),o.push(r.replace(/^\s*```+[^\s`]*/,""). ...
21DevFlow memory snapshot=degraded; reason=${c}. Memory result completeness is not authoritative; do not conclude that the project has no memories.`:r.memories.length===0&&d.length>0?`## Project memory
3`)}var ko=D(()=>{});function xt(){let e=dirname(fileURLToPath(import.meta.url)),t=[join$1(e,"..","plugin-files"),join$1(e,"plugin"),join$1(e,"..","..","dist","plugin-files"),join$1(e,"..","..","..","..","plugins","claude-code")];for(let n of t)if(existsSync$1(join$1(n,".claude-plugin","plugin.json")))return n;throw new Error("Plugin files not found. Run `npm run build` first.")}function De(e=homedir$1(),t={}){let n=t.installedPluginsPath??join$1(e,".claude","plugins","installed_plugins.json"),o=t.pluginCachePath??join$1(e,".claude","plugins","cache");for(let i of Ri)try{let c=JSON.parse(readFi ...
5`);}function Eo(e,t){let n=join$1(e,".claude"),o=join$1(n,"mcp.json"),r={};if(existsSync$1(o))try{r=JSON.parse(readFileSync$1(o,"utf-8"));}catch{console.warn("Warning: Could not parse existing mcp.json, skipping MCP config");return}else mkdirSync$1(n,{recursive:true});let s=r.mcpServers??{};s.devflow||(s.devflow={command:t,args:[],env:{}},r.mcpServers=s,writeFileSync$1(o,JSON.stringify(r,null,2)));}function Ot(e,t,n){let o=join$1(t,Be,nn,n);rmSync(o,{recursive:true,force:true}),mkdirSync$1(o,{recursive:true});let r=join$1(e,"hooks");existsSync$1(r)&&Pt(r,join$1(o,"hooks"));for(let c of ["CLAUD ...
6`);let i=join$1(n,"known_marketplaces.json"),a={};if(existsSync$1(i)){let c;try{c=JSON.parse(readFileSync$1(i,"utf-8"));}catch{throw new Error(`Could not parse Claude marketplace registry: ${i}`)}if(!de(c))throw new Error(`Invalid Claude marketplace registry: ${i}`);a=c;}a[Be]={source:{source:"directory",path:o},installLocation:o,lastUpdated:new Date().toISOString()},writeFileSync$1(i,`${JSON.stringify(a,null,2)}
7`);}function Ci(e,t,n){let o=join$1(e,"..","installed_plugins.json"),r={version:2,plugins:{}};if(existsSync$1(o)){let f;try{f=JSON.parse(readFileSync$1(o,"utf-8"));}catch{throw new Error(`Could not parse Claude plugin registry: ${o}`)}if(!de(f))throw new Error(`Invalid Claude plugin registry: ${o}`);r=f;}else mkdirSync$1(dirname(o),{recursive:true});let s=r.plugins;if(s!==void 0&&!de(s))throw new Error(`Invalid Claude plugin registry plugins: ${o}`);let i=s??{},a=i[Et];if(a!==void 0&&!Array.isArray(a))throw new Error(`Invalid Claude plugin registration for ${Et}`);let c=a??[],l=c.find(f=>de(f) ...
9`)}var xo,Ti,$o=D(()=>{Je();xo=Do.default??Do,Ti=createRequire(import.meta.url);});var Io={};j(Io,{reviewPluginMemoryHints:()=>Bi});async function Bi(e,t,n={}){let o=n.isTTY??!!process.stdout.isTTY,r=new Set(t.split(",").map(qi).filter(Boolean));if(!o||r.size===0)return {available:0,selected:0};let s=await(n.load??loadPlugins)(),i=[];for(let u of r){let d=s.get(u);for(let g of d?.memory?.project?.conventions??[])i.push({id:`${u}:${g.key}`,pluginName:u,convention:g});}if(i.length===0)return {available:0,selected:0};let a=i.map(u=>({name:`${u.pluginName}: ${u.convention.value}`,value:u.id,checke ...
16`);let a=[...r];for(let c of a)try{await je(c,e,!0),console.log(`\u2713 ${c}`);}catch{console.error(`\u2717 Failed to install ${c}`);}return a}let s=t.plugins.map(a=>({name:`${a.name.replace("@devflow-tools/","")} - ${a.description.substring(0,50)}`,value:a.name,checked:r.has(a.name)})),i;try{i=await checkbox({message:"Select plugins to install (space to select, enter to confirm):",choices:s});}catch{return console.log(`
17Plugin installation cancelled.
18`),[]}if(i.length>0){console.log(`
19Installing selected plugins...
20`);for(let a of i)try{await je(a,e,!0),console.log(`\u2713 ${a}
21`);}catch{console.error(`\u2717 Failed to install ${a}
Long lines were clipped for display.
Lines 221-245javascript
221Stored: ${getGlobalDevFlowDbPath()} (${r.runId})`}async function ud(e={}){let t=e.database??openGlobalDevFlowDatabase(),n,o=null;try{n=e.runId?t.getBenchmarkReport(e.runId):t.getLatestBenchmarkReport(),e.baselineRunId&&(o=t.getBenchmarkReport(e.baselineRunId));}finally{e.database||t.close();}if(!n)throw new Error(e.runId?`Benchmark report "${e.runId}" not found`:"No benchmark reports found. Run `devflow benchmark run` first.");if(e.baselineRunId&&!o)throw new Error(`Benchmark baseline "${e.baselineRunId}" not found`);let r=e.output??"text",s=renderBenchmarkReport(n,r);if(!o||r==="json")return ...
238${S.green("\u2713")} ${n}@${o.version} installed and activated`),console.log(`
239Run "devflow plugin list --installed" to verify.`);}catch(o){console.error(`
240${S.red("\u2717")} Installation failed: ${o.message}`),process.exitCode=1;}});});var ei={};j(ei,{pluginUninstallCommand:()=>Gd});var Gd,ti=D(()=>{Se();Gd=new Command("uninstall").description("Uninstall a plugin").argument("<name>","Plugin name (with or without @devflow-tools/ prefix)").action(async e=>{let n=`@devflow-tools/plugin-${e.replace(/^@devflow-tools\/(?:plugin-)?/,"")}`;console.log(`Uninstalling ${n}...`);try{await ln(n,process.cwd(),!0),console.log(`
241${S.green("\u2713")} ${n} deactivated and uninstalled`);}catch(o){console.error(`
5${e.stack}`:e+"")}})})});let _=f;functionm(){let e=i.default.useContext(s.RouterContext);if(!e)throw Object.defineProperty(Error("NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted"),"__NEXT_ERROR_CODE",{value:"E509",enumerable:!1,configurable:!0});return e}functiong(...e){return f.router=new u.default(...e),f.readyCallbacks.forEach(e=>e()),f.readyCallbacks=[],f.router}functionE(e){let t={};for(let r of d){if("object"==typeof e[r]){t[r]=Object.assign(Array.isArray(e[r])?[]:{},e[r]);continue}t[r]=e[r]}return t.events=u.default.events,p.forEach(r=>{t[r]=(...t ...