Audit: fix auth/config bugs, add issue/pr create, tests and docs #2

Merged
claude-bot-andresmgsl merged 1 commit from audit/fixes-and-hardening into main 2026-07-22 19:35:52 +00:00

Full audit of the CLI, with every finding verified against a live Forgejo instance before fixing. All fixes were re-verified live after the change, and the new issue create / pr create commands were used to file issue #1 and this very PR.

Bugs fixed (all reproduced before fixing)

1. auth logout could never revoke the token (silent security issue)

Forgejo only accepts Basic auth on the token endpoints — a token cannot revoke itself (the API answers 401). Every logout printed a warning and left the token active on the server, forever. Logout now accepts -p/--password, --password-file, prompts on a TTY, supports --local-only, and clearly says when a token is left active and where to revoke it. Verified live: revocation now succeeds.

2. The "silent" password prompt echoed the password to the terminal

rl.write = () => {} does not suppress readline echo — keystrokes flow through _writeToOutput. Worse, the readline/promises interface doesn't honor that hook at all. Switched to the callback readline module with _writeToOutput muted. Verified on a real pty: the password no longer appears on screen.

3. Global --config flag was silently ignored

Config paths were computed at require time, before the preAction hook set STOKE_CONFIG_FILE, so --config had no effect and commands silently used the default config. Paths are now resolved lazily (getConfigPath()), with a regression test.

4. XDG_CONFIG_HOME mis-resolved

Config went to $XDG_CONFIG_HOME/.config/stoke; per the XDG spec it now goes to $XDG_CONFIG_HOME/stoke.

5. repo create --auto-init could not be disabled

Boolean flag defaulted to true with no negation — added --no-auto-init.

6. Imports demanded a GitHub token for non-GitHub services

repo import --service git (and batch entries with any non-GitHub service) failed without gh/GITHUB_TOKEN even though no token was needed. Tokens are now only auto-resolved for service: github; batch imports resolve per entry and memoize the gh auth token call.

7. Branding leftovers from the forgejo-cli rename

Error hint said Run: forgejo auth login; User-Agent was forgejo-cli/1.0.0. Both now say stoke, with the UA tracking package.json version.

8. Robustness

  • fetch calls now have timeouts (30s default; 10 min for migrations, which legitimately take long).
  • --limit / --team-id are validated as integers — previously -l abc became NaN and silently printed all results.

New commands

Per the repo's stated design ("every real operation performed against Forgejo becomes a new CLI command"):

  • stoke issue create — title, --body/--body-file, --assignee
  • stoke pr create--head/--base, title, --body/--body-file

Tests

The npm test script pointed at a nonexistent test/ directory and vacuously passed with 0 tests. Added a 25-test suite on the built-in node:test runner (zero new dependencies):

  • config.test.js — path precedence, XDG semantics, lazy resolution, save/load/clear round-trip, 0600 permissions, corrupt-file errors
  • api.test.js — auth header selection, Basic-auth token deletion, pagination, error propagation, URL encoding, new endpoints (mocked fetch)
  • cli.test.js — spawned-process checks: --version, --help completeness, login hint, --config override regression test, numeric validation, body-file errors

Docs

  • Corrected the Node requirement (README said 18; commander@15 requires >= 22.12 — install on Node 18 was already impossible)
  • Documented repo rename (was completely missing), issue create, pr create, logout options and the Basic-auth revocation caveat, --no-auto-init, XDG behavior, and import token rules
  • Added a Testing section; architecture section covers test/

Metadata

  • package.json: engines >= 22.12.0, repository, keywords, author; version bumped to 1.1.0 (lockfile updated)

Verification

  • npm test — 25/25 pass
  • Live against forgejo.heavyduty.builders: interactive login on a pty (no password echo), auth status, logout with real remote revocation (DELETE .../tokens/{id} → 204), repo/issue/pr/branch list, and issue #1 + this PR created with the new commands

Closes nothing; complements #1 (apt packaging is tracked there).

🤖 Generated with Claude Code

