Skip to content

feat: analytics enhancements, migration CLI, deployment fixes, and dependency upgrades - #3

Merged
dviejokfs merged 21 commits into
mainfrom
feat/docker-image-deploy-pipeline-routing
Feb 16, 2026
Merged

dviejokfs merged 21 commits into
mainfrom
feat/docker-image-deploy-pipeline-routing

Conversation

@dviejokfs

Copy link
Copy Markdown
Contributor

Summary

This release brings major new features across analytics, CLI tooling, and deployment infrastructure, alongside critical bug fixes and dependency upgrades.

Analytics & Visualization

  • Add interactive 3D Earth globe for visitor geolocation visualization using @react-three/fiber
  • Implement visitor journey tracking with full event history across sessions
  • Add page flow analytics endpoints (entry/exit pages, drop-off analysis)
  • Add date filtering for visitor analytics (Today, 7/15/30 days, custom range)
  • Add recent activity endpoint for real-time event tracking
  • Add new page detail and page flow components in the web UI

CLI: Migration Command

  • Implement migrate command to import projects from Vercel, Coolify, and Dokploy
  • Includes migration plan generation, risk assessment, environment variable setup, service and domain configuration
  • Pre- and post-migration verification checks

Deployment Fixes

  • Fix: Route Docker image uploads to the correct pipeline for Git projects — determine_deployment_source_type() now checks deployment metadata before project source type fallback
  • Fix: Use environment slug (not project slug) for manual deployment URLs, aligning with the upload_image handler behavior

Infrastructure & Dependencies

  • Upgrade Pingora to v0.7.0, clap to v4.5.50, lru to v0.16.3
  • Update TimescaleDB Docker images from pg17 to timescale/timescaledb-ha:pg18
  • Implement system user (id=0) creation for webhook contexts (GitHub App installations)
  • Enable pull request trigger for Rust tests CI workflow
  • Clean up outdated documentation and thoughts/ files

Other

  • Add source map support for error tracking
  • Add connection filter service to proxy
  • Add doctor and upgrade CLI commands
  • Update README with @temps-sdk/node-sdk package info
  • Add GitHub issue/PR templates and CONTRIBUTING/SECURITY docs

…git projects

determine_deployment_source_type() was checking project.source_type
before deployment metadata, causing Git projects to always route to the
git pipeline even when deployment metadata explicitly specified a Docker
image deployment. This meant uploading a Docker image to a Git project
triggered download_repo + build_image instead of pull_external_image.

Move the metadata check (deployment_source_type, external_image_ref,
static_bundle_path) before the project source type fallback so any
project type can deploy via Docker image or static bundle through the
remote deployment API.
deploy_from_image and deploy_from_static handlers were generating
deployment slugs as {project_slug}-{number}, producing URLs like
my-app-5.preview.domain instead of the expected format
{env_slug}-{number}.preview.domain. This was inconsistent with the
upload_image handler which already used the environment slug.

Align all remote deployment handlers to use environment.slug for the
deployment slug, producing URLs in the format
{env_slug}-{deployment_number}.{preview_domain}.
- Introduced new API endpoints for retrieving the complete visitor journey, including all events across sessions, and for analyzing page flow metrics such as entry/exit pages and drop-off points.
- Updated the analytics service to support fetching visitor journey data and page path visitors.
- Enhanced data models to include new response structures for visitor journeys and page flow analytics.
- Added query parameters for limiting sessions and filtering by device type in performance metrics.

This commit enhances the analytics capabilities by providing deeper insights into user interactions and page performance.
…and new assets

- Introduced the EarthGlobe component for visualizing visitor data on a 3D globe, utilizing @react-three/fiber and @react-three/drei for rendering.
- Added new assets: earth-blue-marble.jpg, earth-night.jpg, and earth-topology.png for the globe's texture.
- Updated VisitorGlobe component to integrate EarthGlobe and improve visitor data representation.
- Enhanced visitor popover with detailed information including location, last seen time, and device details.

This commit significantly improves the analytics interface by providing an interactive 3D visualization of visitor data.
- Added a date filter feature to the VisitorGlobe component, allowing users to select predefined date ranges (Today, Yesterday, Last 7 Days, Last 15 Days, Last 30 Days) or a custom date range.
- Introduced a new state management for date filters and updated the visitor data query to reflect the selected date range.
- Enhanced the UI with quick filter buttons for desktop and a dropdown for mobile, improving user experience in navigating visitor data over time.

This commit significantly enhances the analytics capabilities by providing flexible date filtering options for visitor data visualization.
- Added a new function to ensure the existence of a system user (id=0) in the database, which is necessary for handling webhook-created resources such as GitHub App installations that lack an authenticated user context.
- Integrated the system user creation logic into the setup process, ensuring it is created if it does not already exist.
- Updated relevant areas in the console API and Git provider manager to reference the system user for app installations and organization-level connections.

This commit enhances the setup process by ensuring a consistent user context for webhook interactions.
- Added a new migration command to the CLI, allowing users to migrate projects from platforms like Vercel, Coolify, and Dokploy.
- Introduced a comprehensive migration plan that includes project creation, environment variable setup, service management, and domain configuration.
- Implemented pre- and post-migration verification checks to ensure successful migrations and catch potential issues early.
- Enhanced the user interface for displaying migration plans, including risk assessments and actionable insights.
- Added necessary types and utility functions to support the migration process, ensuring a robust and user-friendly experience.

This commit significantly enhances the CLI's capabilities by providing a structured approach to project migration, improving user experience and operational efficiency.
…cking

- Implemented a new `get_recent_activity` function in the analytics service to fetch recent activity events based on project and environment IDs, with optional pagination.
- Created corresponding query parameters and response types for the recent activity endpoint, enhancing the API's capabilities for real-time analytics.
- Updated the application state and routing to include the new endpoint, ensuring it is accessible via the API.
- Added a seed script to generate fake real-time analytics data for testing the live activity feed.

This commit significantly improves the analytics functionality by providing a dedicated endpoint for retrieving recent activity events, facilitating real-time data tracking and visualization.
- Upgraded `pingora`, `pingora-core`, and `pingora-proxy` to version 0.7.0, enhancing the deployment platform's capabilities.
- Removed outdated dependencies such as `atty` and `clap` version 3.2.25, replacing them with `clap` version 4.5.50, which no longer relies on `atty`.
- Updated `lru` from version 0.14.0 to 0.16.3, ensuring improved performance and compatibility.
- Cleaned up `Cargo.lock` by removing unnecessary package entries and ensuring all dependencies are up-to-date.

