Skip to content

[pkg firewall] add the product.json re-apply watcher - #27

Open
dekkagaijin wants to merge 1 commit into
vscode-fw-3-lifecyclefrom
vscode-fw-4-watcher
Open

[pkg firewall] add the product.json re-apply watcher#27
dekkagaijin wants to merge 1 commit into
vscode-fw-3-lifecyclefrom
vscode-fw-4-watcher

Conversation

@dekkagaijin

Copy link
Copy Markdown
Contributor

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

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
dekkagaijin force-pushed the vscode-fw-4-watcher branch from bcc2c07 to 4cceef5 Compare August 6, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant