Commit Graph

86 Commits

Author SHA1 Message Date
Dan Hill a96c507046 feat: bump to version 0.6.0 (#126) @stripe/link-cli@0.6.0 2026-05-28 16:14:51 -04:00
Dan Hill 18d19579a4 fix: hide web-bot-auth until ready (#125) 2026-05-28 16:07:05 -04:00
Dan Hill fe45662873 HTTP mcp (#121)
* feat: add serve command to expose MCP endpoint over HTTP

Committed-By-Agent: claude

* fix: cleanup and readme

* Potential fix for pull request finding 'CodeQL / Information exposure through a stack trace'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-05-28 15:45:53 -04:00
harryguo-nyc 2639339bfc feat: add spend-request list command (#108)
* feat: add spend-request list command

Implements `spend-request list` subcommand that calls GET /spend_requests,
unwraps the response, and renders active spend requests grouped by status
with color coding. Includes SDK interface update, implementation, Ink UI
component, subcommand registration, and unit/integration tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude

* fix: correct onComplete type in SpendRequestList to accept null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude

* fix: apply biome formatting to list command files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude

* fix: call exit() after list completes so interactive mode terminates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude

* fix: apply biome formatting to useAsyncAction destructure in list.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 10:34:27 -04:00
Dan Hill 0783323004 WIP: auth from env (#107)
* feat: add noRefresh option to createAccessTokenProvider

Committed-By-Agent: claude

* feat: add env-based token provider to ResourceFactory

Committed-By-Agent: claude

* feat: read LINK_ACCESS_TOKEN, LINK_REFRESH_TOKEN, LINK_NO_REFRESH from env

Committed-By-Agent: claude

* docs: add LINK_ACCESS_TOKEN, LINK_REFRESH_TOKEN, LINK_NO_REFRESH to env vars docs

Committed-By-Agent: claude

* fix: auth status shows LINK_ACCESS_TOKEN state instead of stored credentials

Committed-By-Agent: claude

* refactor: extract resolveAuthInfo helper, simplify auth status component

Committed-By-Agent: claude

* fix: formatting
2026-05-27 10:04:43 -04:00
Dan Hill 5d76bf614d feat: document current limits (#104) 2026-05-27 09:00:10 -04:00
Gus d6efd70e01 security: refuse to write credentials through pre-existing symlinks (#94)
* security: refuse to write credentials through pre-existing symlinks

writeCredentialFile (cli/src/utils/credential-output.ts) was vulnerable to
a TOCTOU exfiltration on shared filesystems. The previous flow was:

  fs.access(resolved)         // follows symlinks
  fs.writeFile(resolved, ..., { mode: 0o600 })  // follows symlinks; mode
                                                // applied only if file is
                                                // created, not if exists
  fs.chmod(resolved, 0o600)   // follows symlinks; sets perms on TARGET

If the operator runs `spend-request retrieve <id> --output-file <path>
--force` and an attacker on the same filesystem (CI runner, multi-tenant
host, container with shared /tmp) pre-plants a symlink at <path> pointing
at a file the attacker can already read, the attacker:

  1. Plants <path> as a symbolic link to /tmp/<readable>.
  2. Opens a read fd on /tmp/<readable> while it is world-readable.
  3. Operator runs the command: writeFile follows the symlink and writes
     the full card credential (PAN, CVV, billing address, valid_until) to
     the symlink target.
  4. Operator's chmod 0o600 then locks the target down — too late, the
     attacker's fd was opened before the chmod and survives it.

Verified end-to-end by a Node reproduction: a fresh fd opened against the
target before the operator's writeCredentialFile call reads the JSON-
encoded credential immediately after the call returns, regardless of the
chmod that follows.

Fix replaces fs.access + fs.writeFile + fs.chmod with a single
fs.open(resolved, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, 0o600). The
mode is set at create time. O_NOFOLLOW makes open fail with ELOOP if the
final path component is a symlink. O_EXCL makes open fail with EEXIST if
the file already exists. Force-mode unlinks any pre-existing entry first
(operating on the symlink itself via fs.unlink, not the target via
fs.writeFile), then takes the same atomic-create path.

Tests added to credential-output.test.ts:
- refuses to write through a symbolic link without force
- refuses to write through a symbolic link with force (target untouched)
- TOCTOU race-fail-closed
- 0o600 mode produced even with --force (regression guard for the
  removed fs.chmod step)

`pnpm test` is green (117/117 across 11 files). Negative control: with
the implementation reverted, two of the new tests fail because the
symlink target is overwritten by the credential JSON.

* security(credential-output): scope POSIX-only protection explicitly

Addresses review feedback on PR #94. Gates O_NOFOLLOW as
constants.O_NOFOLLOW ?? 0 with an inline note on the Windows gap
(O_EXCL still prevents overwriting a pre-existing entry, but the path
no longer provides full no-follow protection there). Trims in-file
comment; the full attack chain stays in the PR body and tests. Drops
the biome-ignore directive that pointed at a non-existent rule.

* security(credential-output): refuse symlinks even with --force

Addresses review feedback on PR #94. The previous revision unlinked any
pre-existing entry in --force mode and then ran the atomic open with
O_EXCL | O_NOFOLLOW. That cleared the path for the create but also
silently destroyed pre-existing symlinks, and the no-follow guarantee
only covered the race window after the unlink.

This refactor inverts the precheck: lstat first, refuse outright when
the final path is a symlink (with or without --force), and only unlink
and recreate for non-symlink existing files. O_EXCL | O_NOFOLLOW remains
the race defense between the precheck and the atomic create.

Tests updated. The without-force symlink case now expects
OUTPUT_FILE_SYMLINK and asserts the symlink survives. The with-force
case is renamed to make the new contract obvious and gets the same
assertions. The dedicated post-unlink-race test is dropped because its
premise (force unlinks the symlink) no longer applies; the regular-file
race defense is exercised by the existing 0o600 test.
2026-05-27 08:54:28 -04:00
Arpit Jain 1bdbbe69eb ci: declare contents:read on CI workflow (#102)
CI builds, typechecks, runs biome and pnpm tests, and finally
`pnpm publish --dry-run`. None of these reach beyond the working
tree. Declaring contents:read at workflow scope makes that intent
explicit and matches the workflow-level permissions blocks that
release.yml and issue-triggered-create-jira.yml already carry.

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
2026-05-22 11:49:35 -04:00
kreese-stripe 27b3de936a feat: support polling straight from auth login (#78)
* support polling straight from auth login

* improvements

* sanitize output; make the polling into a shared helper and share across the auth login polling and the auth status polling
2026-05-21 13:24:55 -04:00
nvp-stripe bed025fb36 Add web-bot-auth sign <url> command (#106)
* web bot auth command

* improve tests
2026-05-20 20:05:29 -04:00
nvp-stripe cc4adb28bf feat(sdk): add WebBotAuthResource for Web Bot Auth header minting (#105)
* added WebBotAuth changes

* code quality improvements

* fix build error

* address CR comments
2026-05-20 10:08:40 -04:00
Dan Hill 0cd466dbe9 fix: remove build numbers (#103) 2026-05-18 14:07:45 -04:00
Dan Hill baa5aabf1d Bump to 0.5.0 publish (#99) @stripe/link-cli@0.5.0 2026-05-11 16:05:25 -04:00
Dan Hill 4121c7b5cd Bump to 0.5.0 (#97) 2026-05-11 15:37:51 -04:00
kreese-stripe 8a194d914e Update the docs for totals to reflect new accepted values (#96) 2026-05-11 11:08:10 -04:00
bendavis-stripe 0387b25ee6 fix: sanitize server-returned text to prevent ANSI escape injection (#85)
Server-returned string fields (merchant_name, line_items[].name,
billing_address.*, payment method brand/nickname) are now sanitized
before rendering in Ink components using strip-ansi plus control
character stripping. This prevents terminal escape sequence injection
that could spoof the approval UI.


Committed-By-Agent: claude

Co-authored-by: Ben Davis <ben@bencdavis.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 15:38:29 -04:00
Shubham Mathur f455286336 fix: CLI Approval Polling Bugs (surfaced by #63) (#95)
* fix: recognize all terminal statuses in approval polling

* fix: extend default spend-request polling window past server-side expiry
2026-05-10 15:38:12 -04:00
Dan Hill a8a8933cfa fix: swallowing mpp errors (#93) 2026-05-08 17:46:35 -04:00
Dan Hill 90c88cf397 fix: apply improvements to new code areas (#92) 2026-05-08 13:58:43 -04:00
Dan Hill f793732327 Render interactive helper (#88)
* refactor: extract renderInteractive helper to reduce command boilerplate

Committed-By-Agent: claude

* fix: address review findings for renderInteractive helper

Committed-By-Agent: claude

* fix: resolve biome lint and formatting errors

Committed-By-Agent: claude
2026-05-08 13:01:21 -04:00
Dan Hill 6c0f92ffc3 Add versioning to the skill file (#91)
* feat: add versioning to the skill file

* fix: formatting
2026-05-08 12:53:44 -04:00
Dan Hill f0b391dc90 Use async action hook (#89)
* refactor: extract useAsyncAction hook for async command components

Committed-By-Agent: claude

* fix: add cleanup, ref stability, and safer error handling to useAsyncAction

Committed-By-Agent: claude

* fix: cleanup
2026-05-08 12:50:00 -04:00
Dan Hill de0aa8ddb2 Poll until utility (#90)
* refactor: extract pollUntil utility to consolidate polling logic

Committed-By-Agent: claude

* fix: address review findings for pollUntil utility

Committed-By-Agent: claude

* fix: handle timeout=0 correctly in pollUntil (immediate deadline)

Committed-By-Agent: claude

* fix: resolve biome formatting

Committed-By-Agent: claude
2026-05-08 12:46:53 -04:00
Dan Hill 281b61c38b Specify credential file (#81)
* feat: allow specifying a credential file

* copy cleanup

* fix: make flag clearer
2026-05-08 12:33:40 -04:00
Dan Hill cd53dbee6d Refactor Auth (#87)
* refactor: apply requireAuth uniformly across all authenticated commands

Committed-By-Agent: claude

* refactor: use incur middleware for auth on non-generator commands

Use middleware: [requireAuth] on update, cancel, list, add, pay.
Use requireAuthGuard(c) inline for generator commands (create,
request-approval, retrieve) due to incur limitation with middleware
+ async generators + c.error() return values.

Committed-By-Agent: claude
2026-05-08 12:18:36 -04:00
Ryan Aubrey 68d7d0802a Add support for user-info (#84) 2026-05-08 11:18:17 -04:00
Dan Hill 76f6245ad1 Display delay constant (#86)
* refactor: standardize display delay timeout to single constant

Committed-By-Agent: claude

* fix: apply DISPLAY_DELAY_MS to missed instance in use-approval-polling

Committed-By-Agent: claude
2026-05-07 22:53:27 -04:00
Ryan Aubrey 20c59dc6ab feat: shipping addresses (#79)
* Basic command, readme, skill file

* Skill update

* Better formatting

* Formatting

* Simplify skill file
2026-05-07 18:02:29 -04:00
Brendan Ryan c78c467be8 chore: bump mppx to 0.6.16 (#83) 2026-05-07 15:06:00 -04:00
kreese-stripe 6e1e9d2f66 (feat): Commands to cancel a spend request (#77)
* commands to cancel a spend request

* document

* nit: remove unneeded status text

* code improvements

* Fix typecheck

* Just go back to old promise approach
2026-05-07 14:43:35 -04:00
bensnell-stripe dac4966646 fix: bump incur to 0.4.5 to fix misleading validation errors (#82)
When context was provided but too short (< 100 chars), the CLI
incorrectly reported "Error: missing required argument <context>"
instead of the actual Zod validation message. This was fixed
upstream in incur PR #138 (incur@0.4.5) which distinguishes
between truly missing fields and invalid values.

Fixes LINK_AI_WALLET-208

Committed-By-Agent: claude
2026-05-07 09:56:56 -07:00
Dan Hill e14420ae2c fix: correctly handle claude plugin paths (#75)
* fix: correctly handle claude plugin paths

* fix: codex
2026-05-06 12:07:54 -04:00
github-actions[bot] 5f0e2061e8 Version Packages (#69)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@stripe/link-cli@0.4.3
2026-05-05 07:38:18 -04:00
Dan Hill 8cc93e9906 feat: allow writing payment credentials to file to avoid context (#67) 2026-05-04 17:50:34 -04:00
Dan Hill 046557ebda Docs, skill improvements 2026-05-03 (#68)
* WIP

* fix totals schema type

* Improve copy with dante

* fix: formatting

* fix: minor path fix

* fix: correctly handle update notifier in TTY and non-TTY modes

* fix: update package to correct

* fix: formatting

* fix: improve update notifier
2026-05-04 17:49:03 -04:00
Gus 59a0fe1956 fix(sdk): restrict the auth config file to the owning user (mode 0o600) (#51)
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).
2026-05-04 17:48:52 -04:00
github-actions[bot] 5c49714bce Version Packages (#66)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@stripe/link-cli@0.4.2
2026-05-02 13:07:29 -04:00
Dan Hill 90fc1838a1 Improve plugins (#64)
* feat: add claude marketplace.json and improve plugins

* changeset

* fix: formatting
2026-05-02 13:02:17 -04:00
Dan Hill 4ee0eb19e1 fix: better document how line items and totals can be passed (#65) 2026-05-02 13:01:15 -04:00
Ryan Aubrey 317eba8a1f Add viem as an explicit dependency (#61) 2026-05-02 12:54:04 -04:00
github-actions[bot] afd09b4e70 Version Packages (#53)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@stripe/link-cli@0.4.1
2026-04-29 20:45:31 -04:00
Pejman Pour-Moezzi 21bc584091 fix: fail spend request polling timeout (#56) 2026-04-29 19:53:25 -04:00
Dan Hill 5e03819589 fix: bump npm (#52) 2026-04-29 14:16:42 -04:00
github-actions[bot] 28fac7f129 Version Packages (#50)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@stripe/link-cli@0.4.0
2026-04-29 14:13:44 -04:00
Dan Hill a618ce232d Fix oncomplete (#49)
* 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
2026-04-29 14:03:00 -04:00
Dan Hill 60551347c3 Ink cleanup (#48) 2026-04-29 13:46:20 -04:00
github-actions[bot] a4657c9e3f Version Packages (#47)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@stripe/link-cli@0.3.1
2026-04-29 11:57:53 -04:00
Steve Kaliski b061e10d27 checklist for onboard/demo flow (#45)
* 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
2026-04-29 11:54:24 -04:00
jliwag-stripe 334ff3986d Update issue-triggered-create-jira.yml (#43)
Change the target project from PQTEST to PQ.
2026-04-28 17:31:51 -07:00
Ryan Aubrey ec7117bf9f Add back human output for auth status (#44) 2026-04-28 20:29:08 -04:00