Skip to content

fix(security): 0.1.0 hardening pass (9 findings)#95

Open
dviejokfs wants to merge 1 commit into
mainfrom
security/0.1.0-hardening
Open

fix(security): 0.1.0 hardening pass (9 findings)#95
dviejokfs wants to merge 1 commit into
mainfrom
security/0.1.0-hardening

Conversation

@dviejokfs
Copy link
Copy Markdown
Contributor

Summary

Security audit blocker pass for the 0.1.0 release. Addresses 4 CRITICAL + 5 HIGH + 1 MEDIUM findings from a focused security review of the OSS Rust backend.

The audit was scoped to OSS code paths and excluded temps-ee/ and temps-landing/ (a follow-up deeper sweep of SSRF surfaces, EE crates, and the Stripe webhook handler is recommended). Multi-tenancy was intentionally not treated as a finding — OSS is single-tenant by design (no owner_user_id on projects); per-team isolation is the temps-ee teams Premium feature.

Findings fixed

CRITICAL

HIGH

MEDIUM

Out of scope for this PR

Audit recommendations for a follow-up deeper sweep:

  • SSRF on additional surfaces: custom domain verification, git clone targets, OIDC discovery URL, S3 endpoint URL, notification webhook outbound, OG/avatar fetches, import-from-URL
  • EE crates (temps-ee-auth-policy, temps-ee-license-api, temps-ee-teams RBAC enforcement)
  • Stripe webhook signature verification in temps-landing/
  • Zip-slip / path traversal in file upload + deployment artifact extraction
  • Sandbox network egress controls (Docker → host network reachability)

Test plan

  • cargo check --lib clean across all 42 workspace crates
  • cargo test --lib -p temps-agents (new revoke + token tests pass)
  • cargo test --lib -p temps-ai-gateway (145 tests pass, 6 new BYOK SSRF tests)
  • cargo test --lib -p temps-auth (174 tests pass, 7 new client_ip tests)
  • cargo test --lib -p temps-deployments (node validator: 12 IP cases + 2 handler integration tests)
  • cargo test --lib -p temps-git (project_has_signing_token tests pass)
  • Manual: confirm GitLab webhook to a legacy (tokenless) project now returns 200-but-ignored (no deployment trigger)
  • Manual: confirm GitHub App install completes with external_url from settings (set the env var first)
  • Manual: try BYOK request with X-Provider-Base-URL: http://169.254.169.254 → expect 400
  • Manual: register a worker node with private_address: 169.254.169.254 → expect 400

Migration / operator notes

  • GitLab webhook re-enrollment required for legacy projects. Projects with no stored signing token will silently no longer trigger deployments. Operators must delete + recreate the GitLab webhook in GitLab UI to issue a new signing token.
  • GitHub OAuth requires external_url in settings. The OAuth/install callback no longer falls back to Host header or localhost — external_url must be configured before the GitHub App install flow will complete.

Fixes 4 CRITICAL, 5 HIGH, 1 MEDIUM security issues identified in the
0.1.0 release audit. All fixes verified with cargo check and unit tests.

CRITICAL
- agents: bound agent run token expiry to (timeout + 120s) and revoke
  on every run exit (success/failure/cancel/no-changes). Token still
  carries FullAccess permission because the workspace memory endpoints
  reject non-FullAccess deployment tokens at the auth layer; TODO left
  for a purpose-built AgentRunWrite permission in 0.2.0.
- ai-gateway: validate BYOK X-Provider-Base-URL through
  temps_core::url_validation::validate_external_url before handing to
  reqwest. Blocks cloud metadata (169.254.169.254), loopback, RFC-1918,
  link-local, multicast, non-http(s) schemes.
- deployments: replace pg_notify format!() + manual apostrophe-escape
  with Statement::from_sql_and_values bound parameters in
  mark_deployment_complete; document the two remaining call sites
  (hardcoded table identifiers, constant NOTIFY channel) as safe.

HIGH
- git: delete unsigned legacy /webhook/github route and handler. The
  verified github_webhook_events endpoint remains authoritative.
- git: reject GitLab webhooks for projects with no stored signing token
  (was: None => true). Operators must re-enroll legacy projects.
- auth: extract resolve_client_ip into shared module; trust
  X-Forwarded-For only when the direct TCP peer is loopback. Mirrors
  the existing rate-limiter logic so audit logs cannot be spoofed by
  direct-internet clients.
- deployments: validate worker-supplied private_address on node
  registration against reserved ranges (loopback, link-local incl.
  AWS metadata IP, multicast, broadcast, unspecified, IPv6
  equivalents). RFC-1918 and public IPs still allowed.
- git: replace Host-header reflection in GitHub OAuth/install callback
  redirects with state.config_service.get_external_url(). Removes
  open-redirect vector that allowed phishing post-install.

MEDIUM
- ai-gateway: scrub URLs from reqwest::Error via .without_url() in
  From<reqwest::Error> impl so BYOK keys/base_urls cannot leak into
  structured logs via http client failures.

Tests added (40+ new):
- temps-agents: revoke_run_token, run_token_ids map lifecycle
- temps-deployments: revoke_token_by_id, node address validator
  (12 IP cases) + register_node handler integration
- temps-ai-gateway: BYOK SSRF rejection (6 ranges) + accept (3 providers)
- temps-auth: resolve_client_ip (7 cases) + 174 existing tests still pass
- temps-git: project_has_signing_token (3 cases)

Also fixes pre-existing clippy lint (gitlab.rs:265 explicit_auto_deref)
that the pre-commit hook surfaced.

Verified: cargo check --lib clean across all 42 workspace crates.
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