@vigilbase/cli 0.2.2 → 0.2.4

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 (77) hide show
  1. package/CLOUDFLARE.md +2 -2
  2. package/COMMANDS.md +128 -42
  3. package/LOCAL.md +99 -38
  4. package/ONE.md +1 -1
  5. package/README.md +217 -66
  6. package/SECURITY.md +1 -1
  7. package/dist/auth/acl.d.ts +6 -0
  8. package/dist/auth/acl.js +45 -0
  9. package/dist/auth/device-auth.d.ts +3 -0
  10. package/dist/auth/device-auth.js +13 -3
  11. package/dist/auth/provider.js +4 -4
  12. package/dist/auth/refresh.d.ts +5 -4
  13. package/dist/auth/refresh.js +82 -41
  14. package/dist/auth/store.d.ts +25 -0
  15. package/dist/auth/store.js +91 -11
  16. package/dist/bin.js +13 -1
  17. package/dist/cli.js +205 -81
  18. package/dist/commands/account.js +11 -7
  19. package/dist/commands/auth.d.ts +6 -0
  20. package/dist/commands/auth.js +75 -24
  21. package/dist/commands/billing.js +2 -2
  22. package/dist/commands/cloudflare.d.ts +1 -1
  23. package/dist/commands/cloudflare.js +31 -17
  24. package/dist/commands/helpers.js +3 -3
  25. package/dist/commands/index.d.ts +1 -0
  26. package/dist/commands/index.js +2 -1
  27. package/dist/commands/legacy-validation.d.ts +20 -0
  28. package/dist/commands/legacy-validation.js +77 -3
  29. package/dist/commands/local.js +186 -65
  30. package/dist/commands/one.js +16 -13
  31. package/dist/commands/public-tools.js +66 -35
  32. package/dist/commands/runs.js +5 -5
  33. package/dist/commands/security.d.ts +1 -1
  34. package/dist/commands/security.js +41 -37
  35. package/dist/commands/watchdog.js +5 -5
  36. package/dist/lib/banner.d.ts +5 -11
  37. package/dist/lib/banner.js +11 -31
  38. package/dist/lib/client.d.ts +9 -0
  39. package/dist/lib/client.js +31 -2
  40. package/dist/lib/cloudflare-client.d.ts +2 -2
  41. package/dist/lib/config.d.ts +16 -0
  42. package/dist/lib/config.js +77 -19
  43. package/dist/lib/format.d.ts +5 -1
  44. package/dist/lib/format.js +110 -35
  45. package/dist/lib/help.js +57 -6
  46. package/dist/lib/network.d.ts +12 -3
  47. package/dist/lib/network.js +73 -9
  48. package/dist/lib/one-client.d.ts +2 -0
  49. package/dist/lib/one-client.js +86 -12
  50. package/dist/lib/open-url.js +2 -1
  51. package/dist/lib/platform.d.ts +2 -0
  52. package/dist/lib/platform.js +5 -0
  53. package/dist/lib/progress.d.ts +18 -3
  54. package/dist/lib/progress.js +72 -10
  55. package/dist/lib/system-trust.d.ts +2 -0
  56. package/dist/lib/system-trust.js +6 -0
  57. package/dist/lib/tui.d.ts +1 -1
  58. package/dist/lib/tui.js +5 -7
  59. package/dist/local/cloudflare-checkup/cf/child-env.js +2 -4
  60. package/dist/local/cloudflare-checkup/cf/install.d.ts +4 -2
  61. package/dist/local/cloudflare-checkup/cf/install.js +55 -24
  62. package/dist/local/cloudflare-checkup/cf/locate.d.ts +2 -0
  63. package/dist/local/cloudflare-checkup/cf/locate.js +10 -1
  64. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +18 -3
  65. package/dist/local/cloudflare-checkup/cf/run-cf.js +75 -17
  66. package/dist/local/cloudflare-checkup/contract.d.ts +16 -9
  67. package/dist/local/cloudflare-checkup/contract.js +80 -14
  68. package/dist/local/cloudflare-checkup/errors.d.ts +34 -1
  69. package/dist/local/cloudflare-checkup/errors.js +36 -5
  70. package/dist/local/cloudflare-checkup/output.d.ts +6 -0
  71. package/dist/local/cloudflare-checkup/output.js +9 -0
  72. package/dist/local/cloudflare-checkup/snapshot-file.d.ts +6 -2
  73. package/dist/local/cloudflare-checkup/snapshot-file.js +25 -11
  74. package/dist/local/cloudflare-checkup/submit-client.js +7 -1
  75. package/dist/types.d.ts +6 -1
  76. package/esm-only.cjs +3 -0
  77. package/package.json +7 -4
