My program behaves like a trojan. Here is what makes it not one (part 2)
In part 1 I told how my npm account got locked, Kaspersky deleted a file from my program as a trojan, and GitHub listed my package as malware. I promised Kaspersky's decision. Here it is, and it isn't the clean ending I
In part 1 I told how my npm account got locked, Kaspersky deleted a file from my program as a trojan, and GitHub listed my package as malware. I promised Kaspersky's decision. Here it is, and it isn't the clean ending I wanted.
I build Local Commander, so read everything below as the author's side of the story.
Kaspersky's decision
On November 5th Kaspersky's allowlist team answered about the file I had submitted, JobHost.exe from version 0.3.58: "Sorry, it was a false detection. It was fixed." My application to their Allowlist Program went to their malware experts, which can take 12 to 14 business days.
The next day the detection came back.
The new JobHost.exe, from version 0.3.64, is the version where I had removed the behaviour that looked suspicious. It ran for two days on my own test computer with Kaspersky Endpoint Security. Then the antivirus databases updated on November 6th, and Kaspersky quarantined it while it was running a command I had asked for. I installed the next release, which contains the same file byte for byte. It was quarantined three minutes later. On November 7th the same thing happened on a customer's workstation that had been fine since the 4th.
Nothing in my file changed between "works" and "trojan". What changed was the antivirus databases.
So the decision so far: the old file is cleared, the behaviour detection is still live, and the allowlist answer is pending. I sent Kaspersky the new detections on November 7th. On VirusTotal the current file shows 3 detections out of 75 engines on that day; Kaspersky's and Microsoft's scanners there don't flag it. GitHub's advisory still lists the package as malware, and npm hasn't answered at all yet.
Why an antivirus sees a trojan
Here is my program described the way a behaviour scanner sees it:
- it copies itself into the user's AppData folder;
- it adds itself to the startup list in the registry;
- it runs node.exe in the background with no window;
- it starts an unsigned helper, which starts other programs;
- some of those programs are PowerShell;
- it keeps a connection to a server and runs the commands that come down that connection.
That is the textbook description of a remote access trojan. And every line is true of Local Commander, for ordinary reasons. It starts at login so your computer is reachable when you need it. Commands run without a window because a window popping up for each one would cover whatever you're doing. The helper exists so that "stop" stops the whole tree of processes a command started, not just the first one.
The disclosure file that ships in my package says it in its first lines: "This is a dual-use tool. It receives commands over the network and runs them on the computer it is installed on."
In version 0.3.64 I removed the two tricks that looked most like code injection: starting a process suspended and then resuming it, and running batch files through an encoded PowerShell command. Detections went quiet for two days and then came back. The rule isn't about one trick. It's about the whole shape, and the shape is the product.
Kaspersky also quarantined a plain JavaScript file from my other product. There was no executable in it at all. That's also why I decided not to buy a code-signing certificate: a signature raises a file's reputation, but it doesn't switch off behaviour detection. I'd be paying every year for a maybe.
What the scanner can't see
The difference between my program and a trojan isn't in the code. It's in who installed it, who can send it work, and who can see and stop that work. A scanner can't see any of that. So I have to show it. Most people who hear about Local Commander have two questions anyway: what is it for, and won't I lose control of an AI on my machine.
What it's for. It started in ChatGPT. ChatGPT suggested a plugin, Desktop Commander, and suddenly I could work on my code from the chat. Then I watched Codex run several commands and terminals at once and wanted that too. Some of my commands kept failing when they ran side by side, so I wrote my own scheduling for them. That grew into Local Commander: a small agent on each of your computers, connected to the AI chat you already use.
Today I use it to build other MCP connectors and to debug machines that have no AI of their own, above all servers behind a secure perimeter that can't reach outside AI services.
One real case, recorded on my phone and shown unedited on the site. I asked ChatGPT whether it was safe to restart a Windows server. It checked the free disk space and who was logged in, found a user still working, and told me to wait. It restarted nothing.
How control works. This is the half the antivirus never sees:
- A computer joins your account only if someone at that computer approves it. Each computer has its own key that never leaves it. A copied link can't add a machine.
- Only AI apps you signed in to with your account can send work. Someone else's ChatGPT can't reach your computers. Your account lists every connected app and computer, and you can remove any of them.
- You choose what it can touch. At setup: full access to your user account, which is the default, or only the folders you pick. You can block specific commands. These limits live on the computer and can be widened only there, not from the chat, not from the website, not by the AI.
- You can see what happened. Every request that goes through localcmd.app shows up in your account: which computer, which tool, how it ended. The text of commands and their output is kept only if you turn on full logs, and then for 7 days.
- You can stop it. Ask the AI to cancel, or close Local Commander on that computer. If a computer drops in the middle of a task, the task is never re-run on a guess. It waits for you.
And the limits, said plainly. It isn't a sandbox. Full access means exactly the rights of the user account the agent runs under. A selected folder keeps the file tools inside it, but a command can reach anything that account can reach. On my servers the agent runs under a separate account made for it, and that account is what I'd cut off if something went wrong. Commands and results also pass through my service on the way to your AI.
What I do instead of hiding
I tell users not to restore the file from quarantine and not to add an antivirus exclusion for my program. If a vendor's rule is wrong, the vendor should fix it. My users shouldn't have to switch off their protection for me.
What I can do is describe the program so plainly that anyone, or their AI, can check it:
- the disclosure file in the package lists every file the agent writes, what runs, what it reads and where it connects;
- the npm package declares itself as a dual-use tool;
- since version 0.3.64 every release lists the fingerprints of its runtime files, and install refuses a missing or changed file;
- since version 0.3.67 the agent no longer reads other AI tools' session files at all. That reader is the token-counting feature from part 1.
The details are on the security page.
This isn't abstract. About 250 accounts have signed up, most of them since the end of October. Most connect it to ChatGPT, and nearly 90 ran something in the past week. When Kaspersky removes JobHost.exe, Local Commander stays connected but commands stop running. To the person at the keyboard it looks like my program is broken.
A question
If you ship something that legitimately runs commands on other people's machines, such as an agent, a remote-support tool or a CI runner: how did you get past behaviour-based antivirus without asking users for exclusions?
I'll add Kaspersky's allowlist answer here when it arrives. Part 3: what an incident like this would have cost a company before AI.
Local Commander is free for one person and up to five computers: localcmd.app
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.