The Storage class in `packages/sdk/src/utils/storage.ts` constructed a
`Conf` instance without passing `configFileMode`, so the on-disk file
inherited conf's default (0o666 masked by umask, typically 0o644 on
macOS and Linux).
The file holds the OAuth `access_token` and `refresh_token` from
`auth login`, plus — during a pending device-auth window — the
`device_code` and verification phrase written by the agent-mode
`auth login` flow at packages/cli/src/commands/auth/index.tsx:50.
With 0o644 perms, any other local user (shared dev workstations,
multi-user CI runners, lab machines) can:
- Read the access + refresh tokens and call the Link API as the
victim — including `GET /spend_requests/{id}?include=card` to
retrieve unmasked card details from approved spend requests.
- During an active login window, read the `device_code` and race the
legitimate `auth status --interval` poll loop to
`/device/token`. OAuth 2.0 device-flow polls return tokens to the
first caller after user approval; the device_code is the polling
client's secret, and it should never reach disk in a world-readable
form.
This change passes `configFileMode: 0o600` to the Conf constructor.
Owner-only matches the convention used by gh, aws, mercury-cli, and
similar credential-bearing CLIs. conf writes via atomic rename, so
existing 0o644 files are remediated automatically on the next config
write (next setAuth, clearAuth, or setPendingDeviceAuth).
For testability, the `Storage` class is now exported and accepts an
optional `cwd` so unit tests can run against a temp directory instead
of the real platform user-config path.
Tests:
- `writes the config file with mode 0o600 (owner-only)` — fresh write.
- `rewrites with mode 0o600 when an existing file is 0o644` — covers
the upgrade path from a pre-fix install.
- `also restricts pendingDeviceAuth, which is written to the same
file` — covers the device_code race-window vector.
Skipped on Windows (NTFS uses ACLs; POSIX mode bits don't reflect
actual access).
* fix: correctly expose the SPT in the Ink output
* fix: correctly expose the SPT in the Ink output
* fix: stop spend-request commands from making duplicate API calls
The interactive (Ink) path for create, update, request-approval, and
retrieve each rendered a component that called the API, then made a
second identical API call in waitUntilExit().then() to obtain a value
to resolve the Promise. Thread the result back through onComplete so
index.tsx can resolve with the component's result directly.
Committed-By-Agent: claude
* fix: use ref to track latest request in retrieve polling effect
Using `request` state directly in the polling useEffect timeout path
triggered a biome exhaustive-deps lint error. Track the latest value
in a ref so the polling effect dependency array stays stable.
Committed-By-Agent: claude
* changeset
* fix: remove unused file
* add checklist style flow for onboard/demo
* add changeset
* fix fmt
* update galtee link
* continue to show credential in demo flow after it ends
* misc polish
* update text
* fmt
* migrate commander -> incur enabling mcp server
* save
* use incur skill handling and clarify to use mcp if running
* add changeset
* fix type error
* fix fmt
* fix tests
* fmt