package/ONE.md CHANGED
@@ -95,4 +95,4 @@ vigilbase support tickets [--all] [--limit 1..100] [--customer-id <uuid>] [--jso
95
95
 
96
96
  ## Related commands and validation
97
97
 
98
- See [README.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/README.md) for sign-in and configuration, [COMMANDS.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/COMMANDS.md) for account, billing, public scanners, run history and Watchdog, [CLOUDFLARE.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/CLOUDFLARE.md) for `cf`/`cloudflare`, and [SECURITY.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/SECURITY.md) for security commands. The read operations above do not replace the existing explicitly confirmed write commands.
98
+ See [README.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/README.md) for sign-in and configuration, [COMMANDS.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/COMMANDS.md) for account, billing, public scanners, run history and Watchdog, [CLOUDFLARE.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/CLOUDFLARE.md) for `cf`/`cloudflare`, and [SECURITY.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/SECURITY.md) for security commands. The read operations above do not replace the existing explicitly confirmed write commands.
package/README.md CHANGED
@@ -28,14 +28,17 @@ supported on Windows yet; public tools and `cloudflare-checkup` still work there
28
28
 
29
29
  | Reference | Covers |
30
30
  | --- | --- |
31
- | [COMMANDS.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/COMMANDS.md) | Core commands, aliases, global options, API mapping, sign-in, output |
32
- | [CLOUDFLARE.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/CLOUDFLARE.md) | Accounts, zones, DNS, WAF, analytics, Zero Trust, Workers/Pages, audit, checkups |
33
- | [SECURITY.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/SECURITY.md) | Incidents, logs, intelligence, analytics, assets, connections, policies, notifications, reports |
34
- | [ONE.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/ONE.md) | Customer records, billing, reports, investigations, organizations, monitoring, support |
35
- | [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/LOCAL.md) | `cloudflare-checkup`: verifies your work email, collects your Cloudflare configuration on your machine with Cloudflare's `cf` and uploads it after you confirm |
31
+ | [COMMANDS.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/COMMANDS.md) | Core commands, aliases, global options, API mapping, sign-in, output |
32
+ | [CLOUDFLARE.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/CLOUDFLARE.md) | Accounts, zones, DNS, WAF, analytics, Zero Trust, Workers/Pages, audit, checkups |
33
+ | [SECURITY.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/SECURITY.md) | Incidents, logs, intelligence, analytics, assets, connections, policies, notifications, reports |
34
+ | [ONE.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/ONE.md) | Customer records, billing, reports, investigations, organizations, monitoring, support |
35
+ | [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/LOCAL.md) | `cloudflare-checkup`: verifies your work email, collects your Cloudflare configuration on your machine with Cloudflare's `cf` and uploads it after you confirm |
36
36
 
37
37
  These files ship with the package. Built-in help: `vigilbase <command> --help`
38
- or `vigilbase help <command>`; `vigilbase help <tool>` shows how to run a tool.
38
+ or `vigilbase help <command>`; `vigilbase help <command> <subcommand>` (for
39
+ example `vigilbase help cf zones`) shows the same help as
40
+ `vigilbase cf zones --help`, and `vigilbase help <tool>` shows how to run a
41
+ tool. The library is ESM-only: use `import()`, not `require()`.
39
42
 
40
43
  ## Examples
41
44
 
@@ -143,7 +146,7 @@ read-only Cloudflare API token instead. When you are done:
143
146
  npx @vigilbase/cli cloudflare-checkup logout # removes the read-only cf login and the cf it installed
144
147
  ```
145
148
 
146
- See [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.2/files/LOCAL.md) for
149
+ See [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/LOCAL.md) for
147
150
  every step, what is uploaded and the error codes.
148
151
 
149
152
  ## Public tools and sign-in
@@ -152,48 +155,106 @@ every step, what is uploaded and the error codes.
152
155
  `run security-headers|email-security` call the public API anonymously and
153
156
  never read your saved sign-in, so an expired, broken or unreachable sign-in
154
157
  cannot make them fail. `--public` and `--via public` force the public API for
155
- any tool.
158
+ the public tools (`security-headers`, `email-security`).
156
159
  - **One-only scans need a sign-in.** `website-security`, `react2shell`,
157
160
  `fortibleed-check` and any run with `--via one` go through Vigilbase One.
158
161
  Without a usable sign-in they stop with
159
- ``This tool needs a Vigilbase One sign-in: run `vigilbase login`.``
162
+ ``This command needs a Vigilbase One sign-in: run `vigilbase login`.``
163
+ (code `NOT_SIGNED_IN`), as every One command does. With `--public` or
164
+ `--via public` a One-only tool stops before any request:
165
+ ``website-security runs only through Vigilbase One: drop --public and run `vigilbase login`.``
166
+ `run` with a tool name it doesn't know fails locally with
167
+ ``Unknown tool "<name>". Run `vigilbase list` for the allowlist.``
168
+ (code `INVALID_ARGUMENT`).
169
+ - **Scan quota.** Public scans share an hourly quota of 20 scans per client IP
170
+ address and 20 per target (1,000 an hour overall). When it is used up,
171
+ vigilbase.com answers HTTP 429 (`rate_limit_exceeded`) and the CLI exits 2
172
+ with the delay until the quota resets (up to an hour; `retryAfterSec` in
173
+ JSON). The CLI never retries on its own.
160
174
  - **`list`** reads your saved sign-in from disk only (no refresh, no network,
161
175
  no waiting for another command) to show which tools you can run. If it can't
162
176
  use it, it lists the tools as signed out and prints one line on stderr:
163
177
  ``Couldn't use your saved sign-in (<reason>); running anonymously. Run `vigilbase login` to fix.``
164
178
  When the reason is an unsafe config path or credentials file, the line ends
165
179
  with its fix instead (see [Directory checks](#directory-checks)).
166
- - **`doctor`** reports a sign-in problem as its own failed `signIn` check.
180
+ - **`doctor`** reports a sign-in problem as its own failed `signIn` check,
181
+ including a sign-in saved for another One origin, Identity issuer or client
182
+ ID: ``Saved for a different One origin, Identity issuer or client ID. Pass the matching --one-url, --auth-url and --client-id, or run `vigilbase login`.``
167
183
 
168
184
  ## Automation and output
169
185
 
170
186
  Every command supports `-j` / `--json`. JSON goes to stdout; progress and
171
- browser instructions go to stderr. Progress animations are off for JSON, CI and
172
- output that isn't a terminal. List commands never fetch every page on their
173
- own: keep the JSON pagination fields to fetch the next page. Text output shows
174
- complete identifiers. Human-readable output removes terminal control sequences
175
- and bidirectional overrides from everything it prints, including names, tool
176
- results, skills and errors. JSON keeps successful response data, with control
177
- characters escaped.
178
-
179
- `vigilbase --version` prints the banner in a terminal and only the version
180
- number when stdout is a pipe or a file, so `v=$(vigilbase --version)` works in
181
- scripts. With `--json` it prints `{"name":"@vigilbase/cli","version":"…"}`.
187
+ browser instructions go to stderr. Errors are JSON on stdout whenever `--json`
188
+ or `-j` is given, also inside a cluster of short flags (`-hj`) and as
189
+ `--json=…`; a `--json` after `--` is an argument, not the flag. Commands are
190
+ matched in any case (`vigilbase LIST` is `vigilbase list`).
191
+
192
+ Progress animations are off for `--json`, for output that isn't a terminal, in
193
+ CI (`CI` set to anything but `0` or `false`, in any case), with `TERM=dumb` and
194
+ with `VIGILBASE_CLI_NO_PROGRESS=1`. `NO_COLOR` doesn't turn them off, because
195
+ the spinner has no colour. The spinner fits the terminal width, and Ctrl-C
196
+ during a scan clears its line and exits 130. The same CI rule decides the
197
+ checkup's terminal UI and its colours and opening a browser (login, billing),
198
+ so `CI=yes` neither animates nor opens a browser.
199
+
200
+ List commands never fetch every page on their own: keep the JSON pagination
201
+ fields to fetch the next page. Text output shows complete identifiers: the
202
+ first column of `runs list` works with `runs get`, and `watchdog list` starts
203
+ with the target ID that `watchdog scans --target-id` takes.
204
+
205
+ Human-readable output removes terminal control sequences, bidirectional
206
+ overrides and invisible characters (zero-width characters, word joiners, the
207
+ byte order mark, U+2028/U+2029 line separators and Unicode tag characters) from
208
+ everything it prints, including names, tool results, skills and errors. Server
209
+ text in scan results never starts a line of its own: every line of a message is
210
+ indented. JSON keeps successful response data, with the same characters escaped
211
+ as `\uXXXX`, so values stay lossless.
212
+
213
+ `vigilbase --version` and `vigilbase help` print the banner only in a terminal.
214
+ When stdout is a pipe or a file, `--version` prints only the version number and
215
+ help starts at `Usage:`, so `v=$(vigilbase --version)` works in scripts. With
216
+ `--json`, `--version` prints `{"name":"@vigilbase/cli","version":"…"}`. The
217
+ banner is plain text.
182
218
 
183
219
  | Exit code | Meaning |
184
220
  | --- | --- |
185
- | `0` | Success (including a signed-out `whoami`) |
186
- | `1` | Validation, API, network or sign-in error, or an unsuccessful scan result |
221
+ | `0` | Success (including a signed-out `whoami`, and a graded scan whatever its grade) |
222
+ | `1` | Validation, API, network or sign-in error, or a scan that failed or couldn't be assessed |
187
223
  | `2` | Rate limited (`HTTP 429`) |
224
+ | `130`, `143`, `129` | `cloudflare-checkup` cancelled by Ctrl-C (SIGINT), SIGTERM or SIGHUP (128 + the signal number) |
188
225
 
189
- Errors keep a safe status, error code and retry delay. When a public scan
190
- fails and vigilbase.com explains why, the CLI shows that reason:
191
- `Error: <message> (<code>)` in text, and `error` plus `code` in JSON. The
192
- reason is shown only when vigilbase.com sends it as a known error code, and it
193
- is cleaned to one line of at most 300 characters. Anything else, and every
194
- Vigilbase One error, prints only `Request failed (HTTP <status>).` without the
195
- server's text. Hints (`Hint: …`, `hint` in JSON) are always written by the CLI;
196
- COMMANDS.md lists the One errors that get one.
226
+ A graded scan exits 0 even with an F. Its first line starts with `✓` only when
227
+ the result's `status` is `secure` and the grade isn't F; otherwise it starts
228
+ with `•`. Scripts should gate on `.grade` or `.result.status` in the JSON.
229
+
230
+ Errors keep a safe status, error code and retry delay. These codes come from
231
+ the CLI itself:
232
+
233
+ | Code | Meaning |
234
+ | --- | --- |
235
+ | `NOT_SIGNED_IN` | A command that needs Vigilbase One has no usable sign-in |
236
+ | `SIGN_IN_ENDED` | Vigilbase Identity ended the saved sign-in, and `identity-credentials.json` was removed |
237
+ | `SIGN_IN_UNSUPPORTED` | Signing in isn't supported on Windows yet |
238
+ | `UNKNOWN_OPTION` | An option the CLI doesn't know, with `Did you mean --x?` when one is close |
239
+ | `INVALID_ARGUMENT` | A missing or unexpected option value (`--timeout needs a value.`, `--json does not take a value.`), an invalid value, an unknown subcommand or a token that isn't valid |
240
+ | `UNEXPECTED_REDIRECT` | An origin answered with a redirect, which the CLI never follows |
241
+ | `BLOCKED_PORT` | Node's fetch refuses the port (see [Troubleshooting](#troubleshooting)) |
242
+ | `TIMEOUT` | A request took longer than `--timeout` |
243
+
244
+ Network failures otherwise carry Node's code (for example `ENOTFOUND` or
245
+ `ECONNREFUSED`), and `cloudflare-checkup` has codes of its own (LOCAL.md).
246
+
247
+ When a public scan fails and vigilbase.com explains why, the CLI shows that
248
+ reason: `Error: <message> (<code>)` in text, and `error` plus `code` in JSON.
249
+ The reason is shown only when vigilbase.com sends it as a known error code, and
250
+ it is cleaned to one line of at most 300 characters. Anything else, and every
251
+ Vigilbase One error, prints a message the CLI wrote (for example
252
+ `You don't have access to this.` or `Request failed (HTTP <status>).`), never
253
+ the server's text. The exception is a scan the scanner couldn't assess (for
254
+ example a bot verification challenge), on the public API or through One: the
255
+ CLI prints `✗ <tool> · <target> · n/a` and the scanner's reason, cleaned the
256
+ same way (code `scan_unassessed`). Hints (`Hint: …`, `hint` in JSON) are always
257
+ written by the CLI; COMMANDS.md lists the One errors that get one.
197
258
 
198
259
  Commands use named One APIs and One's own authorization. They never take
199
260
  Cloudflare or other provider secrets, delegated identity claims or an arbitrary
@@ -226,12 +287,26 @@ command arguments can show up in process listings and shell history; neither
226
287
  combines with `--token-stdin`. Login checks the token with One before saving
227
288
  it.
228
289
 
290
+ A token must be one value of printable ASCII (no spaces or control
291
+ characters), at most 16 KiB, whether it comes from `--token-stdin`, `--token`
292
+ or `VIGILBASE_ONE_TOKEN`. Otherwise login stops before any request with
293
+ `The token must be a single value of printable ASCII characters.`,
294
+ `The token exceeds 16 KiB.` or `The token must not be empty.` (code
295
+ `INVALID_ARGUMENT`); an empty `VIGILBASE_ONE_TOKEN` counts as unset. If an Identity
296
+ sign-in saved with other `--auth-url`, `--one-url` or `--client-id` values
297
+ exists, a token login or a legacy device login stops before it sends the token
298
+ or asks for a device code:
299
+ `A saved Identity sign-in belongs to <issuer> for <One origin> (client ID <id>). Pass the matching --auth-url, --one-url and --client-id, or log out with them first.`
300
+
229
301
  ### Where credentials are stored
230
302
 
231
303
  Credentials live in the config directory: `VIGILBASE_CONFIG_DIR` if set,
232
304
  otherwise `$XDG_CONFIG_HOME/vigilbase`, otherwise `~/.config/vigilbase`.
233
305
  `VIGILBASE_CONFIG_DIR` must be an absolute path; a relative `XDG_CONFIG_HOME`
234
- is ignored.
306
+ is ignored. A set but empty `VIGILBASE_CONFIG_DIR` doesn't fall back: when the
307
+ credentials are needed it is an error
308
+ (`VIGILBASE_CONFIG_DIR is empty. Set it to an absolute path or unset it.`). If
309
+ `HOME` is relative and nothing else is set, the error names `HOME`.
235
310
 
236
311
  | File | Holds |
237
312
  | --- | --- |
@@ -286,6 +361,11 @@ that passes the [directory checks](#directory-checks) but can't be parsed.
286
361
  warning, without being revoked; an unsafe file stops logout and stays in
287
362
  place.
288
363
 
364
+ With nothing saved, logout creates no directories. `logout --json` prints
365
+ `{success, cleared, warnings}`: `warnings` lists the warnings text mode prints
366
+ on stderr (an unparseable file removed without revoking, a failed legacy
367
+ sign-out) and is `[]` when there are none.
368
+
289
369
  Saved Identity credentials belong to one issuer, client and One. If you signed
290
370
  in with `--auth-url`, `--one-url` or `--client-id`, log out with the same
291
371
  options; otherwise logout stops, before revoking anything, with
@@ -295,35 +375,60 @@ options; otherwise logout stops, before revoking anything, with
295
375
 
296
376
  Identity access tokens last five minutes. The CLI refreshes within 60 seconds
297
377
  of expiry, or once after a 401. The lock coordinates CLI processes, and each
298
- holder rereads the file before rotating the refresh token. If a refresh may
299
- have been used but its answer was lost, the CLI marks it and asks you to run
300
- `vigilbase login` again rather than retrying it. A command waits up to 15
301
- seconds (or `--timeout`, if shorter) for the lock, then stops with
302
- `Credentials are locked by another command. …`. `login` and `logout` may
303
- remove a lock left by a process that has exited, but never one held by a
304
- running process. If a damaged lock can't be recovered, stop every `vigilbase`
305
- process, delete `credentials.json.lock` and log in again. Never reuse a refresh
306
- token by hand.
378
+ holder rereads the file before rotating the refresh token.
379
+
380
+ - A command waits up to 15 seconds (or `--timeout`, if shorter) for a lock
381
+ held by a running command, then stops with
382
+ ``Credentials are locked by another vigilbase command (process <pid>). If none is running, delete <path> and run `vigilbase login`.``
383
+ - If the command holding the lock has exited (an interrupted refresh, for
384
+ example after Ctrl-C), other commands stop at once with
385
+ ``A previous vigilbase command was interrupted while it held <path>. Run `vigilbase login` to recover.``
386
+ `login` and `logout` replace such a lock, but never one held by a running
387
+ command.
388
+ - An empty or unreadable lock stops with
389
+ ``<path> is damaged. Stop every vigilbase command, delete it, and run `vigilbase login`.``
390
+ `login` and `logout` replace one older than 30 seconds.
391
+ - If a refresh request gets no answer (a timeout or a dropped connection),
392
+ Identity may have rotated the token already, so the first error says so:
393
+ ``The Identity refresh may not have completed (…). Run `vigilbase login` again; its token cannot be retried safely.``
394
+ A definite refusal (a 4xx other than `invalid_grant`) leaves the refresh
395
+ retryable; a 5xx counts as uncertain.
396
+ - An `invalid_grant` answer (for example after a browser sign-out ended the
397
+ session) removes `identity-credentials.json`. The command then fails with
398
+ ``Your saved sign-in was ended by Vigilbase Identity and has been removed. Run `vigilbase login` again.``
399
+ (code `SIGN_IN_ENDED`), while `whoami` reports you as signed out (exit 0).
400
+
401
+ Never reuse a refresh token by hand.
307
402
 
308
403
  ### Directory checks
309
404
 
310
- Commands that read or write credentials (`login`, `logout`, `whoami`, every One
311
- command, `--via one` and the One-only scans) check the whole config path first:
405
+ Commands that read or write credentials (`login`, `logout`, every One command,
406
+ `--via one` and the One-only scans, and `whoami` when credentials may be there)
407
+ check the whole config path first:
312
408
 
313
409
  - no component of the path may be a symbolic link;
314
410
  - every parent directory must be owned by you or root, and not writable by its
315
411
  group or by others unless it has the sticky bit;
316
412
  - the config directory must be owned by you with mode `0700`;
317
413
  - credential files must be regular files owned by you, with one hard link and
318
- mode `0600`.
414
+ mode `0600`;
415
+ - on macOS, the config directory and credential files must have no access
416
+ control list (the CLI removes inherited entries from what it creates), and no
417
+ parent may have an ACL entry that lets another user add, remove or replace
418
+ its entries.
319
419
 
320
420
  Each error names the path that failed and how to fix it, for example:
321
421
 
322
422
  ```
323
423
  Error: /tmp is a symbolic link. Set VIGILBASE_CONFIG_DIR to a private directory without symbolic links in its path.
324
424
  Error: /Users/jane/.config/vigilbase must have mode 0700. Run chmod 700 /Users/jane/.config/vigilbase, or set VIGILBASE_CONFIG_DIR to another directory.
425
+ Error: /Users/jane/.config/vigilbase has an access control list. Run chmod -N /Users/jane/.config/vigilbase, or set VIGILBASE_CONFIG_DIR to another directory.
426
+ Error: /Users/jane/Shared is writable by other users through its access control list. Set VIGILBASE_CONFIG_DIR to a private directory without symbolic links in its path.
325
427
  ```
326
428
 
429
+ Paths with spaces, quotes or other special characters are shell-quoted in the
430
+ suggested commands.
431
+
327
432
  On macOS `/tmp` and `/var` are symbolic links, and dotfiles managers often make
328
433
  `~/.config` one. Fix the mode with the `chmod` the error names, or point
329
434
  `VIGILBASE_CONFIG_DIR` at a private directory of its own, for example:
@@ -338,11 +443,12 @@ files hold your refresh token.
338
443
 
339
444
  Public commands don't need these checks: public scans, `describe`, `skill` and
340
445
  `badge` never open the config directory, and work even when
341
- `VIGILBASE_CONFIG_DIR` isn't absolute. `list` and `doctor` never create or open
342
- anything in a directory that fails them, and only treat it as a problem when a
343
- credentials file may be there: `list` then shows you as signed out with one
344
- warning, and `doctor` reports it in its `signIn` check. With no saved sign-in,
345
- both carry on as usual.
446
+ `VIGILBASE_CONFIG_DIR` isn't absolute. `list`, `doctor` and `whoami` never
447
+ create or open anything in a directory that fails them, and only treat it as a
448
+ problem when a credentials file may be there: `list` then shows you as signed
449
+ out with one warning, `doctor` reports it in its `signIn` check, and `whoami`
450
+ fails with the error. With no saved sign-in, `list` and `doctor` carry on as
451
+ usual and `whoami` reports you as signed out (exit 0).
346
452
 
347
453
  ### Windows
348
454
 
@@ -364,21 +470,32 @@ work and ignore any saved credentials.
364
470
  | `--client-id <id>` | `VIGILBASE_CLIENT_ID` | `vigilbase-cli` |
365
471
  | `--timeout <ms>` | — | `60000` per request |
366
472
 
367
- Origins are HTTP(S) URLs without credentials, paths, queries or fragments. One
368
- needs HTTPS; HTTP is allowed only for loopback development (`localhost`,
369
- `127.0.0.0/8` or `[::1]`). This applies to device sign-in, tokens and saved
370
- sessions. `VIGILBASE_API_URL` and `VIGILBASE_PUBLIC_URL` are older names for
371
- the public origin, used after `VIGILBASE_BASE_URL` when no flag is given.
372
- `VIGILBASE_CLI_NO_OPEN=1` stops the CLI from opening a browser, and
373
- `VIGILBASE_CLI_NO_BANNER=1` hides the banner.
473
+ Origins are HTTP(S) URLs without credentials, paths, queries or fragments, and
474
+ need HTTPS; HTTP is allowed only for loopback development (`localhost`,
475
+ `127.0.0.0/8` or `[::1]`). This applies to `--base-url`, `--one-url` and
476
+ `--auth-url`. An empty flag value (`--base-url ""`) is an error; an empty
477
+ environment variable counts as unset. `VIGILBASE_API_URL` and
478
+ `VIGILBASE_PUBLIC_URL` are older names for the public origin, used after
479
+ `VIGILBASE_BASE_URL` when no flag is given; errors about the public origin name
480
+ the variable that held the bad value.
481
+
482
+ Commands check only the origins they use. Public scans, `list`, `describe`,
483
+ `skill` and `badge` ignore `--one-url`, `--auth-url` and their variables, and
484
+ One-only commands such as `whoami` ignore the public origin. `doctor` checks
485
+ both.
486
+
487
+ | Environment variable | Effect |
488
+ | --- | --- |
489
+ | `VIGILBASE_CLI_NO_OPEN=1` | Never open a browser |
490
+ | `VIGILBASE_CLI_NO_BANNER=1` | Hide the banner |
491
+ | `VIGILBASE_CLI_NO_PROGRESS=1` | Turn off progress animations |
374
492
 
375
493
  **Identity issuer.** The CLI trusts one Identity issuer: `--auth-url` /
376
494
  `VIGILBASE_AUTH_URL` when set, otherwise `https://auth.vigilbase.com`. The
377
495
  issuer One names must be exactly that origin, or sign-in stops before any
378
- request to it. `--auth-url` accepts `https://auth.vigilbase.com`,
379
- `https://identity-staging.vigilbase.com`, or a loopback origin when `--one-url`
380
- is loopback too; staging is trusted only when you pass it. Saved Identity
381
- credentials from another issuer count as signed out.
496
+ request to it. `--auth-url` accepts `https://auth.vigilbase.com`, or a loopback
497
+ origin (`localhost`, `127.0.0.0/8` or `[::1]`) when `--one-url` is loopback
498
+ too. Saved Identity credentials from another issuer count as signed out.
382
499
 
383
500
  Billing portal URLs must use HTTPS on `billing.stripe.com`. Skill downloads are
384
501
  limited to HTTPS `vigilbase.com/agent-skills/<name>.md` (or
@@ -401,9 +518,25 @@ three checks on their own and exits 1 if any fails:
401
518
  - `signIn`: whether your saved sign-in works, including a refresh if one is
402
519
  due.
403
520
 
404
- DNS, TLS, connection and timeout failures name the endpoint and the cause.
405
- Use `--base-url`, `--one-url`, `--auth-url` and `--timeout` to check other
406
- environments.
521
+ DNS, TLS, connection and timeout failures name the endpoint and the cause. A
522
+ connection failure ends with the hint ``Run `vigilbase doctor` for connectivity checks.``,
523
+ which `doctor` itself doesn't print. Doctor's `--json` top level includes the
524
+ first failing check's `code`. Use `--base-url`, `--one-url`, `--auth-url` and
525
+ `--timeout` to check other environments.
526
+
527
+ **Redirects and blocked ports.** The CLI never follows redirects. An origin
528
+ that redirects fails with code `UNEXPECTED_REDIRECT`, naming the origin it
529
+ redirects to and the option to set (for example
530
+ `… redirected (HTTP 301) to https://www.example.com. Set --base-url to that origin.`).
531
+ Node's fetch refuses some ports (the Fetch standard's blocked ports, such as
532
+ 6000 and 10080); they fail with code `BLOCKED_PORT`. Use another port.
533
+
534
+ **Proxies.** The CLI uses Node's built-in fetch, which ignores `HTTP_PROXY` and
535
+ `HTTPS_PROXY` unless `NODE_USE_ENV_PROXY=1` is set (Node 22.21+ or 24.5+; Node
536
+ prints an experimental warning). Skill documents (`skill`, `describe`) are
537
+ fetched directly, with DNS pinned to a checked public address, and never go
538
+ through a proxy: on a network that allows only proxied connections, `skill`
539
+ fails and `describe` shows no docs.
407
540
 
408
541
  **`Unknown command "cloudflare-checkup"`.** An older global install (0.1.x)
409
542
  takes priority over a bare `npx @vigilbase/cli`. Update it with
@@ -416,7 +549,11 @@ pnpm 11 and later skip releases published in the last 24 hours by default
416
549
  (Yarn's `npmMinimalAgeGate`, pnpm's `minimumReleaseAge`). For a day after a
417
550
  release, `yarn dlx @vigilbase/cli` and `pnpm dlx @vigilbase/cli` run an
418
551
  older release, and Yarn refuses the new version by number
419
- (`All versions satisfying "…" are quarantined`). Use `npx @vigilbase/cli`, or
552
+ (`All versions satisfying "…" are quarantined`). pnpm 11 runs a pinned
553
+ version (`pnpm dlx @vigilbase/cli@<version>`). pnpm 10.16 and later apply
554
+ `minimumReleaseAge` only when you set it, and then refuse a pinned version
555
+ released within that time with `ERR_PNPM_NO_MATURE_MATCHING_VERSION`. Use
556
+ `npx @vigilbase/cli`, add `--config.minimum-release-age=0` to `pnpm dlx`, or
420
557
  wait a day.
421
558
 
422
559
  **`Device sign-in isn't available at <origin> right now.`** Login got a 404
@@ -429,6 +566,14 @@ in.
429
566
 
430
567
  **`A previous refresh has an uncertain result.`** Run `vigilbase login` again.
431
568
 
569
+ **`A previous vigilbase command was interrupted while it held …` or
570
+ `… credentials.json.lock is damaged.`** Run `vigilbase login`, or delete the
571
+ file as the message says (see
572
+ [Refresh and the credentials lock](#refresh-and-the-credentials-lock)).
573
+
574
+ **`Your saved sign-in was ended by Vigilbase Identity and has been removed.`**
575
+ You signed out in the browser, or the session ended. Run `vigilbase login`.
576
+
432
577
  **Certificates.** On Node 22.19+ and 24.5+ the CLI adds your operating
433
578
  system's trusted certificates, keeping Node's bundled roots and
434
579
  `NODE_EXTRA_CA_CERTS`. This covers private CAs trusted in the macOS Keychain or
@@ -442,8 +587,14 @@ NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--use-system-ca" vigilbase login
442
587
  ```
443
588
 
444
589
  For a private CA outside the system store, set
445
- `NODE_EXTRA_CA_CERTS=/path/to/trusted-ca.pem` before starting the CLI.
446
- Certificate checks always stay on.
590
+ `NODE_EXTRA_CA_CERTS=/path/to/trusted-ca.pem` before starting the CLI. The
591
+ advice to use Node 22.19+ or 24.5+ appears in TLS errors only when the running
592
+ Node couldn't add the operating system's certificates.
593
+
594
+ The CLI never turns certificate checks off. `NODE_TLS_REJECT_UNAUTHORIZED=0`
595
+ does, for every request of the process, including those that carry your One
596
+ sign-in; Node then prints a warning. It is not supported: use
597
+ `NODE_EXTRA_CA_CERTS` instead.
447
598
 
448
599
  **Timeouts.** Use `--timeout` for slower queries; some security analytics APIs
449
600
  take longer than the default.
package/SECURITY.md CHANGED
@@ -99,7 +99,7 @@ Provider events map `--provider` to `provider_id`, `--dataset` to `dataset_id`,
99
99
  | `settings broadcast-emails` | None | `/settings/broadcast-emails` |
100
100
  | `settings applications` | None | `/integrations/grants` |
101
101
 
102
- Intel lookup input is trimmed and limited to 512 characters; the server validates the indicator and applies lookup quota. Personal settings show existing preferences and authorized application grants. They do not change preferences or revoke grants. Personal settings and `notifications preferences` belong to each user: with `--customer-id` (View-as), a refusal prints `Request failed (HTTP 403).` with the hint `Personal settings belong to each user, so View-as (--customer-id) cannot read them.` Reports are fetched on demand; this CLI does not persist report or billing records.
102
+ Intel lookup input is trimmed and limited to 512 characters; the server validates the indicator and applies lookup quota. Personal settings show existing preferences and authorized application grants. They do not change preferences or revoke grants. Personal settings and `notifications preferences` belong to each user: with `--customer-id` (View-as), a refusal prints `You don't have access to this.` with the hint `Personal settings belong to each user, so View-as (--customer-id) cannot read them.` Reports are fetched on demand; this CLI does not persist report or billing records.
103
103
 
104
104
  ## Limits and pagination
105
105
 
@@ -0,0 +1,6 @@
1
+ /** Remove every entry from a path the CLI just created, so it doesn't keep one inherited from its parent. */
2
+ export declare function stripAcl(path: string): Promise<void>;
3
+ /** The ACL entries of each path, in order, like ` 0: group:everyone allow read`. Empty off darwin. */
4
+ export declare function aclEntries(paths: string[]): Promise<string[][]>;
5
+ /** Whether an entry lets someone other than the current user change the directory's entries. */
6
+ export declare function letsOthersChangeEntries(entry: string): boolean;
@@ -0,0 +1,45 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { userInfo } from 'node:os';
3
+ import { promisify } from 'node:util';
4
+ import { platform } from '../lib/platform.js';
5
+ /**
6
+ * macOS access control lists. An allow entry grants access whatever the mode bits say, and Node's stat can't see
7
+ * entries, so on darwin the credentials paths are checked with /bin/ls and cleaned with /bin/chmod. Other platforms
8
+ * have nothing to do: Linux's chmod 0600 masks a default POSIX ACL.
9
+ */
10
+ const run = promisify(execFile);
11
+ /**
12
+ * Directory rights that let another user add, remove or replace an entry, remove the directory, or take over its ACL
13
+ * or ownership (and then grant themselves the rest). The boundaries keep writeattr and writeextattr from matching.
14
+ */
15
+ const CHANGES_ENTRIES = /\b(?:add_file|add_subdirectory|delete_child|delete|write|append|writesecurity|chown)\b/;
16
+ /** Remove every entry from a path the CLI just created, so it doesn't keep one inherited from its parent. */
17
+ export async function stripAcl(path) {
18
+ if (platform() !== 'darwin')
19
+ return;
20
+ await run('/bin/chmod', ['-N', path]);
21
+ }
22
+ /** The ACL entries of each path, in order, like ` 0: group:everyone allow read`. Empty off darwin. */
23
+ export async function aclEntries(paths) {
24
+ if (platform() !== 'darwin' || !paths.length)
25
+ return paths.map(() => []);
26
+ // -q prints a newline in a name as ?, -f keeps the operands' order.
27
+ const { stdout } = await run('/bin/ls', ['-ledqf', '--', ...paths]);
28
+ const entries = [];
29
+ for (const line of stdout.split('\n')) {
30
+ if (/^\s*\d+: /.test(line))
31
+ entries.at(-1)?.push(line.trim());
32
+ else if (line)
33
+ entries.push([]);
34
+ }
35
+ if (entries.length !== paths.length)
36
+ throw new Error('Unexpected /bin/ls output.');
37
+ return entries;
38
+ }
39
+ /** Whether an entry lets someone other than the current user change the directory's entries. */
40
+ export function letsOthersChangeEntries(entry) {
41
+ const match = /^\d+: (.+?) (?:inherited )?(allow|deny) (\S+)/.exec(entry);
42
+ if (!match || match[2] !== 'allow' || !CHANGES_ENTRIES.test(match[3]))
43
+ return false;
44
+ return match[1] !== `user:${userInfo().username}`;
45
+ }
@@ -14,10 +14,13 @@ export declare class DeviceAuthError extends Error {
14
14
  readonly code?: string;
15
15
  readonly status: number;
16
16
  readonly body: unknown;
17
+ /** The message is the CLI's own text, never the server's (see VigilbaseApiError). */
18
+ readonly isCliAuthored: boolean;
17
19
  constructor(message: string, options: {
18
20
  status: number;
19
21
  code?: string;
20
22
  body?: unknown;
23
+ isCliAuthored?: boolean;
21
24
  });
22
25
  }
23
26
  export declare function requestDeviceCode(config: CliConfig, options?: {
@@ -8,12 +8,15 @@ export class DeviceAuthError extends Error {
8
8
  code;
9
9
  status;
10
10
  body;
11
+ /** The message is the CLI's own text, never the server's (see VigilbaseApiError). */
12
+ isCliAuthored;
11
13
  constructor(message, options) {
12
14
  super(message);
13
15
  this.name = 'DeviceAuthError';
14
16
  this.status = options.status;
15
17
  this.code = options.code;
16
18
  this.body = options.body;
19
+ this.isCliAuthored = options.isCliAuthored ?? false;
17
20
  }
18
21
  }
19
22
  function authBase(config) {
@@ -31,6 +34,7 @@ async function postJson(config, path, body) {
31
34
  const url = provider ? (path === '/device/code' ? provider.deviceEndpoint : provider.tokenEndpoint) : `${authBase(config)}${path}`;
32
35
  const form = provider && path === '/device/token';
33
36
  const { response, text } = await requestText(config, url, {
37
+ option: provider ? '--auth-url' : '--one-url',
34
38
  method: 'POST',
35
39
  headers: {
36
40
  Accept: 'application/json',
@@ -139,7 +143,7 @@ export async function pollDeviceToken(config, deviceCode) {
139
143
  },
140
144
  };
141
145
  }
142
- throw new DeviceAuthError(`Device token request failed (HTTP ${status})`, { status, code, body: errorBody });
146
+ throw new DeviceAuthError(`Device token request failed (HTTP ${status})`, { status, code, body: errorBody, isCliAuthored: true });
143
147
  }
144
148
  export async function sleep(ms, signal) {
145
149
  await new Promise((resolve, reject) => {
@@ -198,14 +202,19 @@ export async function waitForDeviceAuthorization(config, device, options) {
198
202
  consecutiveTransient += 1;
199
203
  lastTransientStatus = result.httpStatus;
200
204
  if (consecutiveTransient >= maxTransient) {
205
+ // After an HTTP error the description is the server's, so only a network failure's
206
+ // own (CLI-authored) description joins the message.
207
+ const detail = (!lastTransientStatus && result.error.error_description) ||
208
+ 'Retry `vigilbase login` once One is healthy.';
201
209
  throw new DeviceAuthError(`Lost contact with device authorization on ${config.oneBaseUrl} while waiting for approval` +
202
210
  (lastTransientStatus
203
211
  ? ` (HTTP ${lastTransientStatus})`
204
212
  : '') +
205
- `. ${result.error.error_description ?? 'Retry `vigilbase login` once One is healthy.'}`, {
213
+ `. ${detail}`, {
206
214
  status: lastTransientStatus,
207
215
  code: 'unreachable',
208
216
  body: result.error,
217
+ isCliAuthored: true,
209
218
  });
210
219
  }
211
220
  break;
@@ -214,9 +223,10 @@ export async function waitForDeviceAuthorization(config, device, options) {
214
223
  status: 403,
215
224
  code: 'access_denied',
216
225
  body: result.error,
226
+ isCliAuthored: true,
217
227
  });
218
228
  case 'expired':
219
- throw new DeviceAuthError('The device code expired. Run `vigilbase login` again.', { status: 400, code: 'expired_token', body: result.error });
229
+ throw new DeviceAuthError('The device code expired. Run `vigilbase login` again.', { status: 400, code: 'expired_token', body: result.error, isCliAuthored: true });
220
230
  default: {
221
231
  const _exhaustive = result;
222
232
  throw new DeviceAuthError(`Unexpected poll result: ${JSON.stringify(_exhaustive)}`, {
@@ -1,6 +1,6 @@
1
1
  import { DeviceAuthError } from './device-auth.js';
2
2
  import { z } from 'zod';
3
- import { DEFAULT_IDENTITY_ISSUER, parseOrigin, STAGING_IDENTITY_ISSUER, trustedIdentityIssuer, validateOneOrigin } from '../lib/config.js';
3
+ import { DEFAULT_IDENTITY_ISSUER, parseOrigin, trustedIdentityIssuer, validateOneOrigin } from '../lib/config.js';
4
4
  import { requestText, parseJsonResponse } from '../lib/network.js';
5
5
  /** An issuer (discovered from One, or saved with credentials) is trusted only when its origin is exactly trustedIdentityIssuer(config). */
6
6
  export function validateIdentityIssuer(raw, config) {
@@ -17,9 +17,9 @@ export function validateIdentityIssuer(raw, config) {
17
17
  return origin;
18
18
  throw new Error(config.identityBaseUrl
19
19
  ? `Untrusted Identity issuer ${origin}: --auth-url / VIGILBASE_AUTH_URL is ${trusted}. Set it to the issuer for this One, or unset it to use ${DEFAULT_IDENTITY_ISSUER}.`
20
- : `Untrusted Identity issuer ${origin}: only ${DEFAULT_IDENTITY_ISSUER} is trusted by default. For staging, pass --auth-url ${STAGING_IDENTITY_ISSUER} or set VIGILBASE_AUTH_URL.`);
20
+ : `Untrusted Identity issuer ${origin}: only ${DEFAULT_IDENTITY_ISSUER} is trusted by default. To use another issuer, pass --auth-url <issuer> or set VIGILBASE_AUTH_URL.`);
21
21
  }
22
- async function json(config, url) { const { response, text } = await requestText(config, url, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent } }); return { response, body: parseJsonResponse(response, text) }; }
22
+ async function json(config, url) { const { response, text } = await requestText(config, url, { option: '--auth-url', headers: { Accept: 'application/json', 'User-Agent': config.userAgent } }); return { response, body: parseJsonResponse(response, text) }; }
23
23
  export async function identityProvider(config, issuer) {
24
24
  issuer = validateIdentityIssuer(issuer, config);
25
25
  const { response, body } = await json(config, `${issuer}/.well-known/openid-configuration`);
@@ -42,7 +42,7 @@ export async function identityProvider(config, issuer) {
42
42
  */
43
43
  export async function discoverAuthProvider(config) {
44
44
  const one = validateOneOrigin(config.oneBaseUrl);
45
- const { response, text } = await requestText(config, `${one}/.well-known/oauth-protected-resource`, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent }, redirect: 'manual' });
45
+ const { response, text } = await requestText(config, `${one}/.well-known/oauth-protected-resource`, { option: '--one-url', headers: { Accept: 'application/json', 'User-Agent': config.userAgent }, redirect: 'manual' });
46
46
  if (response.type === 'opaqueredirect' || response.status === 0 || (response.status >= 300 && response.status < 400))
47
47
  return undefined;
48
48
  if (response.status === 404)
@@ -3,14 +3,15 @@ import type { OneCredentials } from './types.js';
3
3
  export declare function boundIdentityCredentials(config: CliConfig, one: OneCredentials): boolean;
4
4
  /** Library callers may omit or mistype timeoutMs; NaN would make the wait unbounded. */
5
5
  export declare function credentialsLockWaitMs(timeoutMs: unknown): number;
6
- /** Another command holds credentials.json.lock. */
6
+ /** credentials.json.lock is held: by a running command, by one that was interrupted, or it is damaged. */
7
7
  export declare class CredentialsLockedError extends Error {
8
- constructor();
8
+ constructor(message: string);
9
9
  }
10
10
  /**
11
11
  * credentials.json.lock guards both credentials files; CLI 0.2.0 takes the same lock.
12
- * Never steal a stale lock: a crashed refresh may have consumed the rotating token.
13
- * With `wait` false, a held lock fails at once instead of after the lock wait.
12
+ * Only login and logout (`replaceDeadLock`) replace a lock whose owner has exited, or an old damaged one: a crashed
13
+ * refresh may have consumed the rotating token, and both start over without it. Other commands fail at once on a
14
+ * dead owner. With `wait` false, a held lock fails at once instead of after the lock wait.
14
15
  */
15
16
  export declare function withCredentialsLock<T>(config: Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'timeoutMs'>>, callback: () => Promise<T>, replaceDeadLock?: boolean, wait?: boolean): Promise<T>;
16
17
  export declare function managedAccessToken(config: CliConfig, failedToken?: string): Promise<string | null>;