{"slug": "i-point-a-local-llm-at-every-repo-before-opening-it-in-my-editor", "title": "I Point a Local LLM at Every Repo Before Opening It in My Editor", "summary": "A developer has built a workflow that runs every untrusted repository through a local LLM triage pass before opening it in an editor, after encountering a fake-recruiter take-home project that contained a malicious postinstall script. The approach uses static analysis with tools like jq and ripgrep, followed by classification with Ollama running qwen2.5-coder models, to detect supply-chain malware without executing code. The developer also created Argus Lens, a scanner for this class of repo, and emphasizes the importance of checking for dependencies missing from lockfiles.", "body_md": "In May a \"recruiter\" sent me a take-home project for a Web3 role. Nice README, plausible Next.js structure, a real-sounding company. Buried in the build tooling was a postinstall script that decoded a base64 blob and pulled a second stage from a hardcoded IP. If I had done what 99% of candidates do, `git clone`\n\nthen `npm install`\n\nthen open it in my editor, an infostealer would have been running on my machine before I read a single line of code.\n\nThat was not the last one either. These fake-recruiter lures are an industry now, and the payload almost never lives in `src/`\n\n. It lives in the places you skim: lifecycle scripts, config files, a \"utils\" file with one weird function. So I changed my default. Every unknown repo now goes through a local LLM triage pass before my editor ever touches it. No code execution, no install, just static reading.\n\nHere's the workflow.\n\nThe whole point is that the repo stays inert. Two safe ways to get the files:\n\n```\n# Option 1: clone without checkout, inspect the tree first\ngit clone --no-checkout https://github.com/some-org/take-home-task.git\ncd take-home-task\ngit ls-tree -r HEAD --name-only\n\n# Option 2: download the tarball, no git hooks, no clone at all\ncurl -L https://github.com/some-org/take-home-task/archive/refs/heads/main.tar.gz \\\n  | tar -xz -C ./quarantine/\n```\n\nI prefer the tarball. It cannot execute anything, and extracting into a `quarantine/`\n\ndirectory keeps me honest. Also worth saying explicitly: do not open the folder in an editor with plugins that auto-run tasks. VS Code will happily execute workspace settings, launch configs, and some extensions will run `npm install`\n\nfor you as a favor. Read the files with `cat`\n\n, `bat`\n\n, or the LLM pipeline below.\n\nBefore any AI is involved, three cheap checks catch the majority of these campaigns:\n\n```\n# Lifecycle scripts are the #1 delivery mechanism\ncat package.json | jq '.scripts | with_entries(\n  select(.key | test(\"install|prepare|prepublish|postpack\")))'\n\n# Dependencies present in package.json but missing from the lockfile\n# (a classic evasion: the malicious dep gets resolved fresh at install time)\njq -r '.dependencies, .devDependencies | keys[]?' package.json | sort > deps.txt\njq -r '.packages | keys[]' package-lock.json | sed 's|node_modules/||' | sort > locked.txt\ncomm -23 deps.txt locked.txt\n\n# Long encoded blobs anywhere in the tree\nrg -n --max-columns=200 '[A-Za-z0-9+/]{120,}={0,2}' --glob '!*.lock' --glob '!*.map'\n```\n\nThe lockfile mismatch check matters more than people think. Several campaigns I've dissected ship a clean-looking lockfile and a dirty `package.json`\n\n, or reference a typosquatted package only from a script. When I built Argus Lens (lens.noctis.biz), a scanner for exactly this class of repo, deps-missing-from-lockfile turned out to be one of the highest-signal checks in the whole tool.\n\nRegex gets you candidates. Judgment is where a model earns its keep, and this has to be a local model, because I'm sometimes triaging repos under NDA or repos whose mere URL I don't want leaving my machine.\n\nI run Ollama on WSL2 with qwen2.5-coder in two sizes: 1.5b for the fast pass over everything, 7b when the small one flags something. The prompt is a classifier, not a chat:\n\n```\ntriage_file() {\n  local file=\"$1\"\n  ollama run qwen2.5-coder:7b <<EOF\nYou are a supply-chain malware analyst. Classify the following file\nfrom an UNTRUSTED repository. Do not summarize what the code claims\nto do. Focus on what it actually does.\n\nAnswer in exactly this format:\nVERDICT: CLEAN | SUSPICIOUS | MALICIOUS\nSIGNALS: <comma-separated list, or \"none\">\nEXPLANATION: <max 3 sentences>\n\nSignals to look for:\n- decoding of base64/hex strings followed by eval, Function, or child_process\n- network calls to raw IPs or unusual domains at import/build time\n- reading of environment variables, keychains, browser profile paths,\n  .ssh, .aws, or wallet files\n- code that only runs during install/build, not at runtime\n- obfuscation: string array shuffling, charCode arithmetic, packed code\n\nFILE: ${file}\n---\n$(cat \"$file\")\nEOF\n}\n```\n\nThen it's just a loop over the candidates:\n\n```\nrg -l 'child_process|eval\\(|Function\\(|fromCharCode|atob|Buffer\\.from' \\\n  --glob '!node_modules' quarantine/ | while read -r f; do\n  echo \"=== $f\"\n  triage_file \"$f\"\ndone\n```\n\nThe strict output format is doing real work here. Small models ramble, and \"answer with VERDICT on the first line\" turns a rambling model into something you can grep and script against.\n\nAfter feeding a few dozen of these repos through this pipeline (and building spectr-ai, my open-source contract auditor, which taught me a lot about prompting small models for security work), the signals that separate real payloads from noise:\n\n**Install-time execution.** Legitimate projects rarely need `postinstall`\n\nbeyond native module builds. A postinstall that touches the network or decodes strings is close to a guaranteed conviction.\n\n**Deps in manifest but not in lockfile.** Covered above. It means the attacker wants resolution to happen fresh on your machine.\n\n**Encoded blobs plus a decoder.** A base64 string alone is often fine (inlined images, test fixtures). A base64 string within reach of `eval`\n\n, `new Function`\n\n, or `child_process.exec`\n\nis not.\n\n**Env harvesting.** Loops over `process.env`\n\n, or path building toward `~/.ssh`\n\n, browser extension folders, or wallet data directories like `Local Storage/leveldb`\n\n. There is no honest reason for a take-home CRUD app to know where MetaMask keeps its state.\n\n**Effort asymmetry.** The app code is boilerplate quality, but one config or helper file is dense, minified, or oddly sophisticated. Attackers copy the app and hand-craft the payload, and the seam shows.\n\nThe 1.5b model misses things. It's fine as a fast filter over many files, but I've watched it label a charCode-obfuscated dropper as \"string manipulation utilities.\" The 7b catches most of what I throw at it, but a determined attacker who tests their payload against open models will eventually get past this too. That's fine. I'm not trying to build a perfect oracle, I'm trying to make sure the lazy, mass-produced lures (which is most of them) get caught in under two minutes without me executing anything.\n\nAlso: the model reads what you give it. If you only scan `.js`\n\nfiles, the payload will be in a `.node`\n\nbinary or a build config. Cast the net wide first, then classify.\n\nThe whole thing costs me maybe three minutes per unknown repo, runs entirely offline, and has already paid for itself twice. Cheap insurance.\n\nDo you actually inspect repos from strangers before installing, or does `npm install`\n\nstill happen on autopilot?", "url": "https://wpnews.pro/news/i-point-a-local-llm-at-every-repo-before-opening-it-in-my-editor", "canonical_source": "https://dev.to/pavelespitia/i-point-a-local-llm-at-every-repo-before-opening-it-in-my-editor-4dbb", "published_at": "2026-08-04 16:18:32+00:00", "updated_at": "2026-08-04 16:50:55.001789+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-safety", "developer-tools", "ai-tools"], "entities": ["Ollama", "qwen2.5-coder", "Argus Lens", "VS Code", "WSL2", "Next.js", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/i-point-a-local-llm-at-every-repo-before-opening-it-in-my-editor", "markdown": "https://wpnews.pro/news/i-point-a-local-llm-at-every-repo-before-opening-it-in-my-editor.md", "text": "https://wpnews.pro/news/i-point-a-local-llm-at-every-repo-before-opening-it-in-my-editor.txt", "jsonld": "https://wpnews.pro/news/i-point-a-local-llm-at-every-repo-before-opening-it-in-my-editor.jsonld"}}