4e6c57b4ccf8539fcdbbd2088e7b539c3de35c2e
1889 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4e6c57b4cc | fix(agents): relax OpenCode install detection (#1967) | ||
|
|
68250fe3b3 | fix(acp): guard background work status (#1966) | ||
|
|
49e00447a5 | fix: restore legacy GitHub launch compatibility (#1962) | ||
|
|
b20047b3eb |
feat(plugins): add auth capability for plugin-provided OIDC/SAML login (#1964)
* feat(plugins): add auth capability for plugin-provided OIDC/SAML login Plugins can now authenticate a visitor against an external IdP (OIDC/SAML) and contribute a login button to the pre-auth screen: - new `auth` manifest capability + a capability-gated X-Kandev-Auth-Login webhook-response directive; the host mints the kandev_session (ADR 0050), the plugin never receives the raw token. Plugin-set Set-Cookie is dropped. - auth.Service.AuthenticateExternal: existing-identity / link-by-email / JIT member provisioning; refuses to auto-link to an admin; verified-email is the plugin's contract. - auth_providers manifest block + plugins.SSOProviders() populate the anonymous boot payload (auth.ssoProviders); the login page renders a "Continue with <provider>" button per provider. - ADR 0050 + plugin-manifest / plugin-authoring / auth spec docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: exclude publish_review_findings_kandev from public-docs coverage The native code-review agent (#1957) added the publish_review_findings_kandev MCP tool but did not update docs/public/coverage.json; that PR did not touch docs/public so the path-filtered validator never ran. It's an internal review reporting tool, not a user-facing feature, so record it as an exclusion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins,auth): address review on SSO directive and JIT provisioning - takeAuthLoginDirective strips every case variant of X-Kandev-Auth-Login and rejects (403) when a plugin sends more than one, so no variant leaks to the browser and login never uses a nondeterministically-picked assertion. - provisionExternalUser rolls back the just-created user when the identity insert fails (transient error or a lost UNIQUE(provider,subject) race) and resolves to the winning identity, so no account is left without a login and no email is reserved by an unusable row. Adds userstore.DeleteUser. - auth spec: mark the login-page SSO buttons + provider discovery as landed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: drop redundant coverage exclusion; main now covers publish_review_findings_kandev Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a127e2dffd |
feat(review): navigate to code-review findings from the Changes panel (#1963)
* feat(review): navigate to code-review findings from the Changes panel The findings count in the review top bar was a passive badge, so a user had to scroll the diff to hunt for each inline finding. Turn it into a navigator: it opens a popover listing every open finding grouped by file (most-severe first), and clicking one jumps to the finding's card in the diff, scrolls it into view, and briefly flashes it. - lib/review/navigation.ts: navigateToFinding selects the finding's file, emits an event, then polls across frames (files render lazily) for the card and scrolls + flashes it. Reuses the composite <repo>\x00<path> key so cross-repo same-named files stay distinct. - review-findings-overview + review-findings-button: the grouped list and its click-to-open popover trigger, mirroring the FixCommentsButton / ReviewCommentsOverview pattern. The badge moves out of ReviewRunButton into the top bar, keeping the review-open-count testid and copy. - inline-review-finding: stamps data-review-finding-id + scroll margin so navigation can target the card. - unanchored-findings-banner: auto-expands when a navigation targets one of its stale findings so that card renders and can be scrolled to. - globals.css: review-finding-flash highlight, respecting reduced motion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(review): harden finding navigation from code-review feedback Address automated review of the findings navigator: - navigation.ts: scope the finding lookup to the open review dialog so a duplicate card rendered by the Changes panel behind it is not scrolled to instead; re-emit the navigate event each poll frame so a lazily mounted stale-findings banner still expands (a one-shot event fired before the section rendered was missed); honor prefers-reduced-motion for the scroll, matching the flash animation. - review-dialog-surface: clear the sidebar file filter before selecting a finding's file, so a finding in a filtered-out file still mounts. - review-findings-button: surface a warning when the card can't be reached instead of discarding the promise silently. - review-findings-overview: compute the file directory once per row. - Tests: unit coverage for the banner auto-expand and the findings button (null render + navigate), plus a mobile-* e2e that taps the navigator and verifies the target card is reached. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bb72d70586 |
refactor(backend): remove dead code and deprecated shims (#1965)
Delete unreachable/unused backend symbols confirmed by staticcheck U1000
and golang.org/x/tools/deadcode, plus deprecated back-compat aliases kept
alive only by their own tests.
Removed (unused):
- parseGitHubRepoURL, syncTaskStateForBulkWorkflowMove (task/service)
- resolvePrimaryRepoInfo (orchestrator/executor)
- resolveWorkflowAgentProfile, workflowDefaultAgentProfile wrappers (mcp/handlers)
- cleanupExitedContainer (runtime/lifecycle) + orphaned fmt import
- VscodeManager.setEnv (agentctl/server/process)
- repoRootDir const (cmd/preview)
Removed (deprecated shims):
- Canceller.CancelSessionAndNotify alias -> callers use DetachSessionAndNotify
- ScriptPlaceholder type alias (live scriptPlaceholders var kept)
- Placeholder{QueueRun,ClearDecisions,QueueRunForEachParticipant}Callback
no-op structs and their self-referential test
No behavior change; build, vet, and affected-package tests pass.
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
45974b513a | fix(acp): deliver follow-ups after foreground handoff (#1959) | ||
|
|
3b9a4be278 | fix(orchestrator): retire execution activity on teardown (#1961) | ||
|
|
116f7557dd |
fix(auth): scope integration config/watch routes to workspace owner (#1960)
* fix(auth): scope integration config/watch routes to workspace owner
The third-party integration route group is guarded by its own
query-only middleware (integrationWorkspaceScopeMiddleware), which
authorizes only the workspace_id query param and falls through when it
is absent. Two shapes bypassed it entirely under enforced auth:
- POST /{jira,linear,slack}/config/copy takes the copy TARGET from the
request body (targetWorkspaceId), which no layer checked. A caller
could copy their own connection settings + credential into another
user's workspace by naming its ID, after which that workspace's
identity-less poller would run against attacker-controlled config
(Jira/Linear issue watchers auto-creating tasks from attacker issues;
Slack bot token/webhook). CopyConfigToWorkspace now authorizes BOTH
source and target up front, before the pre-write UpdateAuthHealth
side effect. Mirrors github/copy.go.
- GET /{jira,linear}/watches/issue with workspace_id omitted returned
ListAllIssueWatches across every workspace (JQL/filters, repo, agent
and executor profile IDs, spawn prompt). It now filters to the
caller's workspaces; an identity-less internal caller still sees all,
preserving poller use.
- The config Get/Set/Delete entry points resolved an unowned global
"default" workspace when workspace_id was omitted; they now
resolve+authorize via resolveWorkspaceID.
Each service gains SetWorkspaceAuthorizer(taskSvc.AuthorizeWorkspaceAccess),
wired in backendapp; nil (unit tests, auth disabled) stays unscoped.
Denials surface repoerrors.ErrWorkspaceNotFound, mapped to 404 by the
handlers (no existence leak). Recorded the query-only-middleware rule in
integrations/AGENTS.md, including that Sentry and GitLab's
ListAllIssueWatches still need the same filter.
Regression tests added per package and mutation-verified: dropping only
the copy up-front guard fails on the victim health-flip witness;
dropping both guards fails on the config/secret witness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(auth): scope integration watch-write and data-plane paths too
Review of the prior commit (Codex + Claude, converging) found the same
body/query workspace-bypass class in surfaces the first pass missed:
- CreateIssueWatch / UpdateIssueWatch (Jira, Linear) never authorized the
workspace. CreateIssueWatch takes WorkspaceID from the request body, so
a caller could plant a persistent watch in another user's workspace that
repeatedly spawns tasks with that user's credentials and an
attacker-supplied filter/prompt. Now authorize req.WorkspaceID (create)
and the loaded row's workspace (update). The by-ID mutation/trigger/reset
handlers were already guarded by assertWatchInWorkspace + the
query-authorizing middleware; the service-layer guard adds defense in
depth and covers non-HTTP callers.
- TestConnectionForWorkspace (Jira, Linear, Slack) resolved the default
workspace with no auth, so an omitted workspace_id + attacker-supplied
siteUrl could exfiltrate that workspace's stored token. Now authorizes
after normalize and returns a hard error (404), not a swallowed
{OK:false}.
- Data-plane reads funnel through clientFor, which now resolves+authorizes
the workspace — scoping search/projects/statuses in one chokepoint.
- filterIssueWatchesByAccess treated ANY authorizer error as a denial,
silently dropping watches on a transient failure and returning a
truncated 200. It now drops only on ErrWorkspaceNotFound and propagates
everything else.
Denials map to 404 via the shared writeClientError / writeIssueWatchError
sinks and the two list/test handlers. Renamed the test helper
denyExcept -> denyOnly (it denies exactly the named workspaces). Added
per-package regression tests for create/update/list denial, authorizer
error propagation, connection-test denial, and data-plane denial;
mutation-verified the CreateIssueWatch guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9c75c8c72c | feat: move task list options into mobile menu (#1956) | ||
|
|
57adf1449c |
feat(review): native code-review agent with inline findings in the Changes panel (#1957)
* feat(review): persist and publish native code-review findings Adds the backend core for reviewing a task's working diff before a PR exists: task-scoped review runs and anchored, advisory findings that the Changes/Review panel can render as inline comments. - task_review_runs / task_review_findings schema, models, and repository, with supersede-on-re-review and a restart sweep that cancels in-flight runs rather than leaving them looking alive. - ReviewService as the single write path, publishing bus events that the WS gateway forwards to the client. - internal/review: reviewer-identity resolution (agent profile > code-review utility agent > user default, failing closed on passthrough-only profiles), change-set collection, prompt batching, response parsing, anchor extraction, and run orchestration. - publish_review_findings_kandev MCP tool plus the task.review.* WS actions. - Go djb2 that matches apps/web/lib/utils/hash.ts, asserted from shared fixtures on both sides so stale detection agrees across the wire. Spec: docs/specs/native-code-review/spec.md Plan: docs/plans/native-code-review/plan.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(review): add run_code_review workflow step and wire the review runner Makes a review pass triggerable both ways and actually runnable end to end. - run_code_review on_enter action across models, engine compile, and an orchestrator callback that logs rather than propagates failures, so a task never gets stuck in a step because no reviewer was configured. - The step's optional agent_profile_id is what lets a step review with a different model than it implemented with; export/import swap it for a portable agent name/model/mode so it survives a cross-workspace round trip instead of carrying a dangling id. - Concrete adapters for reviewer identity, changed-file collection, prompt rendering from the code-review utility agent's editable prompt, and inference (session-bound agentctl, falling back to the host instance so a review works on a task whose agent is not running). - Boot sweep cancels review runs interrupted by a restart; the E2E reset clears review state before deleting the tasks that own it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(web): add review findings state, helpers, and live updates Frontend plumbing for native code review, ahead of the UI that renders it. - Review types, WS message map, and an API client that preserves the backend's machine-readable failure code so the surface can show an actionable message. - A review store slice that merges findings by id (a re-publish refreshes an issue rather than listing it twice) and keeps a bounded run history. - Findings helpers: composite file keys matching reviewFileKey, staleness via the same per-file diff hash that drives review marks, and best-effort re-anchoring from the stored anchor text so a moved finding follows the code it was written about instead of pointing at whatever now occupies its line. - Hooks for backfill, resolve/dismiss/reopen with optimistic rollback, and sending a finding to the agent as follow-up context without changing its status — handing a finding to an agent is not the human accepting it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(web): render review findings inline in the Changes diff Findings now appear where the code is, in the same annotation surface as anchored review comments, with the run control that produces them. - ReviewFindingCard: severity chip, category, Markdown body, and the three advisory actions. A suggestion is labelled "not applied automatically" because it is display-only in this iteration. Resolved and dismissed findings collapse to one muted line with an Undo, so a reviewed diff does not stay cluttered while the human can still change their mind. - A review-finding annotation type threaded through the diff viewer, anchored from the same per-file diff hash the backend stamped. A finding whose diff moved is re-anchored from its stored text when that text survives, and otherwise reported as unanchored rather than placed against unrelated code. - Review changes control with idle / running / cancel / failed states, and an inline Settings link for the one failure the user can actually fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(web): make the review control reachable from the Review toolbar Mounts the Review changes control next to the walkthrough action and lets an inline finding wire its own actions. The finding card container now reads the active task and session from the store instead of taking four callbacks threaded through FileDiffViewer and DiffViewer; an inline finding annotation only exists when the active task has findings, so the store is always present and every other diff consumer is spared the plumbing. Staleness likewise hashes the diff the viewer already holds, which is already normalized, so it matches the hash the backend stamped without passing it down. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(web): surface stale review findings above their file's diff A finding whose diff moved and whose anchor text is gone had nowhere to render, so it was silently dropped. It now appears in a collapsed per-file banner above the diff: the finding may still be valid, and placing it at its stored line would point at whatever code now occupies that line instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(review): mark the landed native code-review plan tasks done Tasks 01-09 (backend persistence through inline diff rendering) are implemented, tested, and committed. Tasks 10-13 — findings overview and step editor, mobile parity, E2E, and public docs — remain pending. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(review): cover the on-demand review path end to end Adds the mock-agent review responder and a Playwright spec, so both the happy path and the no-reviewer path are verified against a real backend. The mock agent answers any prompt carrying the review sentinel with fenced JSON, deriving its anchors from the prompt's own diff rather than hardcoding them, so findings land on files and lines the fixture really changed. It includes one deliberately malformed entry to exercise the rejected-entry path. Writing it caught an off-by-one shared by three diff walkers: splitting on "\n" leaves an empty element after a trailing newline, which carries no diff marker and was being counted as a context line, pushing new-side numbering one past the end of a hunk. Interior blank lines still count, since a whitespace-stripped blank context line legitimately appears as "". Also adds a listInferenceAgents helper: default_utility_agent_id needs the registered agent-type id, and an agent row UUID is rejected as not inference-capable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(review): close review-run races and stale client findings Addresses the three blockers and three suggestions from the PR #1957 automated reviews. Two independent reviewers found each blocker separately. - Launch claimed the task slot only after inserting the run row, so two concurrent launches both created a run and the loser returned a pending row with no goroutine behind it — a review that never finished until the next restart. The slot is now claimed before any DB write, and freed on every early return. - Cancelling only marked the row. The goroutine ran on the shared background context, finished inference, and its completion overwrote the cancelled status and published findings the user had declined. Runs now carry a cancel handle, the WS action routes through the runner so the live context is cancelled, CompleteRun/FailRun refuse to resurrect a terminal run, and a cancelled pass neither publishes nor re-reports itself as failed. - The backend deletes a superseded finding and inserts a replacement with a new id, but the publish event carried only the new one, so every open browser showed both at the same anchor until a reload. The event now names the superseded ids and the slice drops them. - Run() waited on the shared WaitGroup, blocking on every other task's review; it now waits on its own pass only. - createReviewSlice is typed over the `set` it actually uses, so call sites no longer pass superfluous arguments (CodeQL js/superfluous-trailing-arguments). - A GetAgentProfile fault no longer collapses into "profile not found", which would have told the user to configure an agent they already have. The cancel bug exposed a hole in the original tests: they asserted cancel could not override a terminal run, but never the reverse. Both directions are covered now, along with the concurrent-launch race, per-run waiting, and the supersede path on both sides of the wire. Test fakes now mirror the service's terminal guard rather than hiding it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6ffb23c187 | fix: harden background work lifecycle tracking (#1955) | ||
|
|
c3d235b83b |
fix(auth): close per-user session isolation gaps (service, WS, orchestrator) (#1939)
* fix(auth): scope session-keyed reads and mutations to the workspace owner Session-keyed service entry points did not apply per-user scoping, so knowing another user's session or task ID was enough to reach it: - GetTaskSession, ListTaskSessions, GetPrimarySession returned rows for any ID. These feed GET /task-sessions/:id and GET /tasks/:id/sessions, whose DTO carries session Metadata, the host worktree path, the branch, and container/executor IDs. - DismissLastAgentError mutated any session. - ApproveSession approved a workflow review step on any session, which advances the task's workflow — the highest-impact of the set. Authorize via authorizeTaskID off the row already read, rather than AuthorizeSessionAccess, which would re-read the session on what is a hot path. Internal identity-less callers (pollers, event bus, office schedulers, office agent JWTs) and the synthetic identity used while auth is disabled stay unscoped, and unowned pre-auth rows stay visible. Batch helpers that take ID lists are deliberately left alone: their callers derive IDs from an already-authorized task list, so per-ID checks would add N queries to list views for no gain. Recorded in AGENTS.md. Also route httpApproveSession through handleNotFound so a denial answers 404 like every other session route instead of a 500 that would confirm the session exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): scope the session-keyed paths that bypass the task service An adversarial sweep of the previous commit found session-keyed surfaces reachable with a caller-supplied ID and no ownership check. They bypass the task service, so scoping its methods did not cover them: - POST /task-sessions/:id/{set-mode,set-model,set-config-option, authenticate} and .../processes/:processId/stop resolve their execution with a bare in-memory lookup, which skips the GetOrEnsure* chokepoint where the lifecycle per-user check runs. A non-owner could switch another user's model or mode, re-authenticate their agent, or kill their process. Guarded by a shared denySessionAccess helper. - The orchestrator reads sessions through its own repo handle, so GetTaskSessionStatus and CheckSessionPR inherited nothing from the task service. CheckSessionPR also installs a PR watch as a side effect. Added a SetSessionAccessChecker hook mirroring the lifecycle manager's, wired to taskSvc.AuthorizeSessionAccess. The check runs before CheckSessionPR's unconfigured-GitHub early return so it applies regardless of setup, and denials reuse the "session not found" shape so they cannot be told apart from a missing session. - GetMessage had no scoping while ListMessages beside it did, so a caller holding another user's (session_id, message_id) pair could read their shell output. This does NOT close the whole surface. A second sweep found the orchestrator's session-mutating WS actions (session.stop/delete/rename/ set_primary/set_plan_mode/reset_context/recover/launch/ensure, agent.cancel, permission.respond) and agent.stdin / agent.resize also unscoped — agent.stdin writes arbitrary input into another user's agent process. Those touch live session lifecycle and need their own change; recorded in AGENTS.md so the gap is not mistaken for covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): scope every session-keyed WS action, plus a gateway backstop Completes the sweep. Dispatched WS actions were left to authorize themselves at the service layer, and a dozen never did — a caller with a guessed session ID could stop, delete, rename, relaunch or reset another user's live agent session, cancel their in-flight turn, answer their permission prompts, or write arbitrary bytes into their agent process. Two layers, deliberately both: 1. Gateway backstop. Client.authorizeAction checks any dispatched payload carrying task_id or session_id against the client's identity before dispatch. Per-method opt-in across ~74 actions in four packages had been missed repeatedly; this makes a newly added action scoped by default. Verified safe to apply generically: session_id means a task session everywhere, and user-shell/terminal actions key off task_id / task_environment_id, so nothing is misread. 2. Explicit guards, for defense in depth and for non-WS callers: - orchestrator: StopSession, DeleteSession, SetPrimarySession, RenameSession, ResetAgentContext, CancelAgent, RespondToPermission, SetSessionPlanModeByID, RecoverSession, LaunchSession, EnsureSession, CancelTransientRetry. Added SetTaskAccessChecker for the task-keyed ones. LaunchSession guards every intent at the single funnel; SessionID is empty on create, so the task check carries that case. - agent.stdin / agent.resize now call lifecycleMgr.CheckSessionAccess, the helper already documented for bare-lookup handlers. Guards go first in each method, before any repo/executor use. TestSessionKeyedEntryPointsGuardBeforeDependencies runs all twelve with nil dependencies and fails on a panic if one is placed too late. Denials are indistinguishable from a missing resource: WS returns not-found, CancelTransientRetry reports "nothing cancelled", and GetTaskSessionStatus reuses its "session not found" shape. Unwired checkers stay permissive throughout, so identity-less internal callers (schedulers, event bus, workflow engine) and pre-auth single-user instances are unaffected — pinned per entry point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): scope environment-keyed user-shell actions Third sweep finding, in surface the previous commit's own comment claimed was covered. The user-shell actions key off task_environment_id and treat task_id as optional, so a task_id-only backstop missed them: user_shell.stop with an empty task_id and a foreign task_environment_id fell through to the legacy runner path and tore down another user's terminal PTY. That path never reaches GetOrEnsureExecutionForEnvironment, where the environment check normally runs, so neither layer caught it. - Gateway backstop now also reads task_environment_id, gated by a new AuthPolicy.ActionEnvironment hook wired to taskSvc.AuthorizeEnvironmentAccess. This covers all three environment-keyed actions generically, not just the reported one. - wsUserShellStop guards explicitly via a new lifecycleMgr.CheckEnvironmentAccess, mirroring CheckSessionAccess, so the fallback is safe independent of the gateway. Corrected the comment that wrongly asserted task_environment_id was already covered, and recorded in AGENTS.md both the exact field names the backstop reads and why a permissive type on one of them would silently opt out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): authorize both IDs on task+session entry points Review finding (P1). Entry points taking both a task and a session ID only checked the session, so a caller could satisfy that check with one of their own sessions while pointing taskID at another user's task: - CheckSessionPR then used the unchecked taskID for GetTaskPR, resolveTaskRepo, resolvePrimaryTaskRepositoryID and EnsurePRWatch — cross-workspace PR disclosure plus a PR-watch write. - CancelTransientRetry handed it to handleRecoverableFailure, which writes against that task. Both now use a new authorizeTaskSessionPair, which authorizes each ID and additionally requires the pair to be consistent, so the two arguments cannot describe different tasks. GetTaskSessionStatus already validated the pair after loading the session and is unchanged. The consistency half is skipped when the session cannot be loaded: both IDs are authorized by then, so a mismatch is a caller bug rather than a cross-user leak, and failing there would change behavior for identity-less internal callers. Recorded the rule in AGENTS.md — a two-ID entry point must never use authorizeSession alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): scope AttachWorkspaceSources Found while rebasing onto main. POST /tasks/:id/workspace-sources and the add_workspace_sources_kandev MCP tool landed on main during this PR's review with no ownership check, so a caller could attach repositories and folders to another user's task and have them materialized into that task's workspace. Same class as the rest of this PR, so fixed here rather than left for a follow-up. The WS dispatch backstop added earlier already covered the browser path (the payload carries task_id); this closes the HTTP route and the MCP tool, which do not go through it. ErrTaskNotFound already maps to 404 in workspaceSourceHTTPStatus, so the denial surfaces correctly without touching the handler. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): map denied shell-output reads to 404 Review finding. GetMessage became per-user scoped earlier in this PR, so a denial now arrives as ErrTaskNotFound. httpGetShellOutput only mapped sql.ErrNoRows to 404, sending everything else to the 500 branch — so a foreign message answered 500 while a missing one answered 404, which is an existence signal and inconsistent with the 404-everywhere rule the other session routes follow. Kept the sql.ErrNoRows branch explicit rather than replacing it outright: isNotFound classifies typed sentinels and the "not found" substring, and "sql: no rows in result set" matches neither, so routing that case through handleNotFound alone regressed the missing-message response to 500 (caught by TestHTTPShellOutputSnapshot). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(auth): make the shell-output scoping test actually exercise the guard Both reviewers caught that TestHTTPGetShellOutputDeniesForeignMessageWith404 was vacuous. httpGetShellOutput has a second 404 gate after the service call — `if !ok || message.TaskSessionID != c.Param("id")` — and ExtractShellExecOutput returns ok=false for nil metadata. The fixture had none, so the route answered 404 through that gate whether or not scoping denied the read. Confirmed by mutation rather than inspection: with the authorization removed from GetMessage the test still passed. With valid shell-output metadata in the fixture it returns 200 and the victim's stdout, so the test fails as intended; restoring the guard makes it pass again. Also mutation-tested the sibling denial tests, since this is a defect class rather than a one-off: - TestHTTPGetTaskSessionDeniesForeignSessionWith404 — sound, returns 200 with the session DTO when the guard is removed. - TestProcessRoutesDenyForeignSession — catches removal, though via the documented nil-lifecycleMgr panic rather than a status assertion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(auth): add an in-test witness to the shell-output scoping test Review suggestion. The fixture fix made the test exercise the guard, but nothing in the test proved the 404 came from authorization rather than the route's post-service `!ok` gate — which is exactly how it went vacuous in the first place. The evidence lived in a commit message describing a manual mutation run, not in the test. Now asserts both directions, matching the approve-route test: the non-owner gets 404, and the owner does NOT. If the assertion ever stops proving authorization, the owner case fails and says so. Re-verified by mutation: removing the guard from GetMessage fails the test with 200 and the victim's stdout; restoring it passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a7005e5d2f |
chore: revert agent subscription usage in settings and chat tooltip (#1940)
* chore: revert agent subscription usage in settings and chat tooltip
Reverts #1697 (
|
||
|
|
cc340ae5f2 | fix(build): make Makefile $(shell) probes work under native Windows cmd (#1953) | ||
|
|
488d76edf4 | fix: extend ACP session creation timeout (#1949) | ||
|
|
45ba6691c9 | fix: isolate task state when switching workspaces (#1951) | ||
|
|
75abf779ed | feat(github): support per-workspace GitHub App registrations (#1810) | ||
|
|
bc218dfbbe |
feat: add workspace sources from files panel (#1900)
* feat: add workspace sources from files panel * fix: preserve worktree identity during recovery * feat: unify workspace source actions * fix: validate recovered workspace repositories * fix: harden workspace source filesystem boundaries * test: split workspace repository chip coverage * fix: restrict repository rescan recovery paths * fix: stabilize workspace source CI checks * test: canonicalize workspace source paths * test: align workspace source container paths * fix: populate direct workspace repository names * test: preserve git fixture rewrites across restarts * test: isolate backend git fixture config * fix: resume remote workspace executions before creation * fix: preserve ssh agent identity during resume * fix: preserve SSH workspace across backend restart * fix: simplify workspace actions trigger * fix: refine add workspace repositories UX * docs: narrow workspace source UX updates * fix: avoid phantom turn on session resume * test(web): normalize workspace source request headers * feat(web): streamline workspace source picker * docs: remove trailing spaces from workspace source plan * fix: prevent resume status from creating phantom turns * fix(web): stabilize workspace source attachment * refactor(web): extract workspace source store contract * fix: avoid phantom turns on idle agent resume * fix: address backend static analysis findings * fix: preserve agentctl readiness status value * fix: preserve resume messages without active turns * fix: resume agents from promoted workspace root * chore: merge origin/main into feature/add-new-repo-folder-d97 * feat(web): explain workspace source consequences * test(websocket): count session activity subscription |
||
|
|
dc38aaf0d0 | feat: update ACP compatibility and subagent handling (#1880) | ||
|
|
3d7b43e37b | chore: improve harness workflow guidance (#1941) | ||
|
|
a4d53bdf42 | feat(web): remember task listing display preferences (#1944) | ||
|
|
dba2057590 | feat: accept input while a session's foreground turn waits on background work (#1668) | ||
|
|
2d5fedd5d0 | fix(release): preflight signing configuration before merge (#1948) | ||
|
|
cbc3b97b29 | release: 0.82.0 v0.82.0 | ||
|
|
1c151ee3c2 | fix(web): polish clarification custom answer input (#1942) | ||
|
|
85b44ced45 | fix: restore managed native service updates (#1943) (#1945) | ||
|
|
3c09b1590a | fix: make remote repository entry reliable (#1936) | ||
|
|
715a2033b2 | fix: route update alerts through notification providers (#1938) | ||
|
|
103878047c |
fix(auth): scope in-session agent MCP calls to the task owner (#1937)
* fix(auth): scope in-session agent MCP calls to the task owner The MCP tools an agent gets automatically inside its own session are relayed over the agent's WebSocket stream, which carries no credential. The backend dispatched them on the raw stream context, so the task service saw no identity — its signal for an internal caller (event bus, pollers, office schedulers) — and served the request unscoped. An agent that supplied another user's task_id or workflow_id was given their data, violating the rule that knowing an ID must not grant access. Resolve the owning user from the stream's own AgentExecution (task -> workspace -> owner) and attach that real stored identity before dispatch, so the existing authorize* checks apply exactly as they do on the PAT-authenticated /mcp endpoint. The owning task never comes from the agent-supplied payload: honoring a payload session_id would let an agent name another user's session and inherit their identity. Tool handlers are unchanged. Auth disabled stays unscoped, unclaimed pre-auth workspaces stay visible to everyone, and a lookup failure denies the dispatch rather than falling back to full access. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): never leave in-session MCP dispatch unscoped Review follow-up. Three paths still returned an identity-free context under enforced auth, and "no identity" is exactly what the task service reads as an internal caller and serves unscoped: - Unowned workspace. CreateWorkspace only stamps an owner for scoped callers, so internal callers keep producing owner_id='' rows after setup. Those streams got full cross-user access. Scope them to a sentinel user ID no account can hold: the service's "empty owner_id or my own ID" rule then limits them to the unowned rows that are public by the compatibility contract, and nothing else. - Task with no workspace. Same treatment, same reasoning. - Missing task or workspace row. A lookup that reports not-found as (nil, nil) fell through to unscoped; treat it as a resolution failure. Also deny, rather than fabricate a member identity, when the owner's account is gone or disabled. Disabling a user revokes their sessions and PATs, so a still-running agent session was the one remaining way into their workspace. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
919a39e49a |
feat(auth): opt-in authentication & multi-user segregation (#1930)
* feat(auth): add auth core — identities, sessions, PATs, invites, mode state machine Phase 1 of opt-in authentication + multi-user segregation: - internal/auth: argon2id password hashing (PHC), opaque session/PAT tokens (SHA-256 at rest), login rate limiter, auth-mode state machine (disabled/setup/enabled), setup-wizard promotion of the pre-auth default-user row into the admin account, invites, admin user management with last-admin guard. - internal/auth/store: auth_identities (OIDC-ready), auth_sessions, auth_api_tokens, auth_invites tables (SQLite+Postgres dialects). - internal/auth/authn: request-identity plumbing (gin + plain contexts), RequireAdmin middleware. - users table: display_name/role/status columns via new MigrateLogger wiring; AccountRepository surface (consumer-scoped, keeps existing Repository fakes untouched). - config: AuthConfig.Required/SessionTTLHours/CookieName (KANDEV_AUTH_REQUIRED), FeaturesConfig.Auth; profiles.yaml + runtime flag registry entry for KANDEV_FEATURES_AUTH. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(auth): enforce opt-in auth over HTTP — middleware, auth API, boot payload Phase 2 of opt-in authentication + multi-user segregation: - internal/auth/httpmw: global gin middleware after CORS. Disabled mode injects the synthetic single-user admin identity (behavior unchanged); enabled mode resolves session cookie then PAT bearer, with an explicit allowlist (health, SPA bootstrap reads, credential endpoints, self-authenticating webhooks), deferral for WS/proxy upgrades and office agent JWTs, and SPA shell availability for the login page. - internal/auth/httpapi: full auth surface — setup wizard, login/logout, me, password change, sessions, PATs, invites (mint/preview/accept), auth settings (enable/disable), admin user management. - Boot payload: initialState.auth block always present; unauthenticated visitors on auth-enabled instances get features+auth only (no data). - External MCP: PAT required once auth is enabled (open while disabled). - Admin guards on destructive system routes and runtime-flag overrides. - Startup warning when bound non-loopback with auth disabled (ServerConfig.NonLoopbackBinds fail-closed nudge). - Wiring: auth store in Repositories, auth service in Services, ClaimUnownedWorkspaces backfill on the task repository. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(auth): per-user workspace scoping across HTTP services Phase 3 of opt-in authentication + multi-user segregation: - Task service: workspaces, workflows, repositories, and tasks are scoped to the request identity (owner-only visibility; pre-auth unowned rows stay shared). Denials use *NotFound sentinels - no existence leak. Internal callers and the disabled-mode synthetic identity remain unscoped, preserving behavior exactly when auth is off. - CreateWorkspace stamps the authenticated caller as owner. - Session chokepoint: lifecycle Manager.GetOrEnsureExecution runs a settable session-access check covering shell/files/ports/vscode/LSP. - Secrets: per-user user_id column, scoped CRUD/list, ClaimUnowned wired into the setup-wizard backfills. - Boot payload inherits scoping automatically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(auth): scope WebSocket gateway to authenticated users Phase 4 of opt-in authentication + multi-user segregation: - Connection auth: /ws, /terminal, /lsp, /vscode, /port-proxy routes are guarded when auth is enabled - session cookie (same-origin upgrade), resolved identity from the HTTP middleware, or ?token=<PAT> for headerless programmatic clients; JSON 401 before any upgrade. - Client identity: every WS client carries its authn.Identity; dispatched RPC actions (workspace.list, task CRUD, ...) run under a context carrying it, so WS RPC gets the same per-user scoping as HTTP. - Subscription checks: task/session subscribe+focus verify workspace visibility via the task service; user topic subscriptions are pinned to the client's own user. - Broadcast routing: new Hub.BroadcastToWorkspace routes workspace-scoped events (task/workflow/repo/office/session-state) to the owner's clients only, with safe fallbacks (unowned/unresolvable -> broadcast) so events never vanish; instance-wide events (executors, environments, agent profiles, system jobs) stay global by design. - Replaced the long-standing discarded-token TODO in HandleConnection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(auth): add feature spec and backend architecture notes for opt-in auth - docs/specs/auth/spec.md: product behavior (opt-in, setup wizard, per-user workspaces, sessions/PATs, invites, public surface, v1 limits) - apps/backend/AGENTS.md: internal/auth package layout + scoping rules for new service entry points and WS broadcast call sites Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(auth): public documentation for opt-in authentication - New docs/public/authentication.md: enabling, users & invites, PATs, public endpoints, limitations, disabling - run-as-a-service.md: network-security note now points at the real authentication boundary instead of reverse-proxy-only guidance - meta.json: nav entry under Configure Kandev Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(auth): ADR for opt-in authentication architecture Records the load-bearing choices: mode state machine + synthetic identity in disabled mode, opaque DB-backed credentials over JWTs, default-user promotion at setup, service-layer scoping with no-admin-bypass, and the explicit public allowlist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): auth UI — login, setup wizard, invites, users & account settings Adds the frontend half of opt-in authentication: a pre-auth gate in main.tsx that renders the setup wizard or login/invite pages instead of the app shell whenever the boot payload's auth block says so, a new `auth` store slice + feature flag mirroring the backend's StatePayload, credentialed fetch (`credentials: "include"`) with a 401 handler that clears the session and redirects to /login, and a full auth-api.ts client for the /api/v1/auth and /api/v1/users routes. Settings additions: a System > Users page (list/create/disable/promote users, invite-link dialog) and System > Authentication page (enable/ disable with confirm + env-locked banner), gated on the `auth` feature flag and admin role. A new Account sidebar group (Profile & Password, API Tokens) is shown to signed-in users once auth is enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(auth): E2E lifecycle suite + fix workspace event WS leak it caught New dedicated Playwright auth project (backend restarts with KANDEV_AUTH_REQUIRED=true, isolated from the tokenless default suite): setup-mode boot gating, admin setup wizard, login/logout + bad credentials, PATs, admin-surface authorization, anonymous WS rejection, UI login flow, and the acceptance test - two-user segregation across HTTP, boot payload, and WebSocket. The segregation spec immediately caught a real leak: workspace.* event payloads carry their ID as 'id' (the payload IS the workspace DTO), not 'workspace_id', so BroadcastToWorkspace fell back to broadcast-to-all for workspace create/update/delete. Fixed with action-aware extraction plus a unit regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): close cross-user access gaps found in security audit Two independent security audits of the auth branch found several authorization gaps where session/workspace-scoped surfaces were not wired to the new per-user chokepoint. All fixed: - IDOR on reverse-proxy/terminal routes (vscode, port-proxy, terminal): these resolve executions via GetExecutionBySessionID / GetOrEnsureExecutionForEnvironment / EnsurePassthroughExecution, which skipped the session-access check that only GetOrEnsureExecution ran. Added the check inside the env/passthrough resolvers (before the cache short-circuit) and an explicit CheckSessionAccess gate in the two bare-lookup proxy handlers. An authenticated non-owner can no longer reach another user's IDE, forwarded ports, or shell. - Cross-user agent-conversation read: ListMessages / ListMessagesPaginated / SearchMessages now authorize the session. - Cross-user repository-script read/inject: script CRUD now authorizes the parent repository's workspace. - Office HTTP surface: a group middleware enforces workspace ownership on :wsId routes for real users (agent-JWT callers keep their claim scoping). - Login rate-limit bypass: SetTrustedProxies(nil) so a spoofed X-Forwarded-For can't rotate the limiter key on a directly-reachable backend. - Invite double-provisioning race: MarkInviteUsed is now the atomic serialization point (consumed before user creation). - Setup wizard: serialized with a mutex so concurrent first-visitors can't corrupt the promoted admin profile. - Log sanitizer: explicit kandev_pat_ redaction rule. Regression tests added for message/script scoping and the execution-access-before-cache invariant. Auth E2E (incl. two-user segregation) still green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): scope repository active-session-count endpoint GET /api/v1/repositories/:id/active-session-count went through CountActiveSessionsByRepository, which was not identity-scoped - a cross-user info leak of another owner's active session counts. Added the same authorizeRepositoryID gate the script routes use. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): add json tags to auth store models — omit digests, fix token UI The auth store structs (APIToken, Invite, LoginIdentity, Session) had db: tags but no json: tags, so the handlers that serialize them directly sent Go-cased keys (CreatedAt, Name) AND leaked the SHA-256/password digests to clients. Two consequences: - The API-tokens UI showed 'Invalid Date' and an empty name (frontend reads created_at/name). - token_sha256 / password_hash were serialized in list responses. Added json tags with snake_case keys the frontend reads and json:"-" on every credential digest. Regression test asserts digests never serialize and the snake_case timestamp/name keys are present. Also adds the auth-screenshots capture spec (artifacts gitignored). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): move auth enable into feature toggles; show current user in sidebar Remove the standalone Authentication settings page and its dedicated settings/get-auth-settings API surface now that enabling auth lives in the Feature Toggles system, and drop env_required now that /auth/me no longer returns it. Add a current-user chip with a logout action to the sidebar footer, shown only in enabled auth mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(auth): make features.auth the single on/off control Per review feedback: authentication configuration now lives entirely in the existing Feature Toggles system rather than a separate Authentication settings section. - The auth mode is derived from the features.auth runtime flag (KANDEV_FEATURES_AUTH): off -> disabled, on+no-admin -> setup, on+admin -> enabled. Removed the parallel auth.mode system setting, the SetEnabled service method, GET/PATCH /api/v1/auth/settings, the AuthConfig.Required field, KANDEV_AUTH_REQUIRED, and the auth service's Settings-store dependency and EnvRequired/env_required. - The features.auth registry entry is re-described as the actual enable switch (high-risk, restart-required) and defaults off in every profile (dev was previously true — that only revealed UI before; now it would enforce, so it must be an explicit opt-in). - Setup no longer persists a mode; completing the wizard creates the admin and the mode auto-derives to enabled. - Startup 'exposed without auth' warning + all docs (public guide, run-as-a-service, spec, ADR, AGENTS.md) point at the feature toggle. - E2E: auth specs use KANDEV_FEATURES_AUTH; screenshot spec captures the Feature Toggles page instead of the removed Authentication page; the member-restriction assertion targets an admin invite route. - Tests updated across service/httpmw/httpapi/profiles for the derived mode; full suite green (only pre-existing sandbox-FS failures remain). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): complete cross-user isolation — integrations, automation, plan/walkthrough, session reads A follow-up isolation audit found that per-user scoping had been wired into the task service, secrets, office, and the WS gateway — but several workspace/task/session-scoped surfaces still resolved a caller-supplied ID with no ownership gate. Closed all of them; cross-user access now returns 404 across the board. - Integrations (jira/linear/sentry/slack/azure-devops/gitlab/github/ workflow-sync): a global middleware authorizes the workspace_id (query, or gitlab's /workspaces/:id/ path) on those route groups. Previously a member who knew another user's workspace ID could read/overwrite their integration connection config and watches. - Automation: workspace-authorizer seam gates get/list/create/update/delete/ enable/disable, triggers, run history, and — critically — the webhook secret. Was fully cross-tenant over WS before. - Task plans (PlanService) and walkthroughs (WalkthroughService): task- authorizer seam on every read/write. These bypassed the task service. - Session reads that hit the repo directly — session turns, task context, git snapshots, file reviews — now call AuthorizeSession/TaskAccess. Internal callers (pollers, schedulers, MCP) carry no identity and stay unscoped; disabled mode is unaffected. Regression tests added (plan/walkthrough scoping, automation authorizer matrix, integration path extraction) and the two-user E2E now asserts integration routes are shielded. Docs (spec, ADR, public guide) updated to state what is isolated and that filesystem/agent-credential isolation remains an OS-level limitation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): current-user chip uses border hover, not accent fill The sidebar current-user chip used hover:bg-accent (a purpleish fill). Switch to the border-hover idiom used elsewhere (border border-transparent -> hover:border-border) with the neutral hover:bg-muted/50 the adjacent workspace picker uses, so the hover reads as a subtle outline instead of a colored background. Screenshot spec now captures the chip hover state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): right-align current-user chip in the sidebar footer Push the user chip to the far right of the expanded footer (ml-auto) so it sits opposite the left-aligned icon buttons; collapsed mode keeps the centered vertical stack unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): reject synthetic identity on management routes and scope office run broadcasts Address code-review findings on the opt-in authentication PR: - P1: while auth is disabled the middleware injects a synthetic admin (RoleAdmin), and RequireAdmin alone let it through — so anyone hitting a not-yet-enabled instance could mint a PAT or plant an admin that survives enablement and hijacks first-run setup. Add authn.RequireRealIdentity and guard the credential/session/user-management routes with it (public bootstrap routes stay reachable). Responds 404 while disabled. - P1: office run events carry task_id but no workspace_id, so they fell back to a global broadcast and leaked across users. Resolve the workspace from the task and route via the new Hub.BroadcastToWorkspaceOrDrop, which fails closed (drops rather than global-broadcasts) when the workspace is unknown and auth is enforced. - nit: type createAuthSlice as a single-arg creator and call it with just set. Also register the authentication docs page and its settings routes in docs/public/coverage.json so the public-docs validator passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0ae798d24f |
fix(installer): confine tarball extraction with os.Root (#1934)
* fix(installer): confine tarball extraction with os.Root Lexical containment of tar entry names is not enough on its own: an archive can ship a symlink whose target passes the check (`a` -> `.` resolves to destDir itself) and then write through it with a later entry (`a/x` -> `..`) that really lands outside destDir. Confirmed by extracting a crafted tarball — it wrote a file into destDir's parent. Anchor extraction at an os.Root opened on destDir so the OS-level path walk refuses to follow a link out of the tree, and keep the lexical checks for clear up-front errors. Adds a regression test for the two-hop symlink chain. Fixes CodeQL alert #19 (go/zipslip). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(installer): reject archive symlinks under symlinked parents Root.Symlink follows symlinked parent components, so an entry named "a/x" is created at "x" when "a" is a symlink to ".". The link still lands inside destDir, but one level shallower than its archive path — which changes what a relative target means. "a/x" -> ".." passes a check computed against parent "a" and then resolves to destDir's parent from its real home, leaving an escaping symlink in the install tree. Nothing can be written through it during extraction (os.Root refuses to follow it), but it persists for any later consumer that walks the tree with ordinary os calls. Require every parent component of a symlink entry to be a real directory, so an entry's archive path and its on-disk location agree — the assumption the existing target check already depended on. Reported by the Codex reviewer on #1934. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
767cc89e6c |
fix(backend): move Docker client to the maintained moby client module (#1935)
github.com/docker/docker is frozen at v28.5.2 — moby publishes v29+ under
github.com/moby/moby/v2, with the API client split out into the standalone
github.com/moby/moby/client and github.com/moby/moby/api modules. That leaves
five Dependabot alerts on the old module path with no reachable fix version:
CVE-2026-42306 high docker cp bind mount redirection <= 28.5.2
CVE-2026-41567 high PUT /containers/{id}/archive <= 28.5.2
CVE-2026-41568 medium docker cp symlink swap <= 28.5.2
CVE-2026-34040 high AuthZ plugin bypass < 29.3.1
CVE-2026-33997 medium plugin privilege off-by-one < 29.3.1
All five are daemon-side defects, and we only ever imported the client. Moving
to the split client module drops the daemon source out of the module graph
entirely, so these alerts resolve and future daemon advisories will not flag us
at all. It also trims six transitive dependencies.
The new client takes an options struct per call and returns a result struct, so
this is a mechanical rewrite of the wrapper. Behavioural notes:
- Ports moved from docker/go-connections/nat to moby/api/types/network, where
Port is opaque and PortBinding.HostIP is a netip.Addr. buildDockerPortBindings
now returns an error for out-of-range ports and unparseable host IPs instead
of handing the daemon a malformed request. An empty HostIP still maps to the
zero address, preserving "publish on all interfaces".
- DiskUsage needs Verbose to get per-image and per-container records; the
aggregate LayersSize is now Images.TotalSize.
- BuildCachePrune no longer takes KeepStorage — the client derives the legacy
keep-storage parameter from ReservedSpace for daemons on API <= 1.47.
- ImagesPrune is now ImagePrune.
- Container state and health are typed string enums, and InspectResponse.Config
and .State are pointers, so inspect mapping guards for nil.
Also corrects the RUSTSEC-2024-0429 note in the cargo-audit ignore list. It
claimed the advisory resolves once Tauri moves to webkit2gtk-4.1, but Tauri v2
is already on those bindings and still resolves glib to 0.18.5; the real
blocker is that gtk3-rs is archived. Backport submitted upstream.
Co-authored-by: Kandev Agent <agent@kandev.dev>
|
||
|
|
f85b648d97 |
feat: notify user when a kandev update is detected (#1924)
* feat: notify user when a kandev update is detected Adds a background-poller-driven update notification with a saved preference for delivery channel (desktop / in-app banner / both) and an enable/disable switch on the Updates settings page. Backend: - internal/system/updates: NotifySettings + NotifyStore persist the enable/channel preference on the existing install-wide settings table; notifyIfNewUpdate broadcasts a WS event the first time a newer release is observed, deduped durably via kandev_meta so a restart or repeat poll doesn't re-announce the same version. - New GET/PUT /api/v1/system/updates/notification-settings endpoints. - pkg/websocket: new system.update_available action. Desktop (Tauri): - native_notifications.rs: task_id is now optional (Option<String>, required only for session-scoped events) and the event allowlist accepts system.update_available: alongside the existing session events, so the update notification can use the native bridge. Frontend: - New UpdateNotificationsCard on the Updates settings page (Switch + channel Select), wired through the shared settings-save-provider floating Save/Discard action. - useUpdateAvailableToast bridges the WS event to an in-app toast and/or the native/browser desktop notification, per the saved channel, deduped per version. * docs: document the update notification channel setting * fix: hydrate update notification settings and broadcast delivery ack - Only mark a release notification as delivered when the broadcaster reports at least one connected client, closing a race where an update could be silently dropped before the first client connects. - Hydrate updateNotificationSettings through the settings boot payload (loadSettingsInitialState -> hydrateState) instead of a component- level fetch, per the boot-payload -> store -> component data flow. Widen the hydration types (HydrationState) so callers can hydrate a partial system slice without fabricating the other system fields. UpdateNotificationsCard now only reads/writes the subscribed store state. - Fix module-scope settings-fetch cache leaking across tests in use-update-available-toast.test.ts so the fetch-failure test actually exercises the catch/fallback path. - Add API-client contract tests for fetchUpdateNotificationSettings and saveUpdateNotificationSettings (GET cache policy, PUT method and body). * test: cover delayed hydration and boot-payload mapping for update notifications |
||
|
|
2d950a72fb | fix(web): deduplicate model options and restore reasoning scroll (#1933) | ||
|
|
1e1b6c03a6 | fix(web): improve queue scrolling and diff comment feedback (#1932) | ||
|
|
cfea8fe9cb | fix: scope walkthrough overlays to their task (#1928) | ||
|
|
a4b3677d8d | feat: show full timestamp as tooltip on chat message relative time (#1925) | ||
|
|
a0ee801b82 |
fix(deps): resolve low and medium Dependabot alerts (#1926)
* fix(deps): resolve low and medium Dependabot alerts Bump vulnerable transitive and direct dependencies across npm, Go, and Rust to clear all fixable low/medium severity Dependabot advisories. npm (pnpm.overrides, scoped by major; hono pinned in @kandev/theme to force the shadcn dev-CLI peer): - ajv@6 6.15.0, @babel/core 7.29.x, body-parser 2.3.0, brace-expansion 1.1.16/2.1.2, dompurify 3.4.12, hono 4.12.x, @hono/node-server 2.0.x, js-yaml 4.3.0, lodash(-es) 4.18.x, markdown-it 14.3.x, mermaid 11.16.x, path-to-regexp@8 8.4.x, picomatch@2 2.3.2, postcss 8.5.x, protobufjs 7.6.5, qs 6.15.x, uuid@11 11.1.1, ws 8.21.x Go: golang.org/x/net 0.55.0, github.com/jackc/pgx/v5 5.9.2 Rust (apps/desktop): serde_with 3.21.0, tauri family 2.11.x (rand 0.7.3 dropped by the tauri cascade) Highs are handled by a separate effort; docker/grpc left untouched. Remaining unresolved mediums: glib (blocked by tauri's gtk ^0.18 constraint) and docker cp advisories (fix requires the docker major bump owned by the highs effort / no patched version yet). * fix(desktop): align @tauri-apps/cli with the 2.11 Rust crate bump The Rust `tauri` crate moved to 2.11.5, but `@tauri-apps/cli` (which runs `tauri build` for desktop packaging) was still pinned to 2.8.4. Tauri expects the CLI and crate on the same minor line, so bump the CLI to 2.11.4 to keep packaging tooling in sync with the Cargo crates. --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
41a806b9dd |
fix(deps): resolve open high-severity Dependabot advisories (#1919)
* fix(deps): resolve open high-severity Dependabot advisories Patch all fixable open high-severity Dependabot alerts. Backend (go.mod): - google.golang.org/grpc 1.82.0 -> 1.82.1 (GHSA-hrxh-6v49-42gf) Frontend (pnpm overrides in apps/package.json) — all transitive: - postcss -> ^8.5.12 (GHSA-6g55-p6wh-862q) - fast-uri -> ^3.1.4 (GHSA-v2hh-gcrm-f6hx / -v39h / -q3j6 / -4c8g) - sharp -> ^0.35.0 (GHSA-f88m-g3jw-g9cj) - linkify-it -> ^5.0.2 (GHSA-v245-v573-v5vm / -22p9) - js-yaml@4 -> ^4.3.0 (GHSA-52cp-r559-cp3m) - adm-zip -> ^0.6.0 (GHSA-xcpc-8h2w-3j85) - hono -> ^4.12.25 (GHSA-88fw / -q5qw / -3vhc / -f67f) - @hono/node-server -> ^1.19.10 (GHSA-wc8c-qw6v-h7f6) - ws -> ^8.21.0 (GHSA-96hv-2xvq-fx4p) - lodash / lodash-es -> ^4.18.0 (GHSA-r5fr-rjxr-66jc) - flatted -> ^3.4.2 (GHSA-rf6f / -25h7) - @isaacs/brace-expansion -> ^5.0.1 (GHSA-7h2j-956f-4vf2) - @modelcontextprotocol/sdk -> ^1.26.0 (GHSA-345p-7cg4-v4c7) - brace-expansion@1 -> ^1.1.16, brace-expansion@2 -> ^2.1.2 (GHSA-3jxr) - path-to-regexp@8 -> ^8.4.0 (GHSA-j3q9-mxjg-w52f) - picomatch@2 -> ^2.3.2 (GHSA-c2c7-rcm5-vvqj) - minimatch@3 -> ^3.1.4, @9 -> ^9.0.7, @10 -> ^10.2.3 (GHSA-7r86 / -23c5 / -3ppc) Major-scoped override keys preserve each in-tree major line rather than force a single version across incompatible consumers. Not fixable here: github.com/docker/docker alerts (GHSA-x744-4wpc-v9h2, GHSA-x86f-5xw2-fm2r, GHSA-rg2x-37c3-w2rh). No patched module version is published — latest tag is v28.5.2+incompatible (the current pin) and two of the three advisories have no fix yet. Verified: backend build + vet clean; CLI 280 tests pass; web typecheck, vite build, lint, and 6388 unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(web): regenerate license manifest after dependency bumps --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e2c0d74d51 | fix(web): preserve chat focus when restoring task layout (#1923) | ||
|
|
65c303d37f | fix: distinguish session notification events (#1918) | ||
|
|
2f6be0c777 | fix(agents): harden ACP launcher configuration (#1917) | ||
|
|
7e4f2bb1e5 | feat(github): re-request dismissed reviews (#1921) | ||
|
|
b56bca5a4d |
feat: move subtask action to task context menu (#1920)
* feat: move subtask action to task context menu * fix: resolve subtask defaults from selected parent * fix: handle repository-less subtask parents |
||
|
|
b85ce080d5 |
fix(plugins): make plugin reload idempotent so re-boot never duplicates slots (#1914)
Installed plugins rendered duplicate UI slot widgets (e.g. the chat-input-actions icon twice) because the append-only slot registry could run a plugin's initialize() again while its prior registrations were still live. - Make loadPlugin idempotent: revoke a plugin's prior registrations before re-running initialize(). - Fence overlapping/concurrent loads by a per-plugin generation so the newest-initiated load wins and a superseded (older) load can't revoke the successor or append a stale registration. - Wrap the scoped registry reflectively so every register* method (incl. registerKeybinding) is fenced, never dropped. Tests: registry-count regression, real-DOM PluginSlot render reproduction, two overlapping-load regressions, and a registerKeybinding-forwarding regression. |
||
|
|
49afee96b3 |
fix(web): keep composer usable during clarifications (#1916)
* fix(web): keep composer usable during clarifications * fix(web): guard clarification shortcuts in editor |
||
|
|
5db3e48262 |
feat: add opt-in plugin auto-updater (#1903)
* feat: add opt-in plugin auto-updater Automatically update installed plugins in the background, off by default and opt-in. Builds on the existing verified install pipeline — an auto-update is just an automatic reinstall of a newer catalog package_url. Two-tier toggle on Settings > Plugins: an instance-wide default (SQLite plugin_settings) plus a per-plugin override (tri-state auto_update on the plugin record, carried forward on upgrade). The AutoUpdatePoller (~90-min cadence + a boot sweep) upgrades only plugins that are currently active, opted in, and report update_available; disabled/errored plugins stay on their installed version. New endpoints: GET/PUT /api/plugins/settings and PUT /api/plugins/:id/auto-update. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): recheck auto-update eligibility at install and fix test mock Address PR #1903 review feedback: - Re-check each plugin's current status + effective opt-in immediately before InstallFromURL (eligibleForAutoUpdate), closing the TOCTOU window between the candidate snapshot / catalog fetch and the install so a plugin disabled or opted out mid-sweep is never reinstalled and reactivated. - Correct the marketplace spec to describe the real failure behavior (shared with manual updates) instead of promising a version rollback the install pipeline does not perform. - Fix the frontend CI failure: the plugins page test mocked the whole plugins-api module without the new settings/auto-update functions, so useAutoUpdateSettings threw at mount. - Add an aria-label to the per-plugin auto-update Switch and Reset control. - Add a use-auto-update-settings hook test (load, failure, optimistic setDefault, rollback) and an eligibleForAutoUpdate unit test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ed33c7ea3f |
fix(deps): patch critical Dependabot advisories (#1915)
* fix(deps): patch critical Dependabot advisories Resolve all open critical-severity Dependabot alerts: - golang.org/x/crypto 0.51.0 -> 0.52.0 (CVE-2026-39830/39831/39832/ 39833/39834/42508/46595, GHSA ssh agent/key-constraint bypass, DoS) - vitest ^1.6.0 -> ^4.1.10 in apps/cli (CVE-2026-47429; aligns with apps/web, dedupes the lockfile to a single vitest 4.1.10) - tar ^7.5.11 -> ^7.5.19 in apps/cli (CVE-2026-59873 parse DoS; resolves to 7.5.21) Backend builds and vets clean; all 280 apps/cli tests pass on vitest 4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): bump esbuild to a Vite-8-supported range The vitest 4 bump pulls vite@8, whose esbuild peer is ^0.27||^0.28, but the CLI still pinned esbuild ^0.24.0 — pnpm resolved an unsupported vite/esbuild pair (esbuild@0.24.2) for the CLI test stack. Bump to ^0.27.0 so vite@8 resolves against esbuild@0.27.2 and the peer warning clears. CLI tests still pass (280/280). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6fedac6c64 | fix(build): support make dev on native Windows (#1886) |