Full audit of the CLI, with every finding verified against a live Forgejo instance before fixing. All fixes were re-verified live after the change, and the new `issue create` / `pr create` commands were used to file issue #1 and this very PR. ## Bugs fixed (all reproduced before fixing) ### 1. `auth logout` could never revoke the token (silent security issue) Forgejo only accepts **Basic auth** on the token endpoints — a token cannot revoke itself (the API answers 401). Every logout printed a warning and left the token active on the server, forever. Logout now accepts `-p/--password`, `--password-file`, prompts on a TTY, supports `--local-only`, and clearly says when a token is left active and where to revoke it. Verified live: revocation now succeeds. ### 2. The "silent" password prompt echoed the password to the terminal `rl.write = () => {}` does not suppress readline echo — keystrokes flow through `_writeToOutput`. Worse, the `readline/promises` interface doesn't honor that hook at all. Switched to the callback `readline` module with `_writeToOutput` muted. Verified on a real pty: the password no longer appears on screen. ### 3. Global `--config` flag was silently ignored Config paths were computed at `require` time, before the `preAction` hook set `STOKE_CONFIG_FILE`, so `--config` had no effect and commands silently used the default config. Paths are now resolved lazily (`getConfigPath()`), with a regression test. ### 4. `XDG_CONFIG_HOME` mis-resolved Config went to `$XDG_CONFIG_HOME/.config/stoke`; per the XDG spec it now goes to `$XDG_CONFIG_HOME/stoke`. ### 5. `repo create --auto-init` could not be disabled Boolean flag defaulted to `true` with no negation — added `--no-auto-init`. ### 6. Imports demanded a GitHub token for non-GitHub services `repo import --service git` (and batch entries with any non-GitHub service) failed without `gh`/`GITHUB_TOKEN` even though no token was needed. Tokens are now only auto-resolved for `service: github`; batch imports resolve per entry and memoize the `gh auth token` call. ### 7. Branding leftovers from the `forgejo-cli` rename Error hint said `Run: forgejo auth login`; User-Agent was `forgejo-cli/1.0.0`. Both now say `stoke`, with the UA tracking `package.json` version. ### 8. Robustness - `fetch` calls now have timeouts (30s default; 10 min for migrations, which legitimately take long). - `--limit` / `--team-id` are validated as integers — previously `-l abc` became `NaN` and silently printed *all* results. ## New commands Per the repo's stated design ("every real operation performed against Forgejo becomes a new CLI command"): - `stoke issue create` — title, `--body`/`--body-file`, `--assignee` - `stoke pr create` — `--head`/`--base`, title, `--body`/`--body-file` ## Tests The `npm test` script pointed at a nonexistent `test/` directory and vacuously passed with 0 tests. Added a 25-test suite on the built-in `node:test` runner (zero new dependencies): - `config.test.js` — path precedence, XDG semantics, lazy resolution, save/load/clear round-trip, `0600` permissions, corrupt-file errors - `api.test.js` — auth header selection, Basic-auth token deletion, pagination, error propagation, URL encoding, new endpoints (mocked `fetch`) - `cli.test.js` — spawned-process checks: `--version`, `--help` completeness, login hint, **`--config` override regression test**, numeric validation, body-file errors ## Docs - Corrected the Node requirement (README said 18; `commander@15` requires >= 22.12 — install on Node 18 was already impossible) - Documented `repo rename` (was completely missing), `issue create`, `pr create`, logout options and the Basic-auth revocation caveat, `--no-auto-init`, XDG behavior, and import token rules - Added a Testing section; architecture section covers `test/` ## Metadata - `package.json`: `engines >= 22.12.0`, `repository`, `keywords`, `author`; version bumped to 1.1.0 (lockfile updated) ## Verification - `npm test` — 25/25 pass - Live against `forgejo.heavyduty.builders`: interactive login on a pty (no password echo), `auth status`, logout with real remote revocation (`DELETE .../tokens/{id}` → 204), repo/issue/pr/branch list, and issue #1 + this PR created with the new commands Closes nothing; complements #1 (apt packaging is tracked there). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot-andresmgsl added 1 commit 2026-07-22 19:33:15 +00:00
Fixes found during a full audit of the CLI:

- auth logout: remote token revocation always failed with 401 because
  Forgejo only accepts Basic auth on the token endpoints. Logout now
  asks for (or accepts) the account password, supports --password,
  --password-file and --local-only, and clearly reports when the token
  is left active.
- Silent password prompt actually echoed the password on a TTY:
  overriding rl.write does not suppress readline echo. Switched to the
  callback readline module and mute _writeToOutput instead (the
  readline/promises interface does not honor that hook).
- Global --config flag was silently ignored: config paths were resolved
  at require time, before the preAction hook set STOKE_CONFIG_FILE.
  Paths are now resolved lazily on every access.
- XDG_CONFIG_HOME handling put the config in $XDG_CONFIG_HOME/.config/stoke;
  per the XDG spec it now resolves to $XDG_CONFIG_HOME/stoke.
- repo create: --auto-init defaulted to true with no way to disable it;
  added --no-auto-init.
- repo import/import-batch: a GitHub token was required even for
  non-GitHub services (e.g. --service git), making those imports fail
  without gh/GITHUB_TOKEN. Tokens are now only auto-resolved for the
  github service; batch imports resolve per entry and memoize.
- Branding leftovers: 'Run: forgejo auth login' hint and
  forgejo-cli/1.0.0 User-Agent now say stoke (UA tracks pkg.version).
- Added request timeouts (30s default, 10m for migrations).
- --limit and --team-id are validated as integers instead of silently
  misbehaving on garbage (NaN made -l show all results).

New commands (per the repo's every-operation-becomes-a-command design):

- stoke issue create (title/body/body-file/assignees)
- stoke pr create (head/base/title/body/body-file)

Tests and metadata:

- New test suite on the built-in node:test runner (25 tests) covering
  config resolution/persistence, the API client with a mocked fetch,
  and end-to-end CLI behavior. npm test previously matched no files.
- package.json: engines >=22.12.0 (required by commander@15 — the
  README claimed Node 18), repository, keywords, author; version 1.1.0.
- README: corrected Node requirement, documented repo rename (was
  missing), issue create, pr create, logout options and revocation
  caveat, --no-auto-init, XDG behavior, import token rules, testing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot-andresmgsl merged commit 007b09e3be into main 2026-07-22 19:35:52 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/stoke#2
No description provided.