The 21 marketplace commands, registered and granted; marketplace fields
kept store-owned in update_settings/update_project; a background refresh
of every marketplace at app start.
- apply_marketplace_now emits marketplace-sync-finished per project (F4).
- Settings export carries marketplace account tokens in ExportedSecrets
(account id -> token) and import restores them; imported accounts,
marketplaces and global installs are validated with the commands' own
rules before anything is written. The import preview discloses the
marketplace count, token count and global hook installs, and warns on
the latter (F10).
- refresh_pins and cache removal hold the repo lock (F11).
- ops::validate_host/validate_branch delegate to auth::valid_host and
git::valid_branch (F13).
- A finished gh container login frees only its own cancel slot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lost stream or a failed Enter write returned without killing the
in-container gh, leaving it polling with a temp GH_CONFIG_DIR that would
receive the token. The output loop is now drive_login (generic over the
stream, writer and emitter, so it is unit-tested without Docker), and
its result goes through cleanup_on_error, so every ending except a token
read back runs the pkill. Neutral wording for the shared ANSI stripper's
overflow warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Waits for the entrypoint, uploads the payload and the sync script, runs it
as claude and stores its report (payload skips merged in). The start hook
spawns the sync in the background; a per-project lock serialises syncs of
one project (pre-flight F11b).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drives `gh auth login --web` in a running project container over an
attached pty, with GH_CONFIG_DIR/GIT_CONFIG_GLOBAL in a temp dir that is
removed on exit (also on HUP/INT/TERM), emits the one-time code and
redacted output lines, and returns the token read back between markers.
Host validation reuses auth::valid_host plus a no-port check (F13); a
cancel or timeout also pkills the in-container login (N9). Reuses the
setup-token flow's AnsiStripper, push_capped_tail and Enter delay, made
pub(crate) without behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removal only derives a path from an exact <kind>:<key> state id with a known
kind; any other record is dropped with an error and nothing is deleted
(an id like "skill" used to remove ~/.claude/skills/skill). Invalid plugin
records are dropped too, and a failed chmod 600 on settings.json is reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Validate manifest structure up front; malformed items are skipped with a
reason instead of aborting extraction; an unreadable manifest changes
nothing (no removals).
- Empty/whitespace settings.json reads as {}; non-object settings are left
untouched; hook installs/updates/removals are reported and recorded only
once their entries are actually merged; mv failures are checked.
- Dangling symlinks at user paths count as occupied.
- Removal paths are derived from kind+key, never taken from state.json.
- A symlinked settings.json is written through, not replaced.
- mktemp failure emits a JSON report instead of exiting silently.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Constant POSIX sh + jq script (embedded via include_str!) that applies the
payload into ~/.claude, tracks ownership in state.json, never overwrites
user-owned files, merges hook entries surgically, drives claude plugin and
prints a JSON SyncReport. Also: settings.json kept 0600 (pre-flight N11),
payloads containing symlinks are refused, slugs parsed via @tsv.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds diff::item_diff (similar), MarketplaceManager with snapshots,
persisted sync reports, the gh-login slot and a repo lock held across
fetches (pre-flight F11a), refresh_marketplace, load_cached_snapshot,
compute_updates, pins_by_marketplace, head_for, and the GitFixture test
helper on top of git::test_support (F3). AppState gains marketplace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fix round 1 for Task 5. reqwest's default redirect policy only
strips Authorization/Cookie/Proxy-Authorization/WWW-Authenticate on a
cross-host hop, so GitLab's PRIVATE-TOKEN header (and Authorization on
an https->http same-host downgrade) would have followed a redirect to
an attacker-controlled target. The client now disables redirects
outright, and a 3xx response is treated as "not this kind of host"
rather than an error. Also adds the missing N17 test for a malformed,
credential-bearing URL, and clarifies two doc comments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anonymous fetches of private repos map to Auth, error text drops gix
source locations and names the innermost network cause, and
valid_branch is pub(crate) for the add form (pre-flight F1, F2, F13).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix round 1 from PR review of the tree/catalog parsing:
- collect_dir now rejects an entry whose name is ".", "..", empty, or
contains "/", "\" or NUL before it becomes part of an item's rel_path —
a crafted git tree could otherwise walk a file outside the item's own
folder once that path is joined against the item root downstream.
- collect_dir caps recursion at 32 directory levels and counts
directories (not just files) toward MAX_ITEM_FILES, so a tree that is
wide or deep rather than merely file-heavy is still bounded.
- hook.json and plugins/.claude-plugin/marketplace.json are now rejected
unparsed above 1 MiB, rather than handed to serde_json regardless of
size.
A pre-read size query (checking a blob's size before reading it) is
deferred per controller ruling — this round reads the blob and checks
its length before parsing, which is enough for the JSON-parsing DoS
shape being closed here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>