[pkg firewall] add the product.json re-apply watcher - #27
Open
dekkagaijin wants to merge 1 commit into
Open
Conversation
Third code chunk. Nothing installs this yet — the generator wiring is a later
change.
product.json is the first config target in this repo that a third party rewrites
on its own schedule. VS Code replaces it on every update: roughly monthly for
stable, *nightly* for Insiders. That schedule is uncorrelated with MDM check-in,
so an MDM-check-in-only design would leave an Insiders box unprotected for part
of every day.
vscode_install_watcher / vscode_remove_watcher
_vscode_watcher_launchd macOS
_vscode_watcher_linux systemd, falling back to cron
vscode_state_set / _get / _report
Per platform:
macOS launchd plist, WatchPaths on each product.json *and* its parent
directory, plus StartInterval 3600 and RunAtLoad. The parent directory
matters because Squirrel replaces the whole bundle rather than editing
in place, so a file-only vnode watch goes stale. root:wheel 644 or
launchd refuses to load it.
Linux .service (oneshot) + .path (PathModified on files and parents) +
.timer (OnUnitActiveSec=1h). No systemd -> /etc/cron.hourly, with no
filename extension, because run-parts silently skips dotted names —
a job that installs and never runs looks exactly like success.
Both a sidecar KEY=VALUE state file (not JSON — this stays jq-free), 600,
outside the app bundle. Never write new files *inside* the bundle: an
added unsealed resource is worse for codesign --verify than a modified
one.
The residual race is not closable. If a developer relaunches VS Code between the
update and the watcher firing, that session talks to the public marketplace.
What the state file buys is making it *countable*: each re-apply bumps
repatch_count and stamps last_repatch, and a later run reports "watcher has
re-applied the patch 4x (last: ...)". An invisible race is the thing to avoid;
a counted one is a fact an admin can act on.
The plist and unit files are emitted with printf, not heredocs, because
generate.sh inlines this lib through `grep -v '^# ' | sed '/^ *$/d'` — a heredoc
body would have its blank lines and #-prefixed lines silently stripped.
Tests: 22 assertions. The plist is linted with plutil and read back with
PlistBuddy; the systemd units and the cron job are checked for the properties
that make them work at all (4 PathModified entries, oneshot, hourly backstop, no
filename extension). launchctl and systemctl are stubbed — loading a real system
daemon needs root and would touch the host — so what is under test is the content
of the files the lib writes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dekkagaijin
force-pushed
the
vscode-fw-4-watcher
branch
from
August 6, 2026 16:10
bcc2c07 to
4cceef5
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.
Merge after #26
Third code chunk. Nothing installs this yet — the generator wiring is a later change.
product.json is the first config target in this repo that a third party rewrites on its own schedule. VS Code replaces it on every update: roughly monthly for stable, nightly for Insiders. That schedule is uncorrelated with MDM check-in, so an MDM-check-in-only design would leave an Insiders box unprotected for part of every day.
vscode_install_watcher / vscode_remove_watcher
_vscode_watcher_launchd macOS
_vscode_watcher_linux systemd, falling back to cron
vscode_state_set / _get / _report
Per platform:
macOS launchd plist, WatchPaths on each product.json and its parent
directory, plus StartInterval 3600 and RunAtLoad. The parent directory
matters because Squirrel replaces the whole bundle rather than editing
in place, so a file-only vnode watch goes stale. root:wheel 644 or
launchd refuses to load it.
Linux .service (oneshot) + .path (PathModified on files and parents) +
.timer (OnUnitActiveSec=1h). No systemd -> /etc/cron.hourly, with no
filename extension, because run-parts silently skips dotted names —
a job that installs and never runs looks exactly like success.
Both a sidecar KEY=VALUE state file (not JSON — this stays jq-free), 600,
outside the app bundle. Never write new files inside the bundle: an
added unsealed resource is worse for codesign --verify than a modified
one.
The residual race is not closable. If a developer relaunches VS Code between the update and the watcher firing, that session talks to the public marketplace. What the state file buys is making it countable: each re-apply bumps repatch_count and stamps last_repatch, and a later run reports "watcher has re-applied the patch 4x (last: ...)". An invisible race is the thing to avoid; a counted one is a fact an admin can act on.
The plist and unit files are emitted with printf, not heredocs, because generate.sh inlines this lib through
grep -v '^# ' | sed '/^ *$/d'— a heredoc body would have its blank lines and #-prefixed lines silently stripped.Tests: 22 assertions. The plist is linted with plutil and read back with PlistBuddy; the systemd units and the cron job are checked for the properties that make them work at all (4 PathModified entries, oneshot, hourly backstop, no filename extension). launchctl and systemctl are stubbed — loading a real system daemon needs root and would touch the host — so what is under test is the content of the files the lib writes.
https://endorlabs.atlassian.net/browse/LM-452