Commit Graph

2062 Commits

Author SHA1 Message Date
Carlos Florêncio 3c09b1590a fix: make remote repository entry reliable (#1936) 2026-07-25 11:11:03 +01:00
Carlos Florêncio 715a2033b2 fix: route update alerts through notification providers (#1938) 2026-07-25 11:07:06 +01:00
João Salavisa 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>
2026-07-25 09:56:47 +01:00
João Salavisa 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>
2026-07-25 00:10:22 +01:00
João Salavisa 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>
2026-07-24 23:57:52 +01:00
João Salavisa 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>
2026-07-24 23:33:41 +01:00
Clément Fiere 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
2026-07-24 22:40:24 +01:00
Carlos Florêncio 2d950a72fb fix(web): deduplicate model options and restore reasoning scroll (#1933) 2026-07-24 22:17:55 +01:00
Carlos Florêncio 1e1b6c03a6 fix(web): improve queue scrolling and diff comment feedback (#1932) 2026-07-24 22:17:48 +01:00
Carlos Florêncio cfea8fe9cb fix: scope walkthrough overlays to their task (#1928) 2026-07-24 19:12:06 +01:00
Clément Fiere a4b3677d8d feat: show full timestamp as tooltip on chat message relative time (#1925) 2026-07-24 19:06:53 +01:00
João Salavisa 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>
2026-07-24 19:02:10 +01:00
João Salavisa 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>
2026-07-24 17:06:56 +01:00
Carlos Florêncio e2c0d74d51 fix(web): preserve chat focus when restoring task layout (#1923) 2026-07-24 16:44:24 +01:00
Carlos Florêncio 65c303d37f fix: distinguish session notification events (#1918) 2026-07-24 16:39:43 +01:00
Carlos Florêncio 2f6be0c777 fix(agents): harden ACP launcher configuration (#1917) 2026-07-24 16:36:44 +01:00
José Almeida 7e4f2bb1e5 feat(github): re-request dismissed reviews (#1921) 2026-07-24 15:55:57 +01:00
José Almeida 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
2026-07-24 15:50:30 +01:00
João Salavisa 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.
2026-07-24 14:31:11 +01:00
José Almeida 49afee96b3 fix(web): keep composer usable during clarifications (#1916)
* fix(web): keep composer usable during clarifications

* fix(web): guard clarification shortcuts in editor
2026-07-24 13:49:15 +01:00
João Salavisa 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>
2026-07-24 13:04:54 +01:00
João Salavisa 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>
2026-07-24 13:03:31 +01:00
João Manso 6fedac6c64 fix(build): support make dev on native Windows (#1886) 2026-07-24 12:08:22 +01:00
Carlos Florêncio 34946011a1 fix(web): restore sidebar task title overflow (#1913) 2026-07-24 12:08:07 +01:00
João Salavisa 99dfbbde58 feat(plugins): add keybindings and modal window capabilities (#1895)
* feat(plugins): add keybindings and modal window capabilities

Add two independent plugin UI capabilities.

Keybindings: plugins declare keybindings in the manifest under
ui.keybindings ({id, default, description}), validated backend-side
(combo grammar + unique ids, requires a UI bundle) and exposed to the
frontend. Plugin JS binds a handler via registry.registerKeybinding(id,
handler); a global dispatcher fires it. Combos are user-overridable in
Settings > Keyboard Shortcuts (namespaced plugin:{pluginId}:{id}) with
conflict detection against core and other plugins.

Modals: host.openModal({title?, content, size?, dismissible?}) returns a
{ close() } handle and renders in a single host-owned Dialog isolated by
PluginErrorBoundary, cleaned up on plugin unload.

Fixture plugin, PLUGIN-API.md / spec, and a Playwright e2e demonstrate a
keybinding handler opening a modal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): address review feedback on keybinding dispatch and validation

Core shortcuts now take precedence over plugin keybindings on a colliding
combo (defaultPrevented cooperation between the two capture-phase
dispatchers), a throwing plugin handler no longer aborts dispatch to other
plugins, and the keybinding combo grammar is tightened: the backend validator
and frontend parser both drop the undocumented control/super modifier
aliases and reject shift+digit/symbol combos that can never actually fire
(KeyboardEvent.key reports the shifted character, not the token).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): stop conflicting core shortcut from double-firing with plugin keybindings

The earlier defaultPrevented guard only covered the central useAppShortcuts
dispatcher. Per-component core shortcuts registered via useKeyboardShortcut
run in the bubble phase, after the capture-phase plugin keybinding dispatcher,
so a plugin-handled-and-prevented combo still triggered the core action too.
Guard useKeyboardShortcut (and use-editor-keybinds, another capture-phase core
listener) on event.defaultPrevented so exactly one action fires per combo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): make core shortcuts always win over plugin keybindings

Panel-search, task-switcher, and other core listeners never checked
event.defaultPrevented, so a plugin bound to the same combo (e.g. Cmd/Ctrl+F)
could still fire alongside them. Instead of patching every core listener,
usePluginShortcuts now computes the set of effective core shortcut combos
(registry, resolved through user overrides, plus FIND_IN_PANEL/SAVE) and
skips invoking any plugin handler whose combo is in that set, so the
matching core listener always handles the event exactly once.

Also adds a hook-level regression test for useEditorKeybinds' existing
defaultPrevented guard, and fixes a leaked capture-phase window listener in
use-keyboard-shortcut.test.ts that could bleed into later tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(web): keep Cmd+K closing the command panel after shortcut-precedence guard

cmdk's built-in vim bindings intercept Ctrl/Cmd+K and +P as list-navigation
shortcuts and call preventDefault() before the event reaches our window-level
useKeyboardShortcut listeners. Once that hook started bailing on
event.defaultPrevented (added to fix core/plugin shortcut double-firing), the
palette's own Cmd+K close/reopen toggle got silently suppressed whenever focus
was inside the open panel. Disable cmdk's vim bindings on the command palette
so its Ctrl/Cmd+K and +P always reach the app-level toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(plugins): document keybindings and modal window plugin APIs

Fill in the public author-facing docs that the internal plugin-keybindings
and modal-window contract (PLUGIN-API.md, spec.md) already covers: the
registry.registerKeybinding / host.openModal JS surface, the ui.keybindings
manifest field with its combo grammar, and a capability mention on the
Plugins overview page.

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>
2026-07-24 11:45:28 +01:00
João Salavisa e824dcca87 fix(web): collapse completed silent subagent cards (#1901)
Subagent cards keyed their auto-expand on `isActive || Boolean(resultText)`.
Silent (Auggie-style) subagents return a result_text but stream no child tool
calls, so `resultText` stays populated forever and the card rendered
permanently expanded, unlike subagents that emit child tool calls (which
collapse to their header + metadata row on completion). A render-phase reset
also wiped any manual collapse the moment result_text arrived.

Key auto-expand on the running state alone and drop the reset, so completed
subagents collapse consistently and the result text is one click away. Add
component coverage for the silent-subagent collapse, expand-on-click, and
manual-collapse-persists paths.

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:14:16 +01:00
Mathieu Virbel b4239d885e feat(agents): add configurable command prefix for sandboxed ACP launch (#1888) 2026-07-24 10:29:37 +01:00
Carlos Florêncio 2751b932df perf: cache storage analysis results (#1911) 2026-07-24 09:55:40 +01:00
Carlos Florêncio 1f4fabf30a fix: stabilize task and workspace creation (#1910) 2026-07-24 09:55:18 +01:00
Carlos Florêncio ae6e2f3daa fix(release): sign and verify release tags (#1902) 2026-07-24 09:54:00 +01:00
Jérémy Coatelen cd332ef26b feat(task): nest a task under another as a sub-task from the sidebar (#1837) 2026-07-24 09:51:45 +01:00
Clément Fiere 62919efbdc fix: distinguish archived automation runs from cancelled ones (#1860) 2026-07-24 09:50:36 +01:00
Clément Fiere 93c6987bf5 fix: strip echoed command from persisted shell output (#1898) 2026-07-24 09:48:00 +01:00
Carlos Florêncio 29c86b7fa1 feat: create local repositories from new tasks (#1849) 2026-07-23 22:30:40 +01:00
Carlos Florêncio 4a0ef3c66c feat: improve task sidebar overflow cue (#1908) 2026-07-23 22:29:49 +01:00
Clément Fiere 3d7b2f32a7 fix: resume an unarchived task's archive-cancelled and multi-repo sessions (#1905) 2026-07-23 21:25:42 +01:00
Carlos Florêncio ba8d99f33f fix(task): preserve saved panel focus on task return (#1907) 2026-07-23 21:24:37 +01:00
Carlos Florêncio 77ef32c86c feat: add simplified resource metrics display (#1904) 2026-07-23 21:24:10 +01:00
Carlos Florêncio 1f905be98f fix: support long Git worktree paths on Windows (#1878) 2026-07-23 20:21:23 +01:00
Carlos Florêncio 73bf0fb028 fix: improve session failure recovery UX (#1866) 2026-07-23 20:02:20 +01:00
Carlos Florêncio ca63861207 chore: consolidate harness delegation guidance (#1906) 2026-07-23 19:58:43 +01:00
Clément Fiere 7a044b7e02 fix(task): dedupe repository listing and close local-path create race (#1897) 2026-07-23 17:46:25 +01:00
Carlos Florêncio 8c010ebeca fix: inherit service temporary environment (#1894) 2026-07-23 17:24:54 +01:00
Carlos Florêncio 9cd0c27be0 feat: mark selected task repositories (#1881) 2026-07-23 16:32:25 +01:00
Carlos Florêncio d475cdf597 fix: scope Office agent tools and task mutations (#1867) 2026-07-23 16:31:37 +01:00
Carlos Florêncio a225fbac3e ci: harden trusted PR review evidence (#1896) 2026-07-23 16:22:50 +01:00
Carlos Florêncio 1c70495689 fix(web): restore persisted file tree expansions (#1892) 2026-07-23 15:18:19 +01:00
Carlos Florêncio 5167bb9832 feat: add external vcs file links (#1883) 2026-07-23 15:18:10 +01:00
Carlos Florêncio bfc372b4ad perf(github): speed up CI check feedback (#1882) 2026-07-23 15:08:05 +01:00
Carlos Florêncio f6007de1cb feat: move GitLab MR linking to task menus (#1868) 2026-07-23 14:46:07 +01:00