--- status: shipped created: 2026-08-03 owner: tbd --- # Repository and Workspace Secrets ## Why Repository setup and agent work often need credentials that are specific to a project: package registry tokens, deployment keys, test service credentials, or API keys. Today users must place those values on a shared agent or executor profile, even when only one workspace or repository needs them. This broadens access and becomes ambiguous when a task attaches several repositories. Kandev needs a repository-level authority grant with workspace-scoped storage and a deterministic, fail-closed way to build one task environment. ## What - Secrets can be created as **Global** or **Workspace**. - Global secrets are available across the current user's workspaces. Workspace secrets belong to exactly one workspace. - Existing secrets become Global during migration. - Agent and executor profiles can select Global secrets only. - A workspace-owned repository can bind environment variable names to Global secrets or secrets from that same workspace. - Every task attaching the repository inherits its bindings. A multi-repository task inherits all attached repositories' bindings plus its selected executor profile environment. - Repository bindings contain secret references only, never literal values. - The effective repository environment is available to repository setup, the agent process, child shells, and terminal-panel terminals in every supported executor, including explicitly approved forwarding to SSH. ## Scope and permissions Global means user-global when authentication is enabled and install-global when authentication is disabled. It does not make one authenticated user's secret visible to another user. A caller may: - manage their own Global secrets; - manage Workspace secrets only after Kandev authorizes that workspace; - bind a repository only to a visible Global secret or a secret whose `workspace_id` equals the repository's `workspace_id`; - view and edit bindings only through a repository they are authorized to access. Cross-user and cross-workspace IDs return not found or a generic invalid-binding response without revealing that the target secret exists. Administrators do not bypass workspace privacy. All instance-shared profile types, including agent and executor profiles, reject Workspace secret references both when saving and when resolving legacy or manually inserted data. ## Data model ### Secret User-visible secret metadata adds: ```text scope: "global" | "workspace" workspace_id: string | null ``` Rules: - `global` requires no workspace ID. - `workspace` requires an existing, authorized workspace ID. - scope and workspace are immutable after creation; users create a replacement to move authority. - existing user-visible rows migrate to `global`. - deleting a workspace deletes its Workspace secrets. - secret values remain encrypted with the existing AES-256-GCM store and never appear in list, repository, task, event, or error payloads. ### Repository secret binding ```text repository_id: string key: POSIX environment identifier secret_id: string created_at: timestamp updated_at: timestamp ``` The pair `(repository_id, key)` is unique. The repository foreign key cascades on repository or workspace deletion. The secret reference deliberately permits a dangling ID so deleting a secret leaves a broken binding that blocks future launch. Keys follow profile environment rules: at most 100 bindings per repository, maximum key length 256, POSIX identifier syntax, no duplicates, and no `TASK_DESCRIPTION` or `KANDEV_*` keys. ## API surface The existing HTTP and WebSocket secret CRUD contracts become scope-aware. - Creating a secret accepts `scope` and, for Workspace scope, `workspace_id`. Omitting `scope` remains backward-compatible and creates Global. - Secret metadata responses include `scope` and `workspace_id`. - The default list returns Global secrets. A Workspace-filtered list returns that workspace's secrets, with an explicit option to include visible Global secrets for a repository selector. - Update changes name and/or value only; scope is immutable. - Reveal and delete preserve existing response shapes while applying scope authorization. Repository create, get, list, update, and repository events include: ```json { "secret_bindings": [ { "key": "NPM_TOKEN", "secret_id": "..." } ] } ``` Create/update treats the supplied list as the complete desired set and persists repository fields and bindings atomically. Omitting the field on update preserves the existing set; an explicit empty list clears it. Automated find-or-create/backfill paths that do not submit bindings preserve them. ## Environment resolution Kandev resolves one task environment from named origins: 1. Validate all repository keys and collect the selected executor profile entries plus every attached repository binding without decrypting repository values. 2. Reject collisions with Kandev/runtime-owned values. 3. For a repeated key: - deduplicate exact references to the same secret ID; - reject different secret IDs; - reject literal-versus-secret or different-literal definitions. 4. Reveal each unique repository secret using Global-or-same-workspace authorization. 5. Reject any missing, deleted, unreadable, unauthorized, or wrong-workspace reference. 6. Flatten the validated result and continue normal launch. Agent-profile values retain their existing fill-missing behavior and may not overwrite this task environment. Repository/task order never selects a winner. Failure occurs before executor provisioning, worktree/container/sandbox creation, repository setup, or agent start. User-facing conflict errors contain the environment key and every relevant repository/executor origin. Broken-binding errors contain the key and repository origin. Neither contains plaintext or secret IDs. Routine logs follow the same redaction rule. ## Runtime lifecycle Bindings are evaluated when a task environment is provisioned or freshly recreated. The flattened map is the execution's environment snapshot. - Repository setup scripts see it through the existing launch environment. - Agents and their child shell commands inherit it. - A newly opened terminal for that execution receives the same snapshot. - Local, Worktree, Docker, Remote Docker, Sprites, and standalone transports receive the resolved map through their normal launch path. - SSH forwards the explicit repository-approved keys to remote agent and terminal instances in addition to its managed credential allowlist; it does not forward arbitrary host variables. Editing a binding or rotating a secret does not mutate an already-running process or terminal. Warm resume retains the provisioned environment. Reset Environment, cold recreation, or a fresh task environment resolves the current configuration. A terminal already open keeps its startup environment; a new terminal uses the current execution snapshot. ## Settings experience ### Global secrets The existing **Settings > General > Secrets** page becomes explicitly Global. It lists and creates Global secrets only. Existing create, edit, reveal, and delete interactions remain available. ### Workspace secrets Each workspace gains **Settings > Workspaces > [workspace] > Secrets**. It uses the same management surface but creates and lists only secrets for that workspace. The page explains that these secrets can be selected by repositories in this workspace and cannot be used by shared profiles. ### Repository bindings The repository editor gains an **Environment secrets** section. Each row has an environment key and a secret selector containing Global plus same-workspace secrets, with scope labels. Users can add, remove, and replace rows as part of the repository's existing manual-save flow. A deleted secret is shown as a missing reference rather than silently dropping the row. The selector never reveals values. Executor and agent profile selectors show Global secrets only. ## Mobile design contract - Entry point: the existing mobile Settings navigation exposes Workspace Secrets alongside Repositories, Workflows, Integrations, and Automations. - Hierarchy: secret list and create/edit forms use the existing settings page/card composition; repository bindings stay within the repository editor rather than opening a desktop-only side panel. - Interaction: rows stack to one column at phone width, selectors and key inputs use touch-sized controls, destructive actions retain confirmation, and save/cancel remain reachable without horizontal scrolling. - Scroll ownership: the Settings content area remains the single page scroll owner. Repository editing must not introduce nested vertical scrolling or a clipped fixed footer. - Parity: mobile can create, edit, reveal, and delete Workspace secrets and add/remove repository bindings, matching desktop capability. - Accessibility: scope is conveyed by text/badge as well as color; icon-only actions have localized accessible labels; missing references and validation errors are announced in text. ## Failure modes - Invalid scope/workspace combinations are rejected with 400. - A foreign or nonexistent workspace/secret is not disclosed. - A shared profile containing a Workspace secret cannot be saved and cannot resolve it at runtime. - Duplicate or reserved repository environment keys are rejected on repository save. - A deleted or unreadable bound secret blocks launch and names the affected key/repository without exposing secret material. - Conflicting executor/repository or repository/repository bindings block launch and list all origins; no partial environment is provisioned. - Database mutation of repository fields and bindings is atomic. - Secret decryption failures never fall back to a literal, stale value, or another same-named secret. - SSH forwarding includes only managed credential keys and the launch's explicitly approved repository keys. ## Persistence guarantees - Existing secrets retain IDs, ciphertext, names, owners, and timestamps and are marked Global. - Repository bindings survive restart and are inherited by new sessions and tasks. - Secret plaintext is never persisted in repository, task, session, event, or task-environment rows. - The effective decrypted map is process memory only and is cleared with the execution store. - Repository deletion removes its bindings. Secret deletion preserves broken binding IDs. Workspace deletion removes workspace repositories, their bindings, and Workspace secrets. - Schema changes and migrations are replayable on SQLite and PostgreSQL. ## Scenarios - **GIVEN** a Global secret and a Workspace secret in workspace A, **WHEN** a repository in A is edited, **THEN** both are selectable, while a repository in workspace B sees only the Global secret and B's own Workspace secrets. - **GIVEN** an executor profile editor, **WHEN** secret options load, **THEN** only Global secrets are available, and a direct request naming a Workspace secret is rejected. - **GIVEN** two attached repositories bind `NPM_TOKEN` to the same secret ID, **WHEN** a task launches, **THEN** the binding is deduplicated and the task provisions once with that value. - **GIVEN** two repositories bind `NPM_TOKEN` to different secret IDs, **WHEN** launch is requested, **THEN** launch fails before provisioning and names both repository origins without exposing IDs or values. - **GIVEN** an executor literal and repository secret both define `NPM_TOKEN`, **WHEN** launch is requested, **THEN** launch fails instead of comparing or choosing plaintext. - **GIVEN** a repository binding whose secret was deleted, **WHEN** a task using that repository launches, **THEN** launch fails with the repository and key while the stored binding remains visible as missing. - **GIVEN** a successful local, Docker, Sprites, or SSH launch, **WHEN** repository setup, the agent, an agent child shell, and a new terminal read the bound key, **THEN** each receives the same provisioned value for that execution. - **GIVEN** a running execution, **WHEN** a secret is rotated, **THEN** the existing process and open terminals keep their snapshot and a freshly recreated environment receives the new value. - **GIVEN** a phone viewport, **WHEN** a user manages a Workspace secret and binds it to a repository, **THEN** the complete flow is usable without horizontal overflow or a desktop-only control. ## Out of scope for v1 - Per-task secret bindings or overrides. - Workspace secrets on agent or executor profiles. - Per-branch bindings when a task attaches multiple branches of one repository. - Automatic environment-key namespacing. - Order-based precedence or “primary repository wins.” - Live mutation of running process or terminal environments. - Secret-value audit logs, version history, or automatic rotation.