From 08b24c488f0ec4d5669b94e7ce112be96d6f828f Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 24 Jun 2026 23:26:24 +0200 Subject: [PATCH 1/3] docs(cli): add orbit-station-audit Claude Code skill An operational guide plus helper script for running `orbit-cli audit` against a live station end-to-end. It front-loads the four things that derail a first run: the stale/broken global CLI, the ESM-in-CJS build crash (`Unexpected token 'export'`), the per-origin Internet Identity derivation (orbitwallet.io, not the URL bar), and the `icp identity link web` Enter-keypress. scripts/run-audit.sh builds the CLI from source, repairs the generated IDL, and forwards all args to `orbit-cli audit`. Verified end-to-end against a mainnet station. Co-Authored-By: Claude Opus 4.8 --- .claude/skills/orbit-station-audit/SKILL.md | 157 ++++++++++++++++++ .../orbit-station-audit/scripts/run-audit.sh | 48 ++++++ 2 files changed, 205 insertions(+) create mode 100644 .claude/skills/orbit-station-audit/SKILL.md create mode 100755 .claude/skills/orbit-station-audit/scripts/run-audit.sh diff --git a/.claude/skills/orbit-station-audit/SKILL.md b/.claude/skills/orbit-station-audit/SKILL.md new file mode 100644 index 000000000..769bfe75d --- /dev/null +++ b/.claude/skills/orbit-station-audit/SKILL.md @@ -0,0 +1,157 @@ +--- +name: orbit-station-audit +description: >- + Build and run the Orbit station configuration audit (`orbit-cli audit`) + end-to-end against a live station, including the icp-cli Internet Identity + setup that reliably trips people up. Use this whenever the user wants to + audit, sanity-check, or security-review an Orbit station or wallet — e.g. + "audit my Orbit wallet", "run orbit-cli audit on station ", + "check my Orbit station for misconfigured approval policies / empty quorums", + or any time they pair an Orbit station canister id with auditing, approval + quorum, or permission checks. Reach for this even if they don't say + "orbit-cli" by name. It covers building the CLI from source (the globally + installed orbit-cli is usually too old to have the `audit` subcommand), + obtaining an identity the station recognizes as a member, and reading the + report. +--- + +# Orbit station audit + +`orbit-cli audit` runs read-only sanity checks against a live station and +prints a severity-sorted report — safe to run any time, since it only issues +`list_*` queries and mutates nothing. Flags, exit codes, and report format are +documented in [`cli/src/audit/README.md`](../../../cli/src/audit/README.md); +read that for reference. This skill is the *operational* guide — it gets you +from a fresh checkout to a successful run and front-loads the four things that +actually derail a first one. + +## The four gotchas (read these first) + +1. **Don't rely on the global `orbit-cli`.** Depending on when it was last + built it either predates the `audit` subcommand (unknown-command error) or — + after a `pnpm install`, which rebuilds and re-links it via the `prepare-cli` + postinstall — carries the gotcha-2 bug and crashes. Build and run this repo's + freshly-repaired `cli/dist/cli.js` instead; the helper script does exactly that. +2. **The built CLI may crash with `SyntaxError: Unexpected token 'export'`.** + The build copies a generated IDL (`station.did.js`) that is an ES module + into the CommonJS bundle, and `require()` can't parse it. The helper script + below repairs this automatically; the manual fix is in *Troubleshooting*. +3. **Internet Identity principals are per-origin.** The same II anchor yields a + *different* principal for every app origin. The audit must be called by a + principal the station knows as a member, so the identity has to be derived + from the origin the wallet itself uses. In production that origin is + **`orbitwallet.io`** (pinned as `derivationOrigin` in + [`apps/wallet/src/configs/init.config.ts`](../../../apps/wallet/src/configs/init.config.ts)), + *not* the URL bar. Pass the bare host `orbitwallet.io` to `--app` (no + `https://`, even though the config shows a scheme). Deriving from anything + else (`app.orbit.global`, `oisy.com`, the default `cli.id.ai`) gives a + stranger principal that the station will reject. +4. **`icp identity link web` waits for an Enter keypress** before it opens the + browser. Running it non-interactively, pipe a newline in so it proceeds: + `printf '\n' | icp identity link web ...`. The human still completes the + actual sign-in in the browser. + +## Prerequisites + +- Node 20 and pnpm 9 (the repo pins `pnpm@9.12.2` via `packageManager`; + Corepack normally handles this). +- `icp-cli` on `PATH` (`brew install icp-cli`, or + ) — needed for `--identity-source icp`. +- A working copy of the Orbit repo. If you aren't in one: + `git clone https://github.com/dfinity/orbit && cd orbit`. + +## Step 1 — Build the CLI from source + +The helper script does this for you (install if needed → build → repair the +ESM/CJS quirk from gotcha 2), so prefer it: + +```bash +.claude/skills/orbit-station-audit/scripts/run-audit.sh --help +``` + +Equivalent by hand, if you'd rather see each step: + +```bash +pnpm install # once, if node_modules is missing +pnpm --filter orbit-cli build # emits cli/dist/cli.js +node cli/dist/cli.js audit --help # may hit gotcha 2 — see Troubleshooting +``` + +Run the freshly built `cli/dist/cli.js` directly — that's the copy the helper +script repairs. The global on `PATH` isn't repaired, even right after +`pnpm install`. + +## Step 2 — Get an identity the station recognizes + +The audit signs its calls with an identity that must be a station **member** +(admin-tier users have the required read access by default). For an +Internet-Identity-based wallet, that means an `icp-cli` identity derived from +`orbitwallet.io` (gotcha 3). + +First, reuse an existing one if you already have it: + +```bash +icp identity list +icp identity principal --identity # for each plausible candidate +``` + +If a candidate's principal already matches the principal shown in your wallet +UI under **Settings → Identity**, use that name and skip to Step 3. (Identities +bound to other origins — `oisy.com`, the default `cli.id.ai` — will *not* match.) + +Otherwise create one bound to the wallet's origin: + +```bash +printf '\n' | icp identity link web orbit-audit --app orbitwallet.io +icp identity principal --identity orbit-audit +``` + +A browser opens to `id.ai`. **Sign in with the same Internet Identity anchor +you use to log into Orbit.** The printed principal should match your wallet's +Settings → Identity. The *definitive* test, though, is Step 3: if the audit's +read calls are authorized, you are a member. + +If the principal doesn't match (or Step 3 returns an authorization error), +delete it and re-link trying a different origin or anchor: + +```bash +icp identity delete orbit-audit # note: `delete`, not `remove` +printf '\n' | icp identity link web orbit-audit --app app.orbit.global +``` + +`orbitwallet.io` is correct for the current production wallet; `app.orbit.global` +is the fallback to try if the station predates that derivation origin. + +## Step 3 — Run the audit + +```bash +.claude/skills/orbit-station-audit/scripts/run-audit.sh \ + --station \ + --identity orbit-audit \ + --identity-source icp \ + --output ~/Downloads/orbit-audit-$(date +%F).txt +``` + +Every flag after the script name is forwarded verbatim to `orbit-cli audit`, so +swap in whichever identity matched in Step 2. `--network` defaults to `ic` (pass +`--network local` for a local replica), and dropping `--output` prints to +stdout. `--output` writes internal station metadata (principals, policy ids) — +keep it private. Exit code `2` means at least one blocker (full table in the +README). + +## Troubleshooting + +- **`SyntaxError: Unexpected token 'export'` running the CLI** — the generated + IDL got copied into the CommonJS build as an ES module. The helper script + repairs this after every build; if you're building by hand, copy the + perl/printf one-liner from its step 3. (It's gitignored build output, so the + patch is safe and disposable — a rebuild reintroduces the problem, which is + why the script re-applies the fix each run.) The real fix belongs in + [`cli/package.json`](../../../cli/package.json)'s build script. +- **`Unauthorized` / read calls rejected** — the calling principal isn't a + station member. Re-check Step 2: right anchor, right `--app` origin, and the + principal actually matches your wallet UI. +- **`icp identity store not found`** — `icp-cli` isn't installed, or you used + `--identity-source icp` without it. Install icp-cli, or fall back to a dfx PEM + with `--identity-source dfx` (use a *plaintext* identity — passphrase-protected + PEMs aren't supported; see the README). diff --git a/.claude/skills/orbit-station-audit/scripts/run-audit.sh b/.claude/skills/orbit-station-audit/scripts/run-audit.sh new file mode 100755 index 000000000..d945fbe67 --- /dev/null +++ b/.claude/skills/orbit-station-audit/scripts/run-audit.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# +# Build the Orbit CLI from source and run `orbit-cli audit`. +# +# Why build from source instead of the global `orbit-cli`: the `audit` +# subcommand is recent, and a globally-installed orbit-cli is almost certainly +# older and doesn't have it. Building here keeps us on the checked-out version. +# +# Usage: +# run-audit.sh --station [--identity ] [--identity-source icp] \ +# [--network ic] [--output ] ... +# +# Everything you pass is forwarded verbatim to `orbit-cli audit`, so any flag +# the audit accepts works here. With no args it prints the audit's help. +set -euo pipefail + +REPO="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)" +CLI_JS="$REPO/cli/dist/cli.js" +GEN="$REPO/cli/dist/audit/generated/station.did.js" + +# 1. Install workspace deps once. (If the build below fails on a checkout that +# already has node_modules, the deps are likely stale — run `pnpm install` +# in the repo by hand and re-run.) +if [ ! -d "$REPO/node_modules" ]; then + (cd "$REPO" && pnpm install) +fi + +# 2. Build the CLI (fast; just tsc + a copy step). +(cd "$REPO" && pnpm --filter orbit-cli build) >/dev/null + +# 3. Repair the ESM/CJS mismatch in the generated IDL. +# The build does `cp src/audit/generated/station.did.js dist/...`, but that +# file is an ES module (`export const idlFactory`) while the compiled CLI is +# CommonJS — so `require()` throws `Unexpected token 'export'`. We rewrite the +# dist copy to CommonJS. This runs after every build because the copy step +# re-introduces the ESM file each time. +# +# The proper fix is to emit/copy this file as CommonJS in the CLI's build +# script (cli/package.json) so this guard becomes unnecessary. +if grep -q '^export const' "$GEN"; then + perl -0pi -e 's/^export const /const /mg' "$GEN" + printf '\nmodule.exports = { idlFactory, init };\n' >>"$GEN" +fi + +# 4. Run the audit, forwarding all arguments (default to --help if none given, +# since `audit` requires --station and would otherwise error). +if [ "$#" -eq 0 ]; then set -- --help; fi +exec node "$CLI_JS" audit "$@" From a775a378b3453d1357912224403e11e2481f8625 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 24 Jun 2026 23:32:21 +0200 Subject: [PATCH 2/3] docs(cli): repair generated IDL with Node instead of perl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot review feedback: the helper script shelled out to perl for the ESM->CJS rewrite, but perl isn't a declared prerequisite and isn't used elsewhere in the repo. Node is already a hard requirement, so do the rewrite in Node — more portable, no extra runtime. The snippet is idempotent (no-op once the file is already CommonJS). Co-Authored-By: Claude Opus 4.8 --- .claude/skills/orbit-station-audit/SKILL.md | 2 +- .../orbit-station-audit/scripts/run-audit.sh | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.claude/skills/orbit-station-audit/SKILL.md b/.claude/skills/orbit-station-audit/SKILL.md index 769bfe75d..4208a553f 100644 --- a/.claude/skills/orbit-station-audit/SKILL.md +++ b/.claude/skills/orbit-station-audit/SKILL.md @@ -144,7 +144,7 @@ README). - **`SyntaxError: Unexpected token 'export'` running the CLI** — the generated IDL got copied into the CommonJS build as an ES module. The helper script repairs this after every build; if you're building by hand, copy the - perl/printf one-liner from its step 3. (It's gitignored build output, so the + IDL-repair Node snippet from its step 3. (It's gitignored build output, so the patch is safe and disposable — a rebuild reintroduces the problem, which is why the script re-applies the fix each run.) The real fix belongs in [`cli/package.json`](../../../cli/package.json)'s build script. diff --git a/.claude/skills/orbit-station-audit/scripts/run-audit.sh b/.claude/skills/orbit-station-audit/scripts/run-audit.sh index d945fbe67..ac299e95e 100755 --- a/.claude/skills/orbit-station-audit/scripts/run-audit.sh +++ b/.claude/skills/orbit-station-audit/scripts/run-audit.sh @@ -37,10 +37,18 @@ fi # # The proper fix is to emit/copy this file as CommonJS in the CLI's build # script (cli/package.json) so this guard becomes unnecessary. -if grep -q '^export const' "$GEN"; then - perl -0pi -e 's/^export const /const /mg' "$GEN" - printf '\nmodule.exports = { idlFactory, init };\n' >>"$GEN" -fi +# +# Done in Node (already a hard requirement here) so the script needs no extra +# runtime like perl/sed. Idempotent: a no-op once the file is already CJS. +node -e ' + const fs = require("fs"); + const p = process.argv[1]; + let s = fs.readFileSync(p, "utf8"); + if (s.includes("export const ")) { + s = s.replace(/^export const /gm, "const ") + "\nmodule.exports = { idlFactory, init };\n"; + fs.writeFileSync(p, s); + } +' "$GEN" # 4. Run the audit, forwarding all arguments (default to --help if none given, # since `audit` requires --station and would otherwise error). From 2c8d2b5bbe3ff697837665aa7f91fe02cc8df7e4 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Thu, 25 Jun 2026 09:22:33 +0200 Subject: [PATCH 3/3] docs(cli): drop obsolete ESM workaround from audit skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #639 (merged) emits the station IDL factory as CommonJS, so the built CLI no longer crashes with `Unexpected token 'export'`. Verified after merging main: `pnpm --filter orbit-cli build` produces a CLI that runs `audit` directly with no repair. Removes the now-dead repair step from run-audit.sh, drops the ESM gotcha and its troubleshooting entry from SKILL.md (four gotchas → three), and simplifies the "don't trust the global CLI" note accordingly. Co-Authored-By: Claude Opus 4.8 --- .claude/skills/orbit-station-audit/SKILL.md | 43 +++++++------------ .../orbit-station-audit/scripts/run-audit.sh | 31 ++----------- 2 files changed, 19 insertions(+), 55 deletions(-) diff --git a/.claude/skills/orbit-station-audit/SKILL.md b/.claude/skills/orbit-station-audit/SKILL.md index 4208a553f..3244fe927 100644 --- a/.claude/skills/orbit-station-audit/SKILL.md +++ b/.claude/skills/orbit-station-audit/SKILL.md @@ -22,21 +22,16 @@ prints a severity-sorted report — safe to run any time, since it only issues `list_*` queries and mutates nothing. Flags, exit codes, and report format are documented in [`cli/src/audit/README.md`](../../../cli/src/audit/README.md); read that for reference. This skill is the *operational* guide — it gets you -from a fresh checkout to a successful run and front-loads the four things that +from a fresh checkout to a successful run and front-loads the three things that actually derail a first one. -## The four gotchas (read these first) - -1. **Don't rely on the global `orbit-cli`.** Depending on when it was last - built it either predates the `audit` subcommand (unknown-command error) or — - after a `pnpm install`, which rebuilds and re-links it via the `prepare-cli` - postinstall — carries the gotcha-2 bug and crashes. Build and run this repo's - freshly-repaired `cli/dist/cli.js` instead; the helper script does exactly that. -2. **The built CLI may crash with `SyntaxError: Unexpected token 'export'`.** - The build copies a generated IDL (`station.did.js`) that is an ES module - into the CommonJS bundle, and `require()` can't parse it. The helper script - below repairs this automatically; the manual fix is in *Troubleshooting*. -3. **Internet Identity principals are per-origin.** The same II anchor yields a +## The three gotchas (read these first) + +1. **Don't rely on the global `orbit-cli`.** A global on `PATH` may be from an + older checkout that predates the `audit` subcommand (unknown-command error). + Build and run this repo's `cli/dist/cli.js` instead — what the helper script + does — so there's no doubt about which version you're invoking. +2. **Internet Identity principals are per-origin.** The same II anchor yields a *different* principal for every app origin. The audit must be called by a principal the station knows as a member, so the identity has to be derived from the origin the wallet itself uses. In production that origin is @@ -46,7 +41,7 @@ actually derail a first one. `https://`, even though the config shows a scheme). Deriving from anything else (`app.orbit.global`, `oisy.com`, the default `cli.id.ai`) gives a stranger principal that the station will reject. -4. **`icp identity link web` waits for an Enter keypress** before it opens the +3. **`icp identity link web` waits for an Enter keypress** before it opens the browser. Running it non-interactively, pipe a newline in so it proceeds: `printf '\n' | icp identity link web ...`. The human still completes the actual sign-in in the browser. @@ -62,8 +57,8 @@ actually derail a first one. ## Step 1 — Build the CLI from source -The helper script does this for you (install if needed → build → repair the -ESM/CJS quirk from gotcha 2), so prefer it: +The helper script does this for you (install if needed → build → run), so +prefer it: ```bash .claude/skills/orbit-station-audit/scripts/run-audit.sh --help @@ -74,19 +69,18 @@ Equivalent by hand, if you'd rather see each step: ```bash pnpm install # once, if node_modules is missing pnpm --filter orbit-cli build # emits cli/dist/cli.js -node cli/dist/cli.js audit --help # may hit gotcha 2 — see Troubleshooting +node cli/dist/cli.js audit --help ``` -Run the freshly built `cli/dist/cli.js` directly — that's the copy the helper -script repairs. The global on `PATH` isn't repaired, even right after -`pnpm install`. +Run the freshly built `cli/dist/cli.js` directly so there's no doubt about +which build is on `PATH`. ## Step 2 — Get an identity the station recognizes The audit signs its calls with an identity that must be a station **member** (admin-tier users have the required read access by default). For an Internet-Identity-based wallet, that means an `icp-cli` identity derived from -`orbitwallet.io` (gotcha 3). +`orbitwallet.io` (gotcha 2). First, reuse an existing one if you already have it: @@ -141,13 +135,6 @@ README). ## Troubleshooting -- **`SyntaxError: Unexpected token 'export'` running the CLI** — the generated - IDL got copied into the CommonJS build as an ES module. The helper script - repairs this after every build; if you're building by hand, copy the - IDL-repair Node snippet from its step 3. (It's gitignored build output, so the - patch is safe and disposable — a rebuild reintroduces the problem, which is - why the script re-applies the fix each run.) The real fix belongs in - [`cli/package.json`](../../../cli/package.json)'s build script. - **`Unauthorized` / read calls rejected** — the calling principal isn't a station member. Re-check Step 2: right anchor, right `--app` origin, and the principal actually matches your wallet UI. diff --git a/.claude/skills/orbit-station-audit/scripts/run-audit.sh b/.claude/skills/orbit-station-audit/scripts/run-audit.sh index ac299e95e..dc77ef5ba 100755 --- a/.claude/skills/orbit-station-audit/scripts/run-audit.sh +++ b/.claude/skills/orbit-station-audit/scripts/run-audit.sh @@ -2,9 +2,9 @@ # # Build the Orbit CLI from source and run `orbit-cli audit`. # -# Why build from source instead of the global `orbit-cli`: the `audit` -# subcommand is recent, and a globally-installed orbit-cli is almost certainly -# older and doesn't have it. Building here keeps us on the checked-out version. +# Why build from source instead of the global `orbit-cli`: a global on PATH may +# be from an older checkout that predates the `audit` subcommand. Building here +# keeps us on the version in this repo, with no doubt about what's invoked. # # Usage: # run-audit.sh --station [--identity ] [--identity-source icp] \ @@ -16,7 +16,6 @@ set -euo pipefail REPO="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)" CLI_JS="$REPO/cli/dist/cli.js" -GEN="$REPO/cli/dist/audit/generated/station.did.js" # 1. Install workspace deps once. (If the build below fails on a checkout that # already has node_modules, the deps are likely stale — run `pnpm install` @@ -28,29 +27,7 @@ fi # 2. Build the CLI (fast; just tsc + a copy step). (cd "$REPO" && pnpm --filter orbit-cli build) >/dev/null -# 3. Repair the ESM/CJS mismatch in the generated IDL. -# The build does `cp src/audit/generated/station.did.js dist/...`, but that -# file is an ES module (`export const idlFactory`) while the compiled CLI is -# CommonJS — so `require()` throws `Unexpected token 'export'`. We rewrite the -# dist copy to CommonJS. This runs after every build because the copy step -# re-introduces the ESM file each time. -# -# The proper fix is to emit/copy this file as CommonJS in the CLI's build -# script (cli/package.json) so this guard becomes unnecessary. -# -# Done in Node (already a hard requirement here) so the script needs no extra -# runtime like perl/sed. Idempotent: a no-op once the file is already CJS. -node -e ' - const fs = require("fs"); - const p = process.argv[1]; - let s = fs.readFileSync(p, "utf8"); - if (s.includes("export const ")) { - s = s.replace(/^export const /gm, "const ") + "\nmodule.exports = { idlFactory, init };\n"; - fs.writeFileSync(p, s); - } -' "$GEN" - -# 4. Run the audit, forwarding all arguments (default to --help if none given, +# 3. Run the audit, forwarding all arguments (default to --help if none given, # since `audit` requires --station and would otherwise error). if [ "$#" -eq 0 ]; then set -- --help; fi exec node "$CLI_JS" audit "$@"