This commit significantly improves the dependency management and overall stability of the project.
- Changed Docker images in various configurations and test utilities from `timescale/timescaledb:latest-pg17` to `timescale/timescaledb-ha:pg18` for improved performance and compatibility.
- Updated the contributing guide, docker-compose file, and multiple test files to reflect the new image version.
- Removed outdated documentation files related to GitHub installation flow and backup/restore test utilities to streamline the repository.

This commit enhances the database setup by ensuring the use of the latest TimescaleDB version, improving overall stability and performance.
- Updated the GitHub Actions workflow for Rust tests to enable triggering on pull requests to the main branch, enhancing CI/CD integration and ensuring tests run for proposed changes before merging.
- Updated the README.md to include the `@temps-sdk/node-sdk` package, providing users with information about its functionality related to blob storage.
Also updates install URL from install.sh to deploy.sh and refreshes
screenshots with corrected demo data (monitoring uptime, domains,
deployments).
…go.lock

- Updated dependencies including `aws-sdk-s3` to version 1.112.0, `aws-config` to version 1.6, and `octocrab` to version 0.49.5 for improved functionality and compatibility.
- Upgraded `bytes` to version 1.11.1 across multiple crates to enhance performance.
- Removed outdated dependencies and cleaned up `Cargo.lock` to streamline the project and ensure all packages are up-to-date.
- Enhanced email validation service to utilize the builder pattern for `CheckEmailInput`, aligning with the latest API changes.

This commit improves dependency management and overall stability of the project while ensuring compatibility with the latest versions of key libraries.
All external service providers (postgres, redis, mongodb, s3) had
wait_for_container_health with exponential backoff starting at 100ms
with factor 1.5x and no cap. By iteration 14 (~28s cumulative), the
polling interval was ~15s, causing health checks to miss the container
becoming healthy during Docker's 30s start_period.

Changes:
- Increase initial delay from 100ms to 500ms
- Increase max wait from 60s to 90s
- Cap individual delay at 2s to maintain responsive polling
- Add early exit when container state is EXITED or DEAD
- Include exit code in error message for dead containers
Both temps-query-redis and temps-query-s3 doc examples called
list_containers() and list_entities() from the DataSource trait
without importing it, causing doctest compilation failures.
Resolve version inconsistency where postgres.rs config defaults used
18-alpine but get_default_docker_image(), get_default_version(), and
parameter_strategies.rs used 17-alpine. All defaults, examples, schema
definitions, UI placeholders, and CLI help text now consistently use
postgres:18-alpine.

Updated test upgrade paths from v16->v17 to v17->v18, and changed
Docker image update tests to use same-major-version variants
(18 -> 18-alpine) to avoid data format incompatibility failures.
Wrap both unit and integration test steps in a retry loop (up to 3
attempts) to handle transient failures from Docker container startup
races, network timeouts, and testcontainer flakiness. Retries wait
5s between unit test attempts and 10s between integration test
attempts to allow Docker resources to stabilize.
…d of PathBuf

Modified several command files to change function signatures from PathBuf to Path for improved consistency and performance. This change enhances type handling across the codebase, ensuring better compatibility with Rust's type system. Additionally, adjusted the SetupCommand to use a Box for better memory management.
…dvisory

Updated the CI workflow to improve test resilience by implementing targeted retries for failed unit and integration tests, allowing for more efficient handling of transient failures. Additionally, modified the Clippy job to be non-blocking, ensuring that warnings do not fail the build. This change enhances the overall stability and feedback loop of the testing process.
@dviejokfs
dviejokfs enabled auto-merge February 16, 2026 18:58
@dviejokfs
dviejokfs disabled auto-merge February 16, 2026 18:59
@dviejokfs
dviejokfs merged commit 25abbf9 into main Feb 16, 2026
9 checks passed
@dviejokfs
dviejokfs deleted the feat/docker-image-deploy-pipeline-routing branch February 25, 2026 20:10
dviejokfs added a commit that referenced this pull request Apr 8, 2026
…way secret

Addresses three ship-blocking findings from the feat/autopilot security audit:

#1 [CRITICAL] Sandbox passwordless sudo narrowed to package managers only
  - Replace `NOPASSWD: ALL` with a scoped Cmnd_Alias covering apt-get, apt,
    dpkg, pip, uv, npm, bun. Validate via `visudo -c` at image build.
  - Claude can still `sudo apt-get install` mid-session (95% case); arbitrary
    privilege escalation inside the container is blocked.
  - Operators must rebuild the sandbox image for the change to take effect.

#2 [CRITICAL] Deployment tokens now expire
  - Workspace session token: 6h TTL (was: never).
  - Autopilot run token: 2h TTL (was: never).
  - Wildcard permissions retained for now — the memory/workspace handlers
    gate on admin Permission, not deployment-token permissions, so real
    scoping requires new permission variants. Tracked for Phase 2.

#3 [HIGH] Preview gateway shared secret is now required + auto-managed
  - `temps serve` generates `\$TEMPS_DATA_DIR/preview_gateway.secret` on
    first boot (32 random bytes, hex, 0600) and exports it as
    `PREVIEW_GATEWAY_SHARED_SECRET` so the Pingora proxy injects it on
    every forwarded preview request.
  - `preview_gateway::spawn_reconcile` passes the secret into the
    container env on create.
  - `temps-preview-gateway` binary now refuses to start without the secret.
  - Blocks cross-sandbox/cross-tenant direct hits to 127.0.0.1:8090 that
    would otherwise bypass the preview-password wall.
