fix(cloud): disclose + confirm login target URL to block config phishing (#400) - #401
Merged
Merged
Conversation
…ing (#400) A malicious `.specnaut/backlog-config.yml` could point `api_url` at an attacker HTTPS host; `specnaut cloud login` would then silently run the device/browser flow against it. (Read commands — token/orgs/board — were already safe: creds are keyed by apiUrl, so an unknown URL yields no token.) Hardening, in the shared resolution path: - `resolveApiUrl` now returns `{ url, source }` (flag | config | prompt). - `runLogin` discloses the target before opening the browser: Connecting to: <url> Source: <where it came from> - An explicit y/N confirmation is required ONLY for the precise phishing window — `source === "config"` AND no stored creds for that URL (first-ever auth against a config-supplied host). `--api-url`, a typed prompt, and re-login to a known deployment stay friction-free. Default-deny (empty/EOF → cancel). Design reviewed by the architect (keyed-store reality → disclosure over a blanket gate; no CredentialStore.list() since keychains can't enumerate). Verified by security-auditor: primary phishing window closed, 0 critical/high, no bypass. Residual (re-login to a URL already confirmed once) is acknowledged in-code; disclosure still renders. 2 pure-helper unit tests. deno lint/check clean, 1036/1036 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #400.
Threat
A malicious
.specnaut/backlog-config.yml(committed to a repo a victim clones) setsapi_url:to an attackerhttps://host.specnaut cloud loginwould then silently run the device/browser flow against it — a phishing vector. (The read commandstoken/orgs/boardwere already safe: credentials are keyed byapiUrl, so an unknown/attacker URL yields no stored token → "not authenticated", token never sent.)Fix (in the shared resolution path)
resolveApiUrlnow returns{ url, source }wheresource ∈ {flag, config, prompt}.runLogindiscloses the target before opening the browser:source === "config"and no stored creds for that URL (first-ever auth against a config-supplied host).--api-url, an interactively-typed URL, and re-login to a known deployment proceed unprompted, so theinit → loginhappy path keeps no friction. Default-deny (empty/EOF → cancelled,login()never called).Two pure helpers (
urlSourceLabel,loginNeedsTrustConfirm) carry the logic and are unit-tested.Review
CredentialStore.list()(OS keychains can't enumerate via FFI).Out of scope / residual
The config-URL-swap-after-prior-auth case (requires the user to have already approved that URL once) is documented in the code comment; not addressed here to avoid scope creep.
Verification
deno task lint+deno task checkclean ·deno task test1036/1036 · smoke ofcloud token/logoutURL resolution ✓Agent adoption
specnaut cloud login(and thespecnaut loginalias) now prints the target server URL and where it came from before opening the browser, and asks for confirmation the first time you authenticate against a URL that came from a project's.specnaut/backlog-config.yml. No action needed for normal use; it's a security disclosure.🤖 Generated with Claude Code