agenshield 0.11.4 → 0.11.5-beta.540

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/agenshield +171 -13
  2. package/package.json +1 -1
package/bin/agenshield CHANGED
@@ -87,6 +87,16 @@ find_installed() {
87
87
  return 0
88
88
  done
89
89
  IFS="$OLDIFS"
90
+ # The macOS .pkg installs the real CLI at /Library/AgenShield/bin and only
91
+ # symlinks it onto PATH as /usr/local/bin/agenshield. A half-finished uninstall
92
+ # can delete that symlink while the binary itself survives — the PATH scan
93
+ # above then misses a perfectly runnable uninstaller. Check the canonical .pkg
94
+ # location directly so `uninstall` (and every other command) delegates to the
95
+ # real CLI instead of falling back to residue cleanup / bootstrap.
96
+ if [ -x "/Library/AgenShield/bin/agenshield" ]; then
97
+ echo "/Library/AgenShield/bin/agenshield"
98
+ return 0
99
+ fi
90
100
  return 1
91
101
  }
92
102
 
@@ -122,11 +132,143 @@ print_manual_cleanup_hint() {
122
132
  echo " sudo rm -rf /Library/AgenShield /opt/agenshield /Applications/AgenShield.app" >&2
123
133
  echo " rm -rf \"\$HOME/.agenshield\"" >&2
124
134
  echo " rm -f \"\$HOME/Library/LaunchAgents/com.frontegg.AgenShield.\"*.plist" >&2
135
+ echo " sudo security delete-certificate -c \"AgenShield CA\" /Library/Keychains/System.keychain" >&2
125
136
  echo " launchctl unsetenv NODE_EXTRA_CA_CERTS; unset NODE_EXTRA_CA_CERTS" >&2
126
137
  echo " Then fully quit & reopen your terminal and GUI apps (Cursor, Claude Desktop, VS Code)" >&2
127
138
  echo " so they drop the stale NODE_EXTRA_CA_CERTS value." >&2
128
139
  }
129
140
 
141
+ # ── macOS: remove AgenShield residue in-process (no reinstall) ──────────────
142
+ # Reached by `uninstall` when no runnable CLI binary remains but artifacts do.
143
+ # The thorough uninstaller lives in the CLI binary, but on macOS the only
144
+ # published artifact is the full signed .pkg — there is no CLI-only download to
145
+ # bootstrap, and installing the .pkg would REINSTALL the whole product (daemon +
146
+ # ES/NE system extensions + LaunchDaemons) just to tear it down, via a GUI
147
+ # installer that lays nothing down synchronously. So we reproduce the residue
148
+ # cleanup the CLI uninstaller performs, in-process. This mirrors
149
+ # tools/build/test-uninstall.sh and ADDS the System-keychain CA removal a plain
150
+ # `rm` skips (a leftover trusted root is a MITM risk). Best-effort throughout:
151
+ # every step is guarded so one failure never aborts the rest. It mutates
152
+ # privileged system state, so we confirm first (skip with -f/--force; a
153
+ # non-interactive pipe prints the manual hint instead of destroying silently).
154
+ macos_residue_uninstall() {
155
+ _force=0
156
+ for _a in "$@"; do
157
+ case "$_a" in -f|--force) _force=1 ;; esac
158
+ done
159
+
160
+ echo "agenshield: no runnable binary found, but AgenShield artifacts remain on this Mac." >&2
161
+ echo " This removes AgenShield: app, daemon, system extensions, CA trust, config, and data." >&2
162
+ if [ "$_force" != "1" ]; then
163
+ if [ -t 0 ] && [ -t 1 ]; then
164
+ printf " Continue? [y/N] " >&2
165
+ read -r _ans || _ans=""
166
+ case "$_ans" in
167
+ y | Y | yes | YES) : ;;
168
+ *)
169
+ echo " Cancelled — nothing was removed." >&2
170
+ return 0
171
+ ;;
172
+ esac
173
+ else
174
+ # Non-interactive and not --force: never destroy silently.
175
+ print_manual_cleanup_hint
176
+ return 0
177
+ fi
178
+ fi
179
+
180
+ # Privileged paths (/Library, /opt, the /usr/local symlink, the System
181
+ # keychain, system-domain launchd) need root; user-session paths do not.
182
+ SUDO=""
183
+ if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
184
+ if [ -n "$SUDO" ] && ! sudo -v 2>/dev/null; then
185
+ echo "agenshield: administrator access is required to finish removing AgenShield." >&2
186
+ print_manual_cleanup_hint
187
+ return 0
188
+ fi
189
+
190
+ # The GUI console user owns the menubar app + the launchd session env vars.
191
+ _cuser="$(stat -f %Su /dev/console 2>/dev/null || echo '')"
192
+ _cuid=""
193
+ if [ -n "$_cuser" ] && [ "$_cuser" != "root" ]; then
194
+ _cuid="$(id -u "$_cuser" 2>/dev/null || echo '')"
195
+ fi
196
+
197
+ echo "agenshield: removing AgenShield…" >&2
198
+
199
+ # 1. Stop the daemon/helper + the menubar and dashboard apps.
200
+ $SUDO launchctl bootout system/com.frontegg.AgenShield.daemon 2>/dev/null || true
201
+ $SUDO launchctl bootout system/com.frontegg.AgenShield.privilege-helper 2>/dev/null || true
202
+ if [ -n "$_cuid" ]; then
203
+ $SUDO launchctl bootout "gui/$_cuid/com.frontegg.AgenShield.menubar" 2>/dev/null || true
204
+ fi
205
+ killall AgenShield 2>/dev/null || true
206
+ killall "AgenShield Dashboard" 2>/dev/null || true
207
+
208
+ # 2. Ask the app (while it still exists) to deactivate the ES + NE system
209
+ # extensions — the only non-developer-mode path. Bounded so a pending
210
+ # approval prompt can't hang the uninstall (macOS /bin/sh has no `timeout`);
211
+ # if it can't finish, deleting the app schedules removal on next reboot.
212
+ if [ -x "/Applications/AgenShield.app/Contents/MacOS/AgenShield" ] && [ -n "$_cuid" ]; then
213
+ ($SUDO launchctl asuser "$_cuid" sudo -u "$_cuser" \
214
+ /Applications/AgenShield.app/Contents/MacOS/AgenShield --uninstall-all >/dev/null 2>&1) &
215
+ _deact=$!
216
+ _i=0
217
+ while [ "$_i" -lt 20 ]; do
218
+ kill -0 "$_deact" 2>/dev/null || break
219
+ sleep 1
220
+ _i=$((_i + 1))
221
+ done
222
+ kill -9 "$_deact" 2>/dev/null || true
223
+ fi
224
+
225
+ # 3. Remove the AgenShield CA from the System keychain (sweep by CN, delete by
226
+ # SHA-1) — mirrors the CLI's removeInstalledCaTrust.
227
+ $SUDO /usr/bin/security find-certificate -a -Z -c "AgenShield" /Library/Keychains/System.keychain 2>/dev/null |
228
+ while IFS= read -r _line; do
229
+ case "$_line" in
230
+ "SHA-1 hash: "*)
231
+ _sha1="${_line#SHA-1 hash: }"
232
+ $SUDO /usr/bin/security delete-certificate -Z "$_sha1" /Library/Keychains/System.keychain >/dev/null 2>&1 || true
233
+ ;;
234
+ esac
235
+ done
236
+
237
+ # 4. Clear the node-ca-trust LaunchAgent + the NODE_EXTRA_CA_CERTS it sets in
238
+ # the user's launchd session (it points at the CA we just removed).
239
+ if [ -n "$_cuid" ]; then
240
+ $SUDO launchctl bootout "gui/$_cuid/com.frontegg.AgenShield.node-ca-trust" 2>/dev/null || true
241
+ if [ "$(id -u)" -eq 0 ]; then
242
+ launchctl asuser "$_cuid" /bin/launchctl unsetenv NODE_EXTRA_CA_CERTS 2>/dev/null || true
243
+ else
244
+ /bin/launchctl unsetenv NODE_EXTRA_CA_CERTS 2>/dev/null || true
245
+ fi
246
+ fi
247
+
248
+ # 5. Remove LaunchDaemon plists + every AgenShield user LaunchAgent.
249
+ $SUDO rm -f /Library/LaunchDaemons/com.frontegg.AgenShield.daemon.plist \
250
+ /Library/LaunchDaemons/com.frontegg.AgenShield.privilege-helper.plist 2>/dev/null || true
251
+ rm -f "$HOME/Library/LaunchAgents/com.frontegg.AgenShield."*.plist 2>/dev/null || true
252
+
253
+ # 6. Remove binaries, the app, root config, and per-user data.
254
+ $SUDO rm -f /usr/local/bin/agenshield 2>/dev/null || true
255
+ $SUDO rm -rf /Library/AgenShield /opt/agenshield /Applications/AgenShield.app 2>/dev/null || true
256
+ rm -rf "$HOME/.agenshield" 2>/dev/null || true
257
+
258
+ echo "" >&2
259
+ echo "agenshield: AgenShield removed." >&2
260
+ # `launchctl unsetenv` only affects future launches; already-running processes
261
+ # keep the stale value until restarted.
262
+ if [ -n "${NODE_EXTRA_CA_CERTS:-}" ]; then
263
+ echo " This shell still has NODE_EXTRA_CA_CERTS set — run: unset NODE_EXTRA_CA_CERTS" >&2
264
+ fi
265
+ echo " Fully quit & reopen your terminal and GUI apps (Cursor, Claude Desktop, VS Code)" >&2
266
+ echo " so they drop the stale NODE_EXTRA_CA_CERTS value." >&2
267
+ if /usr/bin/systemextensionsctl list 2>/dev/null | grep -q "com.frontegg.AgenShield"; then
268
+ echo " Reboot to finish removing the AgenShield system extensions." >&2
269
+ fi
270
+ }
271
+
130
272
  # ── `install` → run the full installer with the user's args ─────────────────