dviejokfs added a commit that referenced this pull request Apr 25, 2026
…autofixer (#58)

* feat(agents): AI agents framework + autofixer for error tracking

## Agents Framework (temps-agents crate)
- Multiple agents per project via .temps/agents/*.yaml
- YAML synced to DB during deployment (source of truth)
- Custom prompts, triggers (error/cron/manual), per-agent budget
- Claude CLI + Codex CLI support, real-time SSE streaming
- Dedup gates: cooldown, budget, concurrency limits
- Stuck run recovery, cancel endpoint

## Autofixer (two-phase error fixing)
- Analyze → Review → Fix → Review → Create PR workflow
- Live streaming of Claude conversation
- Interactive context input, workflow timeline
- Integrated into error group detail page
- Root cause analysis extraction from stream-json

## Git Provider
- create_pull_request() for GitHub + GitLab
- Auto-creates branch if it doesn't exist

## Data Model
- project_agents, agent_runs, agent_run_logs tables
- 3 migrations with data migration from autopilot

* feat(agents): autopilot cron scheduler and autofixer enhancements

Add cron-based autopilot scheduling for recurring AI agent runs,
integrate error tracking service into agents crate, and extend
autofixer with conversation continuation support for Claude CLI.

* docs(changelog): add autopilot agents and autofixer entries to unreleased

* feat(agents): pluggable sandbox system, agent detail page, and settings UI

- Add SandboxProvider trait with Docker and Local implementations
- Add SandboxRegistry for persistent sandbox state across autofixer phases
- Add per-agent sandbox_enabled toggle with migration
- Add AgentDetailPage showing all agent properties (triggers, limits, AI provider, prompt, sandbox)
- Add AgentSettingsDialog for creating/editing agents from dashboard
- Add sandbox_enabled to AgentRunResponse for per-run sandbox visibility
- Show sandbox badge in runs table and run detail page
- Fix run query to match by both agent_id and config_id

* feat(agents): add report deliverable, fix SENTRY_RELEASE with commit SHA

- Add "report" deliverable type: stores AI output as report without
  creating branches, PRs, or deployments (for health-check, audit agents)
- Fix SENTRY_RELEASE: use actual commit SHA from PR response instead of
  branch name, which contained slashes that Sentry rejected
- Add head_sha field to PullRequest struct (GitHub + GitLab providers)
- Add analysis field to AgentRunResponse API so frontend can render reports
- Show Report card in run detail UI when analysis is present
- Send completion notification for all deliverable types via shared helper

* feat(agents): markdown-to-HTML email rendering for agent notifications

- Add pulldown-cmark to temps-agents for markdown→HTML conversion
- Walk parser events to emit email-safe HTML with inline styles
  (tables, headings, code blocks, lists, bold, links all styled)
- Notification service detects HTML content and passes through directly
- Agent reports now render as rich formatted emails instead of raw markdown

* fix(agents): capture stderr from Claude CLI so error messages are visible

Both run() and continue_conversation() piped stderr but never read it,
resulting in empty error messages like "failed with exit code 1:".
Now stderr is captured in a parallel task and included in AiCliFailed.
Falls back to stdout if stderr is empty (Claude sometimes writes errors
to stdout in stream-json mode).

* feat(agents): global sandbox settings with UI configuration

- Export AgentSandboxSettings from temps-core
- Executor reads global sandbox settings (cpu_limit, memory_limit_mb)
  from DB and applies them to sandbox containers
- Global enabled flag: agents run in sandbox by default unless
  individually overridden
- Add Agent Sandbox page to Settings UI with enable toggle,
  CPU cores input, and memory limit input
- Add agent_sandbox to PlatformSettings frontend type
- Wire into settings save/load pipeline

* feat(agents): complete sandbox settings overhaul

Functionality:
- sandbox_enabled is now nullable (Option<bool>): None = use global
  default, Some(true) = force on, Some(false) = force off
- Migration makes column nullable, converts existing false to NULL
- Global settings add image (custom Docker image), network_mode
  (full/restricted/none) fields
- Docker provider uses per-run image and network overrides
- Sandbox status endpoint checks Docker availability and image readiness
- SandboxProvider trait gains is_available() and image_status() methods

Usability:
- Settings page explains what sandbox is and why to use it
- Resource presets (Light/Standard/Heavy/Custom) instead of raw inputs
- Network access selector with clear descriptions
- Custom image field with guidance on requirements
- Per-agent dialog uses tri-state select instead of toggle

* feat(agents): sandbox lifecycle logs, MCP config mounting, Docker status UI

Sandbox logs:
- Log resource config (CPU, RAM, network, image) before sandbox creation
- Log sandbox creation time in seconds
- Log sandbox destruction on cleanup

MCP servers in sandbox:
- Mount ~/.claude at /root/.claude-host (read-only) instead of /root/.claude
- Copy to writable /root/.claude on container start so Claude CLI can
  write session state while preserving auth tokens and MCP config
- MCP servers (npx-based) work in sandbox with network=full

Docker status:
- Add global /settings/sandbox-status endpoint (no project context needed)
- SandboxProvider trait gains is_available() and image_status() methods
- Settings UI shows Docker connection status and image readiness
- Refresh button to re-check status
- Clear indicators: green check / red X for Docker and image

* feat(agents): runtime preset sandbox images (Node, Bun, Python, Rust, Go, Full)

Replace free-text Docker image field with runtime presets. Each preset
generates a Dockerfile dynamically with the right base image and toolchain.
All presets include git, curl, and Claude CLI pre-installed.
Images built on first use, cached as temps-sandbox-{runtime}:latest.
Custom runtime auto-pulls user-provided images.
Python preset includes uv package manager.

* fix(agents): sandbox runtime changes take effect without server restart

The executor now always resolves the sandbox image name from current DB
settings instead of relying on the provider's startup-time config.
Changing the runtime preset in Settings takes effect on the next agent
run without restarting the server.

* test(agents): e2e sandbox lifecycle and runtime preset tests

Unit tests (14 new):
- resolved_image for all 6 runtime presets
- resolved_image for custom images and empty custom fallback
- Dockerfile generation for each runtime (base image, claude-code, uv)
- image_name_for_runtime mapping
- Unknown runtime defaults to node

Docker e2e tests (3 new, skip gracefully without Docker):
- Full lifecycle: create, exec, env vars, recovery, destroy
- Image status check per runtime
- Custom runtime config

* feat(agents): user context for manual agent triggers

- Add user_context field to TriggerAgentRequest and create_run
- Executor appends user context to the AI prompt when present
- UI shows a dialog with a text area when clicking Run on an agent,
  allowing users to provide instructions or a research topic
- Essential for agents like researcher that need user input to be useful

* fix(agents): make SourceMapService a required dependency, not optional

SourceMapService is always registered by the error-tracking plugin.
Using Option silently degrades autofixer quality — the agent gets
minified file paths instead of real TypeScript source locations.
Changed to require_service and Arc<SourceMapService> (non-optional).

* refactor: replace get_service with require_service for required dependencies

28 get_service calls that immediately unwrapped their result replaced
with require_service which fails fast at startup with a clear error
instead of silently disabling features or panicking with generic messages.

* fix(deployments): update workflow planner test for configure_agents job

Test expected 8 jobs but the agents feature added a 9th job
(configure_agents). Updated assertion to match.

* test(agents): integration tests for report deliverable, user context, sandbox, markdown

24 new tests covering recently added features:
- Report deliverable: stream-json extraction and fallback
- User context: prompt appending with separator
- Sandbox override: all Option<bool> vs global combinations
- Markdown email: headings, bold, tables, code blocks, empty input
- Config service: sandbox_enabled Option<bool> storage

Total: 135 tests passing (was 113)

* fix(agents): handle root user for Claude CLI and sandbox containers

Claude CLI refuses --dangerously-skip-permissions when running as root.

Host fix: detect root via euid, drop to temps or nobody user via su.
Sandbox fix: Dockerfiles create non-root temps user with passwordless
sudo, container runs as USER temps, workspace owned by temps.
Also cleans up leftover autofixer work dirs from crashed runs.

* feat(agents): rebuild sandbox image button in settings UI

- Add rebuild_image() to SandboxProvider trait
- Docker provider removes old image then rebuilds from Dockerfile
- POST /settings/sandbox-rebuild endpoint
- Rebuild Image button in Docker Status section of settings

* feat(agents): AI provider setup, auth types, agent management UI

AI Provider (Settings > AI Agents):
- Provider selector: Claude Code, OpenCode, Codex
- Auth type: Subscription (OAuth token) vs API Key
- Dynamic model list from installed CLI
- Token save with encryption, smoke test
- No host ~/.claude mount, auth via env vars only

Agent management:
- Enable/disable switch on every agent card
- Edit dialog: schedule cron, deliverable, form resets on agent change
- Enter key submits form
- OpenCode CLI provider

Fixes:
- Sandbox status resolved against global setting in run detail
- Duplicate Root Cause Analysis heading stripped from PR body
- Raw stream-json filtered from error event display
- CLAUDE.md dependency rules

* feat(workspace): terminal sessions, sandbox limits, audit logging

- Live Docker cgroup updates on session PATCH (cpu/memory/pids)
- Default sandbox limits bumped to 4 vCPU / 8 GB for JS frameworks
- Audit logging on terminal websocket attach/detach
- Workflow memory service + handlers
- Fix pre-existing test bitrot in workspace + agents crates

* fix(security): phase 1 hardening — sudo allowlist, token expiry, gateway secret

Addresses three ship-blocking findings from the feat/autopilot security audit:

#1 [CRITICAL] Sandbox passwordless sudo narrowed to package managers only
  - Replace `NOPASSWD: ALL` with a scoped Cmnd_Alias covering apt-get, apt,
    dpkg, pip, uv, npm, bun. Validate via `visudo -c` at image build.
  - Claude can still `sudo apt-get install` mid-session (95% case); arbitrary
    privilege escalation inside the container is blocked.
  - Operators must rebuild the sandbox image for the change to take effect.

#2 [CRITICAL] Deployment tokens now expire
  - Workspace session token: 6h TTL (was: never).
  - Autopilot run token: 2h TTL (was: never).
  - Wildcard permissions retained for now — the memory/workspace handlers
    gate on admin Permission, not deployment-token permissions, so real
    scoping requires new permission variants. Tracked for Phase 2.

#3 [HIGH] Preview gateway shared secret is now required + auto-managed
  - `temps serve` generates `\$TEMPS_DATA_DIR/preview_gateway.secret` on
    first boot (32 random bytes, hex, 0600) and exports it as
    `PREVIEW_GATEWAY_SHARED_SECRET` so the Pingora proxy injects it on
    every forwarded preview request.
  - `preview_gateway::spawn_reconcile` passes the secret into the
    container env on create.
  - `temps-preview-gateway` binary now refuses to start without the secret.
  - Blocks cross-sandbox/cross-tenant direct hits to 127.0.0.1:8090 that
    would otherwise bypass the preview-password wall.

* fix(agents,proxy,workspace): phase 2 security hardening

- LocalSandboxProvider now gated behind TEMPS_ALLOW_LOCAL_SANDBOX env
  var. Refuses to run agents directly on the host unless explicitly
  opted in — prevents silent fallback from Docker to host execution.
- Terminal WebSocket hardened: 1 MiB max frame, 4 MiB max message,
  token-bucket rate limit (2 MiB/s, 8 MiB burst) on both binary and
  text input paths. Blocks trivial DoS from a hijacked session token.
- Preview auth cookie fingerprint now SHA-256(argon2_hash)[..16] hex
  instead of a raw argon2 prefix, so the client-visible cookie no
  longer leaks hash bytes. Added determinism/uniqueness test.

* fix(agents,workspace,proxy): phase 3 security hardening

- Sandbox kill_processes now takes a typed KillSignal enum (Term|Kill)
  instead of a raw i32, constraining callers to SIGTERM/SIGKILL. Stops
  a future caller from smuggling arbitrary signals (SIGSTOP, SIGUSR1,
  etc.) through the sandbox exec boundary. Unit tests cover the
  enum-to-signum mapping and Copy-ness.
- PreviewAuthLimiter's failure map is now hard-capped at 65_536
  entries with opportunistic expired-entry eviction + oldest-entry
  fallback. Closes the unbounded-memory-growth vector where an
  attacker sprays unique (ip, session_id) pairs to OOM the proxy.
  Test sprays 70k unique keys and asserts the map stays within cap.

* test(agents,workspace): add integration + unit tests for security hardening

Docker Hub pull-then-build fallback:
- ensure_image_for_runtime now tries gotempsh/temps-sandbox-{runtime}
  from Docker Hub first, falls back to local build if pull fails.
  Speeds up CI/fresh installs from minutes to seconds when images
  are published.
- Tests: hub_image_for_runtime naming, pull_fallback_on_missing_hub_image

Preview gateway shared secret (Phase 1):
- 5 unit tests: creation, idempotency, empty-file rejection, 0600
  perms on Unix, nested parent dir creation

KillSignal integration test:
- Docker-gated test spawning real sleep processes, verifying SIGTERM
  and SIGKILL. Skips when Docker unavailable or temps serve running.

Terminal WebSocket rate limiter:
- Token-bucket math unit test verifying drain, rejection, refill

Sessions handler:
- is_safe_tab_id and tmux_cli_for_provider unit tests

* fix(agents): update glab to v1.91.0, fix plugin init on image build failure

- glab 1.51.0 download URL returns 404 from GitLab — update to 1.91.0
  and simplify arch mapping (dpkg --print-architecture already returns
  amd64/arm64, no x86_64 translation needed)
- When Docker is available but image pre-build fails, continue with the
  Docker provider instead of falling back to ensure_local_sandbox_allowed.
  The image will be rebuilt on first agent run. This prevents plugin init
  from crashing in CI where TEMPS_ALLOW_LOCAL_SANDBOX is not set.
- Update Dockerfile tests to match current base images and native Claude
  installer (ubuntu:24.04 for node, golang:1.23-bookworm for go,
  claude.ai/install.sh instead of npm claude-code)
- Add build-sandbox-images.sh script for multi-arch image builds
- All 6 sandbox images published to Docker Hub (gotempsh/temps-sandbox-*)

* feat(agents): config repos, global secrets, sandbox reliability

- Add per-agent config repo (URL + branch) for .claude/ overlay
- Add global secrets (agent_secrets table) with AES-256-GCM encryption
  - Secrets defined at settings level, not per-project
  - Routes at /settings/secrets with SettingsRead/Write permissions
  - Migration renames project_secrets → agent_secrets, drops project_id
- Add secrets management UI in Agent Sandbox Settings page
- Sandbox image rebuild now always builds locally (skip Docker Hub pull)
- Add /opt/claude-backup in Dockerfile for named-volume resilience
- Post-start exec restores Claude CLI if masked by stale named volume
- Use full path /home/temps/.local/bin/claude in exec commands
- Stream rebuild progress via SSE to frontend
- Log container ID and image name in agent run logs
- Add global config repo setting in platform settings

* feat(settings): add AI quickstart guide to agent settings page

Progressive 4-step setup card at the top of AI Agents settings:
choose provider, save credentials, check Docker/sandbox, test
connection. Shows next-action cards when complete (open workspace,
configure agent). Includes collapsible "how it works" explainer.

* feat(agents): implement config repo injection, secrets, and managed-agents-style YAML

- Implement inject_config_repos_and_secrets in executor: clones global
  and per-agent config repos, overlays .claude/ into sandbox via
  write_directory, resolves ${TEMPS_SECRET:name} placeholders in JSON
  files, injects env-type secrets as /workspace/.temps/secrets.env and
  file-type secrets to their mount paths

- Add SecretService to AgentExecutor constructor for secret resolution

- Add model/system/tools fields to AgentYamlConfig aligned with Claude
  Managed Agents conventions: model takes precedence over provider,
  system takes precedence over prompt, tools array for inline tool
  definitions. Full backward compatibility maintained.

- Update quickstart UI to use managed-agents terminology: Agent,
  Environment, Session, Config & Secrets as core concepts

- Update config_service sync to use resolved_provider/resolved_prompt

* feat(agents): add MCP servers, skills, tools, and custom tool proxy

- Add mcp_servers_config, skills_config, tools_config JSONB columns to
  project_agents (migration m20260409)
- MCP servers injected into sandbox .claude/settings.json with deep-merge
- Skills written as .claude/skills/{name}.md files in sandbox
- New temps-agents-mcp-proxy crate: lightweight MCP stdio proxy (Bun script)
  that bridges custom tool definitions to webhook endpoints
- Custom tools (type: "custom" with webhook_url) auto-detected from tools_config,
  proxy script + config injected at runtime, registered in settings.json
- API handler exposes new fields with env value masking for MCP servers
- UpsertAgentRequest supports mcp_servers_config, skills_config, tools_config
- ${TEMPS_SECRET:name} placeholders resolved in MCP env vars and webhook headers
- Full YAML roundtrip test for custom tools

* feat(agents): project-level skill and MCP definitions with slug-based references

Workflows now reference skills and MCP servers by slug instead of inline
content. Definitions are managed as shared project-level resources with
full CRUD API, resolved at runtime by the executor and injected into the
sandbox.

- Add project_skill_definitions and project_mcp_definitions tables
- Add DefinitionService with CRUD + batch slug resolution
- Add REST endpoints for /projects/{id}/skills and /projects/{id}/mcp-servers
- Update YAML config to parse slug arrays (skills: [a, b], mcp_servers: [c])
- Executor resolves slugs, writes skills as .claude/skills/*.md, merges MCP
  configs into .claude/settings.json, warns on unresolved slugs
- Frontend dialog uses checkbox pickers instead of inline editors
- Add webhook token, AI session tracking, run detail enhancements

* feat(agents,cli): global skill/MCP definitions, skill archives, CLI management commands

- Add global skill and MCP server definitions (settings/* endpoints) alongside
  project-scoped ones; combined-scope queries inject both into agent runs
- Support skill archives: tar.gz uploaded as BYTEA, extracted into the sandbox
  at /workspace/.claude/skills/<slug>/ via SandboxRegistry::write_directory
- Write merged MCP server config to both /workspace/.claude/settings.json and
  /home/temps/.claude.json so Claude Code picks it up at user level
- Add temps-cli skills and mcp-servers commands (list/create/update/delete)
  with @path support for files, directories, and tar.gz archives
- Make containers list -e optional; aggregates across all environments when
  omitted, with new Env column in output
- Web UI: global + project-scoped Skills and MCP Servers settings panels
- Migrations: allow nullable project_id on definitions; add archive BYTEA column
- Remove orphaned temps-demo-apps submodule reference

* fix(agents): harden skill/MCP definitions against common attack paths

- Validate slug format in all create paths (length, charset, leading char)
- Replace bulk tar unpack with per-entry unpack_in + 500MB decompression cap
  and reject symlinks/hardlinks (prevents path traversal + zip bombs)
- Layer 50MB DefaultBodyLimit on skill upload routes
- CLI skills import: use execFileSync with validated owner/repo/branch regex
  instead of shell-interpolated execSync (prevents command injection)
- Add audit logging to all 14 write handlers via shared DefinitionAudit
- Custom Debug impl on ResolvedSecret redacts value field

* feat(agents): multi-provider Codex/OpenCode support with sandbox fixes

- Fix Codex CLI invocation: use `codex exec --dangerously-bypass-approvals-and-sandbox --json`
  to disable bwrap nested sandbox (fails in Docker) and emit structured JSONL
- Add provider catalog (`ai_cli/catalog.rs`) with credential formats per provider
  (ApiKey, OauthToken, ConfigFile) and seed paths
- Credential injection uses per-provider `provider_config()` instead of legacy
  flat fields, fixing wrong credential being written for Codex
- Add provider-aware MCP config injection (Claude JSON, Codex TOML, OpenCode JSON)
  via `sandbox_injector::write_mcp_configs()`
- Deferred credential file writing for ConfigFile/OauthToken auth types
  (~/.codex/auth.json, ~/.claude/.credentials.json)
- Provider-aware output parsing (Codex vs Claude) for token tracking
- Frontend: parse Codex JSONL format in AutopilotRunDetail with agent_message,
  command_execution, and turn.completed events rendered as readable conversation
- Make workflow YAML provider field optional to respect platform default
- Multi-provider workspace sessions, AI provider settings UI, sandbox image builder

* feat(agents,cli): sandbox preview routing, DB-backed gateway secret, JSON quiet spinners

Preview gateway:
- Persist shared_secret in settings.preview_gateway (DB), with legacy file migration
- Export secret to env from DB on reconcile, eliminating cwd/data_dir fragility
- Force rebuild when container missing PREVIEW_GATEWAY_SHARED_SECRET env
- Surface real gateway health (restarting/crash_looping/running/stopped)
  instead of reporting green while container crash-loops

CLI:
- Suppress ora spinners when leaf command is invoked with --json so stdout
  stays pipeable to jq/parseable by callers

Pre-existing clippy/compile fixes needed to unblock the commit hook:
- io::Error::other for openpty failure
- allow await_holding_lock on pty-agent integration tests (intentional serial lock)
- rewrite preview login/logout guards as Option::filter (no is_some+unwrap)
- derive Default on CreateSandboxRequest; drop explicit impl
- plain String::from over format! without interpolation
- #[allow(too_many_arguments)] on run_git_clone
- const_assert on timeout constant sanity checks
- mock workspace_sessions::Model: add public_id field
- SandboxResponse test fixture: add preview_url_template field
- docs: unindent nested list item in message_executor

* feat(agents,sandbox,backup,proxy): autopilot runs, preview passwords, PG major upgrades, generic restore

Agents / Autopilot:
- CLI `temps workflow run` with error-group + ephemeral YAML support
- Prompt persistence (`agent_runs.prompt_text`) + frontend viewer
- PR deliverable guidelines (<pr_title>/<pr_body>) with sanitization
- GitHub credential injection via files only (no env vars; avoids Copilot PAT pickup)
- OpenCode tool event parsing + noise reduction in CLI renderer
- Multi-provider Claude/Codex/OpenCode stream parsing

Sandbox:
- Preview password support (client-generated plaintext, argon2 hash stored)
- Preview gateway routing for sandboxes + workspace sessions

Backup / Postgres:
- Generic restore framework (PG/Redis/Mongo/S3) via `restore_runs`
- Postgres major version upgrades (`postgres_major_upgrades` + UI)
- Default S3 source with partial-unique constraint + fallback resolver

Proxy:
- Preview wall hardening + structured proxy logs

Web:
- Autopilot run detail with streaming + prompt viewer
- S3 sources management, sandbox preview password card
- Major upgrade dialog + detail pages

* feat(agents,sdks,web): move injected skills to home, add analytics SDK packages, error-tracking setup wizard

- Move Temps-injected Claude/Codex skills from /workspace/.claude/skills
  to /home/temps/.claude/skills so they no longer pollute the user's git
  working tree via the /workspace bind mount (executor, sandbox_injector,
  session_manager). Adds workspace_volume migration for agent_runs to
  persist /workspace across autopilot runs.
- Introduce standalone analytics SDK packages: @temps/analytics-core,
  @temps/analytics-browser, @temps/svelte-analytics, @temps/vue-analytics,
  shared across the React SDK. Adds SessionRecorder, SpeedTracker, and
  EngagementTracker primitives.
- Add error-tracking setup wizard (SetupWizardShell + ErrorTrackingSetup)
  and wire it into ProjectDetail/ProjectOverview.
- Harden AutopilotPage YAML example to match WorkflowYamlConfig schema
  (provider:, on:). Replace confirm() with AlertDialog in IpAccessControl.
- Autofixer/trigger/run_service refinements, sandbox_registry plumbing,
  request_metadata updates, proxy + analytics handler touch-ups.

* feat(sdks,web): @temps-sdk/sandbox package, AI Workflows route split, sandbox docs embed

- sdks/node/packages/sandbox: new Sandbox SDK mirroring @vercel/sandbox
  (Sandbox.create/get/list, exec/execDetached, fs read/write/stat/mkdir,
  domain(port), lifecycle, RFC 7807 SandboxError). 5 unit tests passing.
- web: extract /agent-sandbox into layout + dashboard + providers
  list/detail + sandbox/preview/secrets sub-pages; delete the old
  AgentSandboxSettings monolith.
- web: embed CreateSandboxDocs on /sandboxes (CLI via bunx @temps-sdk/cli,
  REST, SDK) — full card in empty state, collapsible banner otherwise.
- web: sidebar/settings/command palette/error-tracking polish from prior
  sessions, plus frecency hook + lib.

* style(web): apply Vercel Geist palette to light and dark themes

Match Vercel's dashboard look — pure white cards with #eaeaea hairline borders
in light mode, #0a0a0a background with #1a1a1a cards in dark mode. Swap charts
to Vercel accent set (blue/green/amber/red/purple) and use #0070f3 focus ring.

* feat(revenue,web,cli): per-project revenue tracking with metered MRR

Adds the temps-revenue crate: Stripe/LemonSqueezy webhook ingestion, CSV
import, analytics (MRR/ARR/churn), and typed per-provider config for
price/product allowlists. Metered/tiered subscriptions derive MRR from
paid invoice lines so flat + usage-based revenue doesn't double-count.

Ships the Revenue page in the web console with config editor dialog,
CSV import, and CLI commands for scripted access. Includes supporting
env-vars provider refactor, entity additions, and DB migrations.

Also untracks temps_data/auth_secret and temps_data/encryption_key
(local runtime secrets that should never have been in the repo).

* chore(migrations,web,revenue): squash Apr migrations, add revenue global events, prune unused web components

- Squash 34 April migrations (sandbox, workspace, agents, skills, MCP, revenue, restore, PG upgrades) into m20260421_000001_squash_apr_post_v006
- Add global_recent_events service + GET /revenue/events HTTP handler for org-wide transaction queries
- Remove ~60 unused web components (onboarding wizards, dashboard, storage/webhooks dialogs, analytics charts, icons)
- Add ContainerDetailPage and Revenue page, useContainerMetricsStream hook

* fix(notifications): use sort_by_key to satisfy clippy unnecessary_sort_by

CI clippy runs with -D warnings and flagged the sort_by closure as
clippy::unnecessary_sort_by. Replace with sort_by_key + Reverse.

* feat(auth,web): expose email_verified and timestamps on user API, add UserDetail page

- Add email_verified, created_at, updated_at fields to UserResponse and internal User type
- New UserDetail page with route wired in App.tsx
- UsersManagement surfaces verification and timestamps in the list

* fix(web,log-aggregator): rewrite settings links and satisfy clippy

- Replace /settings/<section> links with /<section> across web app
  (domains, git-providers, dns-providers, agent-sandbox, etc.);
  App.tsx redirect routes remain as the old→new bridge
- Use sort_by_key in temps-log-aggregator search to fix
  clippy::unnecessary_sort_by failure

* fix(email): count unique bounces/complaints and fix open/click rate rounding

Global tracking stats now query email_events for distinct bounce/complaint
emails (the emails table has no aggregate column for those), and the web
UI no longer multiplies an already-percent rate by 100.

* refactor(web): polish traces, container logs, domain detail, email analytics

- traces: hide OTel setup when traces exist; toggle via Setup button
- container logs: restyle to match the main Logs page (theme-aware, shared LogLine component)
- domain detail: drop ui.sh variant picker, keep Split layout, prune dead helpers
- email analytics: make event rows clickable to the email detail page
- service-logo: inline SVGs instead of external /storage assets

* fix(web): pass project_id as path param in ProjectAnalytics query

* feat(backup,providers,web): add service restore, external service health monitoring, and data-browser UX

- temps-backup: new restore handler/service supporting Postgres, MongoDB,
  Redis, and S3 sources with streaming progress and audit logging
- temps-providers: external service health monitor with per-provider
  checks (postgres, mongodb, redis, rustfs, s3) and connection pooling
- temps-git: connection health service and background checks
- temps-entities + migrations: external_service_health_checks table and
  git_provider_connections health columns
- temps-cli: new `services restore` command and refreshed openapi types
- web: service data browser with tabs, saved views, smart cells, command
  bar, service health card, service-type presets, and restored BackupDetail
  / ServiceDetail / S3SourceDetail flows
- web: dashboard sidebar/header polish, plugins page, json-schema form
  improvements

* feat(backup,providers,web): expand S3/RustFS restore, engine compat, and UI polish

- providers/s3,rustfs: add restore_capabilities + restore_to_new_service so
  S3/RustFS services can be provisioned fresh from a backup; update default
  RustFS image to 1.0.0-alpha.98
- providers/mongodb: capture wal-g stdout/stderr, salvage success-on-exit≠0,
  add auth-probe loop with _alt_password fallback
- backup: classify object-store backups as "mirror" (was "unknown"); skip
  control-plane backups from service restore picker
- backup/restore: engines_compatible() helper treats s3/rustfs/minio/blob as
  one family so cross-engine restores within the object-store family work
- backup/restore: decrypt s3_source credentials once into plaintext clone and
  pass to trait impls — fixes in-place restore failing with ciphertext-as-key
- web/ServiceRestore: mirror engine-compat in UI filter; add helper
- web/ServiceTypePresets: make RustFS the default S3 engine (MinIO now legacy)
- web: add EmailDomainDetail page + route; refactor EmailDomainsManagement

* fix(analytics,web): paginate service backups and satisfy clippy sort_by_key

Replace `sort_by` with `sort_by_key(Reverse(...))` in analytics top-pages
ranking to clear `clippy::unnecessary_sort_by`, and add paginated backup
lists to ServiceDetail and ServiceRestore with responsive page controls.

* feat(git,web): add OAuth state store, sync progress counts, and credentials editor UI

Adds oauth_states table and migration for secure OAuth flow state, sync_progress_count
column on git provider connections, GitLab provider enhancements, and a new
CredentialsEditorDialog with supporting git-providers helpers.

* fix(git,web): support GitLab nested groups end-to-end via repo-id routing

GitLab repositories inside nested groups (e.g. gala-games/chain/platform/
operation-api-next) were stored with only the first path segment as `owner`,
so owner/name lookups produced `gala-games/operation-api-next` and the
upstream GitLab API returned 404 (surfacing as a 502 from branches). The
frontend also couldn't parse the four-segment path back into owner/name.

Backend: derive `owner` from `path_with_namespace` minus the trailing slug
in all three GitLab provider code paths, use the URL slug (`path`) for
`name`, expose `git_provider_connection_id` on `RepositoryResponse`, add
`GET /repository/{id}` for symmetric by-id access, and backfill existing
rows via a new migration.

Frontend: switch the import route to `/projects/import/:repositoryId`,
fetch the repo and its branches by id so the browser no longer encodes
any repo path, and let the server derive the git provider connection
from the row itself.

* feat(git,web): fire-and-forget repository sync with guaranteed flag reset

Repository sync previously ran inline under the HTTP request. Large
GitLab tenants (500+ repos) routinely hit browser/proxy timeouts, leaving
the connection stuck at `syncing: true` until the next server restart.
The handler now spawns a detached tokio task that owns a drop-guard and
a 30-minute deadline, so:

- `POST /git-connections/{id}/sync` returns 202 immediately.
- `syncing=false` is written on every exit path (success, error, panic,
  timeout, task cancel) via `SyncFlagGuard::Drop`.
- A hard deadline bounds runaway syncs so the flag can never get wedged.
- PAT provider creation no longer blocks on the first sync.

Also removed the dead 500-preset-broadcast-per-sync loop (nobody subscribed),
mapped the new SyncTimeout variant to HTTP 504, and wired real toast
feedback on the frontend so failed syncs surface a visible error instead
of silently invalidating the query.

Bundles in-progress work across temps-blob, temps-error-tracking,
temps-providers, temps-deployments (npmrc helper, build_image tweaks),
and profile tweaks in Cargo.toml/.gitignore.

* fix(git,web): url-encode GitLab refs and add multiline env var support

Fix GitLab archive/file/commit endpoints returning 404 for branches
containing slashes (e.g. chore/npmrc-gitlab-token) by URL-encoding
ref_spec. Also adds multiline textarea UI for env vars (e.g. .npmrc),
switches template preview images to hosted URLs.

* feat(secrets,web): mount per-project secrets as files at /run/secrets/<KEY>

Adds a Secrets feature that delivers sensitive values to user containers
as bind-mounted files under /run/secrets/<KEY> (mode 0400, root-owned),
matching the Docker Swarm convention. Values are AES-256-GCM encrypted
at rest, never appear in `docker inspect`, and are never returned in
plaintext from the API after creation.

Backend:
- New `secrets` and `secret_environments` tables (migration
  m20260424_000001_create_secrets) mirroring env-var scoping
- `SecretService` (temps-environments) with create/list/update/delete
  /get_for_deploy, 1 MiB cap, key validation, always-encrypted, 17 unit
  tests covering scoping, validation, encryption roundtrip
- `DeployRequest.secrets` HashMap; deployer materializes secrets to a
  per-container host directory and bind-mounts it into the container
  (chosen over tmpfs+tar-upload because /run/secrets doesn't exist in
  most images at create time)
- `workflow_planner::gather_secrets` decrypts project+env secrets and
  routes them through the existing job_config plumbing
- `remove_container` cleans up the per-container secrets host dir
- HTTP handlers (list/create/update/delete_project_secret) under
  /projects/{id}/secrets, named with `project_` prefix to avoid
  collision with temps-agents existing /settings/secrets routes

Frontend:
- SecretsSettings.tsx with create dialog (key validation, 1 MiB
  client cap, "cannot be retrieved after save" warning, masked
  placeholder rows, delete confirmation)
- Production+preview environments pre-selected by default
- N keyboard shortcut to open the create dialog (mirrors env-vars page)
- Settings sidebar entry, command palette entry, route wired in

* chore(deps): patch 5 RUSTSEC advisories via cargo update

Bumped transitive dependencies to resolve five advisories where a
semver-compatible fix was available:

- rustls-webpki 0.103.10 → 0.103.13 (RUSTSEC-2026-0098, -0099, -0104)
  CRL-parsing panic, URI name-constraint bypass, wildcard bypass
- rand 0.9.2 → 0.9.4 (RUSTSEC-2026-0097)
- rand 0.8.5 → 0.8.6 (RUSTSEC-2026-0097)
  Unsoundness with custom logger calling rand::rng()

Drops `cargo audit` from 9 vulnerabilities down to 4. Remaining items
are all transitive deps blocked on upstream maintainers:

- rustls-webpki 0.101.7 (3 advisories) — pulled in via
  aws-smithy-http-client → rustls 0.21. Requires aws-smithy-http-client
  to migrate to rustls 0.23. Added to Cargo.toml triage notes.
- protobuf 2.28.0 (already triaged — pingora → prometheus chain)
- remove_dir_all 0.5.3 (already triaged — nixpacks → tempdir chain)
- rsa 0.9.10 (already triaged — no upstream fix)

No source code changes — only Cargo.lock updates and Cargo.toml
documentation refresh.

* fix(deployer): chown secrets to image USER with graceful EPERM fallback

Secrets mounted at /run/secrets were written mode 0400 root-owned on the
host, so containers running as a nonroot user (e.g. distroless nonroot
= 65532, node:alpine, USER 1000) got EACCES when reading them.

Now: before materializing the secrets dir, inspect the image's
Config.User and parse the numeric uid/gid, then chown the dir and each
file to match. Handles empty/"root"/"0" → (0,0), "1000" → (1000,1000),
"1000:2000" → (1000,2000), ":1000" → (0,1000). Named users are logged
with a warning and fall back to root ownership (user should switch to
a numeric UID).

When Temps itself runs unprivileged (typical on macOS dev, any non-root
Linux install), chown(2) to a foreign uid returns EPERM. Instead of
failing the deploy, log a warn! once and downgrade to world-readable
(0755 dir / 0444 files) so the container can still read its secrets.
The trust boundary is still the container (cap_drop ALL,
no-new-privileges, ro bind mount); strict per-uid ownership kicks back
in automatically when Temps runs as root.

Adds tests for the USER spec parser (15 cases) and for the EPERM
fallback path. All 65 existing tests still pass.

* fix(clippy): resolve sandbox sort_by and log-aggregator unused_mut warnings

CI was failing on `cargo clippy --workspace --all-targets --all-features
-- -D warnings`. Two errors:

- temps-sandbox/job_tracker.rs: `sort_by` with reverse cmp → `sort_by_key`
  with `std::cmp::Reverse`.
- temps-log-aggregator/search.rs: drop unused `mut` on a test binding that
  is only read after construction.

Bundle the previously-unstaged log dedup work (collector.rs `since=N+1`
fixes, search.rs HashSet-based dedup + tests, LogStream `Hash` derive) so
the branch lands in one coherent state rather than splitting closely
related changes across commits.

* fix(clippy): use sort_by_key in disk_space and add ci-local.sh

- Replace sort_by closure with sort_by_key + Reverse to satisfy
  clippy::unnecessary_sort_by (was failing CI in temps-monitoring).
- Add scripts/ci-local.sh that mirrors the rust-tests workflow
  (fmt --check, cargo check, clippy -D warnings) so PR-equivalent
  checks can be reproduced locally with one command. Supports
  per-step targets and a --fix mode for fmt and clippy autofixes.

* fix(clippy): collapse lighthouse match arm and bust ci-local.sh cache

- Collapse the nested `if` inside the `None` arm of the score match
  in lighthouse-plugin into an `if` guard on the arm itself, satisfying
  clippy::collapsible_match (was failing PR #58 CI on a cold cache).
- ci-local.sh clippy step now wipes target/**/.fingerprint/clippy-*
  before running, so every workspace crate gets re-linted from scratch
  the same way a fresh CI runner would. Without this, clippy reuses
  cached lint results and silently skips files (which is exactly how
  the lighthouse-plugin warning slipped past the previous local run).

* fix(clippy): use sort_by_key in temps-otel test_support

Replace `sort_by(|a, b| b.start_time.cmp(&a.start_time))` with
`sort_by_key(|s| Reverse(s.start_time))` at lines 378 and 532, satisfying
clippy::unnecessary_sort_by under -D warnings (was failing CI in the
temps-otel test target).
brunyeestudio pushed a commit to brunyeestudio/temps that referenced this pull request Aug 1, 2026
The KV and Blob data endpoints authenticated the caller but never verified
that the client-supplied project_id belonged to them (security review
finding gotempsh#3). For session/API-key auth the project_id was trusted verbatim,
so any authenticated principal could read/write/list/copy/delete another
project's KV entries and blobs by passing an arbitrary id. Deployment
tokens were already constrained; the session/API-key path was the hole.

Route every data-plane handler through the team-based project_access_guard!
(the same ADR-028 mechanism every other project-scoped resource uses):

- KV: fold the guard into the async extract_project_id chokepoint (covers
  all 7 data handlers).
- Blob: fold it into extract_project_id for the body handlers and add an
  explicit authorize_project_access() call to the path-based blob_head and
  blob_download handlers.
- Wire an optional ProjectAccessChecker into KvAppState/BlobAppState from the
  plugin context, and register temps-kv/temps-blob in the ADR-028 coverage
  snapshot.

No-op in plain OSS (no checker registered, matching prior behaviour);
enforced when a team-access plugin is present. Regression tests cover the
denied/allowed/no-checker paths for both data planes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant