package-firewall: add VS Code as a target ecosystem - #22
Draft
dekkagaijin wants to merge 1 commit into
Draft
Conversation
dekkagaijin
marked this pull request as draft
August 4, 2026 19:53
Routes VS Code and VS Code Insiders extension traffic through the Endor Package
Firewall on macOS, Linux and Windows, by patching the extensionsGallery object in
product.json.
Why product.json and not the ExtensionGalleryServiceUrl enterprise policy — that
policy looks like the obvious fit, but (a) it expects the URL to return an
IExtensionGalleryManifest document rather than a marketplace API root, (b) VS Code
gates it behind a signed-in GitHub account holding a Copilot Business/Enterprise
seat, and a failed gate disables the Extensions view entirely, and (c) it is never
consulted by `code --install-extension`. Details and what would unblock it are in
package-firewall/docs/vscode-enterprise-policy.md.
The patch sets one key and removes one:
serviceUrl -> the firewall's /firewall/vscode/_ak/<token> endpoint;
VS Code derives both /extensionquery and
/vscode/{publisher}/{name}/latest from it
extensionUrlTemplate -> removed. It is the fallback VS Code uses when the
resource API returns 5xx; left in place, a firewall
outage would silently resolve versions straight from
www.vscode-unpkg.net. Removing it redirects that
failure into the firewall's own extensionquery.
controlUrl (Microsoft's malicious-extension revocation list), resourceUrlTemplate,
itemUrl, publisherUrl, nlsBaseUrl, mcpUrl and accessSKUs are deliberately left
alone, and the writer merges key-by-key so keys added by future VS Code versions
survive.
Because product.json is JSON it can carry neither a sentinel comment nor an env-var
reference, so the managed marker is a top-level key that also stores the original
extensionsGallery verbatim — removal restores it byte-for-byte. Re-running is a
no-op when current; a rotated credential is detected as stale and triggers
restore-then-patch, so the captured original is never lost.
VS Code replaces product.json on every update (monthly for stable, nightly for
Insiders), so an update watcher re-applies the patch: launchd WatchPaths on macOS,
a systemd .path/.timer pair (or hourly cron) on Linux, a Scheduled Task on Windows.
The remaining race is made countable via repatch_count rather than left invisible.
New prerequisites, called out in the READMEs:
- macOS Ventura+ needs the App Management (SystemPolicyAppBundles) TCC grant for
the MDM agent; root is not exempt. The script detects EPERM and says so.
- The gallery token lands in world-readable product.json and cannot not — VS Code
offers no indirection. Use a dedicated, separately revocable API key.
- Extension downloads still come from Microsoft's CDN by design, so *.vsassets.io
and *.vscode-unpkg.net must stay reachable.
- codesign --verify will report the bundle as modified. Expected; do not re-sign.
endor-vscode.* is deliberately not folded into endor-all.*: it is the only script
that writes inside an application bundle and the only one installing a persistent
daemon.
Also fixes stale documentation predating the user-attribution change: the
placeholder tables conflated generation-time and install-time tokens, and both
READMEs still described pip/uv/go credentials as env-var references.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dekkagaijin
force-pushed
the
vscode-firewall
branch
from
August 6, 2026 23:42
02aa9c1 to
e66c4ff
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://endorlabs.atlassian.net/browse/LM-452
Add VS Code as a package-firewall target ecosystem
Routes VS Code and VS Code Insiders extension traffic through the Endor Package Firewall on
macOS, Linux and Windows. Closes the VS Code extension firewall backlog item.
Why not the
ExtensionGalleryServiceUrlenterprise policyThe backlog item proposed the enterprise policy, and it looks like the obvious fit — MDM-native,
survives updates, not user-editable. Verified against the shipping VS Code 1.131.0 bundle and
upstream
release/1.99+main, it can't carry this:getExtensionGalleryManifestFromServiceUrl()GETs the policy value and parses it as
IExtensionGalleryManifest. Our endpoint is the_apis/public/galleryanalogue.requiresSignIn; otherwisecheckAccess()requires acopilot_enterprise_*/copilot_for_business_*seat SKU oraccount.enterprise. A failed gate yields anullmanifest, which disables the Extensions view entirely — worse than not deploying. Present
since 1.99.
code --install-extensionnever consults it. The policy name appears only inworkbench.desktop.main.js;cliProcessMain.jsreadsproductService.extensionsGallery.serviceUrldirectly.
Recorded in
package-firewall/docs/vscode-enterprise-policy.mdso this doesn't get re-investigated, along with what would unblock it.
What the patch does
One key set, one removed — everything else left as VS Code shipped it, including keys added by
future versions (the writer merges key-by-key, never replacing the object):
serviceUrl${serviceUrl}/extensionqueryand${serviceUrl}/vscode/{publisher}/{name}/latestfrom itextensionUrlTemplatewww.vscode-unpkg.net— bypassing the firewall exactly when it's unhealthy. Removed, that failure retries our ownextensionqueryand fails the install if that fails too. Fail-closed.controlUrlresourceUrlTemplate,itemUrl,publisherUrl,nlsBaseUrl,mcpUrl,accessSKUsWorth noting on the
400the factory returns when a version is blocked: that status is exactlyright.
400classifies asClientError, which is re-thrown before the unpkg fallback isconsulted, so blocked extensions can never reach Microsoft. Only 5xx could, which is what
removing
extensionUrlTemplatecloses.Managed marker, not a sentinel block
product.jsonis JSON, so it can carry neither a#comment nor an${ENDOR_*}reference. Thescripts add one top-level key holding the original
extensionsGalleryverbatim (base64), soremoval restores it byte-for-byte. Re-running is a genuine no-op when current; a rotated
credential is detected as
staleand triggers restore-then-patch, so the captured originalsurvives any number of rotations.
Update watcher
VS Code replaces
product.jsonon every update — monthly for stable, nightly for Insiders —uncorrelated with MDM check-in, so a watcher re-applies it: launchd
WatchPaths(macOS), systemd.path/.timeror hourly cron (Linux), Scheduled Task (Windows). Each watches the file and itsparent directory, since the updater swaps the whole directory.
The residual race isn't closable — if a developer relaunches VS Code before the watcher fires, that
session talks to the public marketplace. It's made countable instead of invisible: each re-apply
bumps
repatch_count, and later runs printwatcher has re-applied the patch 4x (last: …).--no-vscode-watcher/-NoVSCodeWatcheropts out loudly but still exits 0 — failing everycheck-in over a deliberate setting is alert fatigue.
New prerequisites — please review these specifically
SystemPolicyAppBundles) TCC grant for the MDMagent. Root is not exempt. This is a prerequisite the repo has never had; without it the
script fails loudly with an explanation rather than silently no-op'ing.
product.json(0644) and cannot not. VS Code readsit as the user and offers no indirection. Any local user can read a working firewall credential,
and it appears in VS Code's own logs. Mitigation is blast radius only: use a dedicated,
separately revocable API key for VS Code.
filtering-by-omission, so
*.vsassets.io/*.vscode-unpkg.netmust stay reachable..vsixaren'tretroactively caught.
codesign --verifywill report the bundle as modified. Expected. Do not re-sign — ad-hocre-signing strips hardened-runtime entitlements and would durably break stored GitHub auth.
endor-vscode.*is deliberately not folded intoendor-all.*: it's the only script thatwrites inside an application bundle and the only one installing a persistent daemon, so folding it
in would silently widen the blast radius of every existing
endor-alldeployment.Testing
263 assertions across 7 suites, all passing. Exercised against the real
/Applications/Visual Studio Code.appproduct.json(1.131.0) plus synthetic fixtures(4-space-indented, single-entry, minified, CRLF).
Covered end-to-end: install → idempotent re-run → simulated VS Code update → watcher repatch →
credential rotation → byte-exact removal, plus the EPERM/TCC path, snap/flatpak refusal, 5xx
fail-closed behaviour, and dry-run token redaction.
Six real bugs were caught by these tests and fixed: a gawk-only
for-init that BSD awk rejects;trailing-newline fidelity (shipped
product.jsonhas none, awk always adds one, which would havemade byte-exact restore impossible); the node writer's pretty-printed marker being unreadable by
the single-line marker parser;
set -eaborting the generated script on the benign"already current" return; PowerShell's
ConvertFrom-Jsonsilently accepting trailing commas; andthe PS template recomputing the attributed token in repatch mode, where no console user exists —
which would have minted a token attributed to nobody.
Not included / follow-ups
scope creep on top of an already large change — happy to add them under
package-firewall/tests/in a follow-up if you'd like them, and given what they caught I'd recommend it.
pwsh7.6.4 was used to exercise the JSONediting, state machine, marker handling, both writer paths and the template bodies. Scheduled Task
registration and
%ProgramFiles%/ AppData discovery cannot run off-Windows and are explicitlyskipped — those two need a Windows box before shipping.
generation-time and install-time placeholders and still described pip/uv/go credentials as env-var
references.