131
273
  # install.sh handles the macOS .pkg / Linux tarball download + checksum verify
132
274
  # + enrollment (--token/--cloud-url/--org) + service setup.
@@ -147,27 +289,43 @@ if [ "${1:-}" = "install" ]; then
147
289
  exec env AGENSHIELD_VERSION="$WRAPPER_VERSION" sh "$INSTALLER" "$@"
148
290
  fi
149
291
 
150
- # ── `uninstall` → delegate if installed, else bootstrap+run when residue remains ─
292
+ # ── `uninstall` → delegate if installed, else clean up residue ──────────────
151
293
  # `npx agenshield uninstall` must remove AgenShield even after the binary itself
152
- # is gone but system residue remains. Three cases:
294
+ # is gone but system residue remains. Cases:
153
295
  # 1. A runnable binary exists → delegate to it (the real, thorough uninstaller).
154
- # 2. No binary, but residue bootstrap the signed binary from GitHub Releases
155
- # detected (--skip-services) and run the REAL uninstaller
156
- # the only thing that removes the keychain CA,
157
- # sandbox users, ACLs, the node-ca-trust LaunchAgent
158
- # + its NODE_EXTRA_CA_CERTS env, LaunchDaemons, etc.
159
- # If the bootstrap download fails (offline / no
160
- # published release), print manual cleanup steps.
161
- # 3. No binary, no residue → genuinely nothing to do; report and exit 0.
162
- # Bootstrapping ONLY when residue is present avoids the re-download churn the old
163
- # code guarded against: a successful uninstall removes the residue, so the next
164
- # run lands in case 3 and exits cleanly.
296
+ # 2. No binary, but residue, on macOS remove the residue IN-PROCESS
297
+ # (macos_residue_uninstall). macOS publishes only the full signed .pkg, so
298
+ # there is no CLI-only artifact to bootstrap, and installing the .pkg would
299
+ # REINSTALL the entire product (daemon + ES/NE system extensions +
300
+ # LaunchDaemons) via a GUI installer that lays nothing down synchronously —
301
+ # just to tear it down. Bootstrapping was the original design here; it
302
+ # surfaced the macOS "uninstall opened the installer GUI, then reported
303
+ # 'could not bootstrap the uninstaller'" bug. macos_residue_uninstall covers
304
+ # the keychain CA, system extensions, the node-ca-trust LaunchAgent + its
305
+ # NODE_EXTRA_CA_CERTS env, LaunchDaemons, dirs, and the dangling symlink.
306
+ # 3. No binary, but residue, on other platforms → bootstrap the CLI-only
307
+ # tarball (--skip-services) and run the REAL uninstaller; on a download
308
+ # failure (offline / no published release), print manual cleanup steps.
309
+ # 4. No binary, no residue → genuinely nothing to do; report and exit 0.
310
+ # Acting only when residue is present means a successful uninstall removes the
311
+ # residue, so a second run lands in case 4 and exits cleanly — no churn.
165
312
  if [ "${1:-}" = "uninstall" ]; then
166
313
  BIN="$(find_installed || true)"
167
314
  if [ -n "$BIN" ]; then
168
315
  exec "$BIN" "$@"
169
316
  fi
170
317
  if agenshield_residue_present; then
318
+ # No runnable CLI binary, but AgenShield residue remains. macOS cannot
319
+ # bootstrap the thorough uninstaller — the only published artifact is the
320
+ # full signed .pkg, and installing it would REINSTALL the whole product (via
321
+ # an interactive GUI installer that lays nothing down synchronously) just to
322
+ # remove it. So remove the residue in-process here. Other platforms publish a
323
+ # CLI-only tarball, so they can lay the binary down and run the real
324
+ # uninstaller below.
325
+ if [ "$OS" = "darwin" ]; then
326
+ macos_residue_uninstall "$@"
327
+ exit 0
328
+ fi
171
329
  if [ ! -f "$INSTALLER" ]; then
172
330
  echo "agenshield: leftover AgenShield artifacts detected, but the bundled installer is missing." >&2
173
331
  print_manual_cleanup_hint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenshield",
3
- "version": "0.11.4",
3
+ "version": "0.11.5-beta.540",
4
4
  "description": "AgenShield — AI Agent Security Platform",
5
5
  "bin": {
6
6
  "agenshield": "bin/agenshield"