Proprietary Navian tooling for evaluating rendered web experiences.
AI Security Review
scanned 13d ago · by lpm-firewall-ai
LPM treats this as warn-only first-party agent extension lifecycle risk. Install and an explicit CLI flag can write first-party coding-agent instruction files. The install path is gated by NAVIAN_SETUP_SKILL=1 and copies this package's skill into the user Claude skills directory. The --setup command also writes a project Cursor rule and AGENTS.md. No default unconsented agent-config write, secret theft, or remote payload execution was found.
npm postinstall when NAVIAN_SETUP_SKILL=1, or a user running designcheck --setup.
Impact
Opt-in agent instruction files tell coding agents to invoke this CLI and attribute results to Navian. That is a guarded first-party extension setup, not a silent hijack of unrelated agent configuration.
Mechanism
setup.js creates ~/.claude/skills/designcheck and copies SKILL.md plus designcheck.js there. Full mode also writes .cursor/rules/designcheck.md and appends the same auditor instructions to AGENTS.md.
AI rationale
The package can mutate Claude, Cursor, and Codex instruction files, but the install path does so only when NAVIAN_SETUP_SKILL=1 and the broader writes require the explicit --setup command. That is guarded first-party agent setup rather than an unconsented control-surface hijack or a hidden exfiltration chain.
AI called this Suspicious at 78.0% confidence as Dangerous Capability with medium false-positive risk.
Evidence for warning
postinstall runs on npm install and calls Claude skill setup only when NAVIAN_SETUP_SKILL is 1, using runSetup mode auto.
runSetup always installs a Claude Code skill under the home .claude/skills/designcheck directory, and mode full also writes a Cursor rule and appends AGENTS.md.
The designcheck CLI --setup flag invokes runSetup in full mode, which writes those same agent instruction files.
Finding uplink is off unless the operator sets NAVIAN_PULSE_URL; the bearer token is sent only to that configured host.
Evidence against
Without NAVIAN_SETUP_SKILL=1, postinstall leaves the agent skill step as an empty done list.
NAVIAN_NO_SETUP=1 or CI=true exits postinstall before any setup or browser install.
package.json does not depend on its own name, and there is no preinstall hook.
Telemetry and context fetches use the caller-supplied Pulse URL rather than a hardcoded vendor collector.
11Fix the path and run: designcheck --install-vlm-key=<path>
L12: `)}try{const e=require("child_process");process.stdout.write(`
L13: navian-designcheck: fetching the browser it drives (one-time)\u2026
High
Child Process
Package source references child process execution.
11Fix the path and run: designcheck --install-vlm-key=<path>
L12: `)}try{const e=require("child_process");process.stdout.write(`
L13: navian-designcheck: fetching the browser it drives (one-time)\u2026
...
L15: `)}catch{process.stdout.write(` The browser could not be fetched here. Run once when online:
L16: npx playwright install chromium
L17: `)}try{const e=process.env.NAVIAN_SETUP_SKILL==="1"?require("../lib/setup.js").runSetup({mode:"auto"}):{done:[]};e.done.length&&(process.stdout.write(`
High
Runtime Package Install
Package source invokes a package manager install command at runtime.
25 Licensed copy? reinstall with --navian-license=<path> (or run designcheck --install-license=<path>).
26`),process.stdout.write(`
27 Then just ask your agent to audit a page.
28
29`))}catch{}process.exit(0);
30
11Fix the path and run: designcheck --install-vlm-key=<path>
L12: `)}try{const e=require("child_process");process.stdout.write(`
L13: navian-designcheck: fetching the browser it drives (one-time)\u2026
...
L15: `)}catch{process.stdout.write(` The browser could not be fetched here. Run once when online:
L16: npx playwright install chromium
L17: `)}try{const e=process.env.NAVIAN_SETUP_SKILL==="1"?require("../lib/setup.js").runSetup({mode:"auto"}):{done:[]};e.done.length&&(process.stdout.write(`
High
Runtime Package Install
Package source invokes a package manager install command at runtime.
3 navian-designcheck: this build needs a valid per-customer license to run.`),console.error(" Set NAVIAN_LICENSE / NAVIAN_LICENSE_FILE, or place ~/.navian/designcheck.license."),process.exit(2)}}return fs.readFileSync(path.join(__dirname,"..","designcheck.js"),"utf8")})();if(PROBE)try{const{instrumentJs:e}=require("../lib/probe-instrument.js"),n=e("designcheck.js",SRC);n&&n.code&&n.sites&&n.sites.length?SRC=n.code:console.error(" \u26A0 probe requested but no instrumentable engine functions were found; runtime graph will not be sent.")}catch(e){console.error(" \u26A0 probe instrumentation f ...
4 navian-designcheck: this build requires a valid per-customer license.`),process.exit(2))}functionlicenseGate(){constn=license.checkLicense({env:ENV,anchor:void0}),o=n.claims&&n.claims.licensee?" ("+n.claims.licensee+")":"";if(n.hardDisabled){consti=n.claims&&n.claims.disable_at?newDate(n.claims.disable_at*1e3).toISOString().slice(0,10):"";console.error(`
5 navian-designcheck: this trial ended`+(i?" on "+i:"")+" and has stopped working."+o+`
6 Contact Navian for a full license.
7`),process.exit(2)}if(n.valid&&n.posture==="Full"){consti=n.claims&&(n.claims.licensee||n.claims.sub)||"this install",h=n.claims&&n.claims.expires_at?" \xB7 valid to "+newDate(n.claims.expires_at*1e3).toISOString().slice(0,10):"",f=n.claims&&n.claims.env?" \xB7 "+n.claims.env:"";return console.error(" \u25C6 Running under license \u2014 "+i+h+f+" [navian-designcheck v"+PKG.version+"]"),n}if(n.valid&&n.posture==="WarnBanner")return console.error(" \u26A0 LICENSE: in grace \u2014 "+n.daysLeft+" day(s) left. Renew to avoid the UNLICENSED watermark."+o),n;if(n.valid&&n.posture==="WatermarkUnl ...
8 navian-designcheck: `+a+`.
9 This build requires a valid per-customer license (NAVIAN_LICENSE_ENFORCE=1).
10`),process.exit(2),console.error(" \u26A0 UNLICENSED (advisory): "+a+". Running anyway \u2014 set NAVIAN_LICENSE_ENFORCE=1 to require one."),n}function printLicenseStatus(){const e=license.checkLicense({env:ENV,anchor:void 0}),n=licenseSummary(e);return console.log("navian-designcheck \u2014 license status"),console.log(" status : "+(e.hardDisabled?"DISABLED (trial ended \u2014 tool will not run)":n.licensed?"LICENSED":"UNLICENSED")),console.log(" state : "+n.state+(e.daysLeft?" ("+e.daysLeft+"d grace left)":"")),e.claims&&e.claims.disable_at&&console.log(" trial-end: "+new Date(e.cla ...
11`)[0]+"); the design review runs without a peer comparison")}}DESIGN_INJECT="window.__NAVIAN_DESIGN=true;window.__NAVIAN_NORMS="+JSON.stringify(e)+";"}const doAx=!flags.includes("--no-ax"),doCrossBrowser=flags.includes("--cross-browser"),attachFlag=flags.find(e=>e.startsWith("--attach=")),attachEndpoint=attachFlag?attachFlag.slice(attachFlag.indexOf("=")+1):null;if(attachEndpoint){const e=["--rlg","--fuzz","--pixels","--keytrap","--dynamic","--vision","--ax","--reduced-motion","--repair","--cross-browser","--sweep","--llm","--alt-llm"].filter(n=>flags.includes(n));e.length&&(console.error("des ...
12 Next: npx playwright install chromium (one-time browser), then ask your agent to audit a page.`),process.exit(e.fail.length?1:0)}const installLicFlag=flags.find(e=>e.startsWith("--install-license"));if(installLicFlag){const e=argv[argv.indexOf(installLicFlag)+1],n=installLicFlag.indexOf("=")>=0?installLicFlag.split("=").slice(1).join("="):e&&e.indexOf("--")!==0?e:"";try{const o=require("../lib/setup.js").installLicense(n);console.log(" \u2714 license installed \u2192 "+o+`
13 Verify: designcheck --license-status`),process.exit(0)}catch(o){console.error(" \u26A0 could not install license \u2014 "+(o&&o.message||o)),process.exit(2)}}const installVlmFlag=flags.find(e=>e.startsWith("--install-vlm-key"));if(installVlmFlag){const e=argv[argv.indexOf(installVlmFlag)+1],n=installVlmFlag.indexOf("=")>=0?installVlmFlag.split("=").slice(1).join("="):e&&e.indexOf("--")!==0?e:"";try{const o=require("../lib/setup.js").installVlmKey(n);console.log(" \u2714 VLM key installed \u2192 "+o+`
14 Now --vision works with no env var.`),process.exit(0)}catch(o){console.error(" \u26A0 could not install VLM key \u2014 "+(o&&o.message||o)),process.exit(2)}}if(flags.includes("--license-status")&&process.exit(printLicenseStatus()?0:1),evidenceVerifyFlag){const e=evidenceVerifyFlag.slice(evidenceVerifyFlag.indexOf("=")+1);try{const n=JSON.parse(fs.readFileSync(e,"utf8")),o=Array.isArray(n)?n:[n],a=h=>"'"+String(h).replace(/'/g,"'\\''")+"'";let i=!0;o.forEach((h,f)=>{const l=evidence.verifyEvidence(h&&h.bundle,h&&h.attestation),y=!l.signed&&l.attestation==="unsigned"&&l.integrityOk,s=l.veri ...
20- alt_ok: does the CURRENT alt accurately and concisely convey the image to a blind user (empty or absent alt on a NON-decorative image is not ok).`;let r;try{r=await vlmFetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"x-api-key":n,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:VISION_MODEL,max_tokens:400,messages:[{role:"user",content:[{type:"image",source:{type:"base64",media_type:"image/png",data:S}},{type:"text",text:b}]}]})})}catch(k){y=k;continue}if(r.status===401||r.status===403){let k="";try{const O=await r.json();k= ...
21 if (globalThis.__dcQS) return;
Long lines were clipped for display.
•matchType = previous_version_dangerous_delta
matchedPackage = @navianpulse/designcheck@2.4.6
matchedIdentity = npm:[redacted]:2.4.6
similarity = 0.533
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.
2`).map(o=>o.slice(3).trim().replace(/^"|"$/g,"")).flatMap(o=>o.split(" -> ")).filter(o=>o&&n.test(o));return{any:r.length>0,files:r}}catch{return{any:!0,files:["(git status unavailable)"]}}}async function ensure(e,t){t=t||{};const n=t.log||(()=>{}),s=repoState(t.cwd);if(!s.ok)return{ok:!1,reason:s.reason};const r=await isIndexed(e,s.commit),o=changedSince(s,t.cwd);if(r.known&&!o.any)return{ok:!0,indexed:!1,commit:s.commit};r.known?n(" \u25B8 "+o.files.length+" source file(s) changed since the graph was built ("+o.files.slice(0,3).map(c=>c.split("/").pop()).join(", ")+(o.files.length>3?", \u20 ...
High
Command Output Exfiltration
Source combines command execution, command-output handling, and outbound requests; review data flow before blocking.