Skip to content

feat(backend): port hexagonal API + multi-cloud providers onto main (layer-C rescue, SvelteKit-aligned)#139

Merged
KooshaPari merged 1 commit into
mainfrom
rescue/layer-c-hexagonal-port
Jun 1, 2026
Merged

feat(backend): port hexagonal API + multi-cloud providers onto main (layer-C rescue, SvelteKit-aligned)#139
KooshaPari merged 1 commit into
mainfrom
rescue/layer-c-hexagonal-port

Conversation

@KooshaPari
Copy link
Copy Markdown
Owner

@KooshaPari KooshaPari commented May 31, 2026

Summary

Surgical path-checkout (NOT a merge) from feat/merge-oct-2025-rearchitecture (PR #10, closed) onto current main. Do NOT merge — review gate only.

  • Ported full hexagonal Go backend (backend/internal/): domain, application use-cases, infrastructure (auth/http/persistence/secrets), DI container
  • Ported complete multi-cloud provider layer (backend/lib/cloud/): Vercel, Railway, Netlify providers + AWS STS + GCP/Azure secret stubs + registry/interfaces
  • Module root: backend/go.mod (module github.com/byteport/api, Go 1.24, aws-sdk-go-v2, GORM, WorkOS, testcontainers)
  • Next.js frontend NOT imported — canonical frontend/web (SvelteKit/Tauri) untouched
  • Pulumi infra NOT ported — stub only in source branch; deferred
  • Zero collisions with main (main touched zero backend/* paths since merge-base)

What was ported (111 files, +31,592 lines)

Layer Path
Domain backend/internal/domain/deployment/
Application use-cases backend/internal/application/deployment/
WorkOS auth service backend/internal/infrastructure/auth/
Credential validator backend/internal/infrastructure/clients/
HTTP handlers + middleware backend/internal/infrastructure/http/
Postgres persistence (GORM) backend/internal/infrastructure/persistence/postgres/
Secrets providers (AWS/GCP/Azure/Vault) backend/internal/infrastructure/secrets/
DI container backend/internal/container/
Multi-cloud providers backend/lib/cloud/ — Vercel/Railway/Netlify + registry + interfaces/types/errors

SvelteKit follow-up items (NOT in this PR)

  • Provider connect flows (Vercel/Railway/Netlify OAuth) — needs SvelteKit pages in frontend/web
  • Deployments dashboard consuming new hexagonal API endpoints
  • WorkOS PKCE auth flow integration in SvelteKit

Build note

go build ./... ran from backend/ (module github.com/byteport/api). Go 1.24 was downloaded on first run. Unit tests standalone; integration tests need Postgres via testcontainers.

Clone strategy fix

HTTP/2 stream cancellations on the 120k-LOC monolithic commit fixed with:
git config http.version HTTP/1.1 && git config http.postBuffer 524288000

Refs: rescue of PR #10; source branch feat/merge-oct-2025-rearchitecture


Note

High Risk
Large new backend footprint touching auth/secrets/cloud in the full PR, dual deployment API paths, and at least one broken test file; wiring and production behavior need careful review before merge.

Overview
Introduces a new backend Go module (github.com/byteport/api, Go 1.24) with Gin, GORM/Postgres, WorkOS, AWS Secrets Manager, Vault, and testcontainers dependencies.

Deployment API (two styles in tree): Root handlers.go adds a legacy Gin surface with in-memory DeploymentStore, provider auto-selection, mocked logs/metrics, and async simulateDeployment. In parallel, internal/ implements hexagonal architecture: a Deployment domain entity (status transitions, services, env vars, cost), repository port, domain service (validation, owner access, cost/provider helpers), application use cases (create, get, list, terminate, update status) with typed ApplicationError responses, Postgres repository wiring, and a Container that injects use cases into DeploymentHandler.

Tests: Broad unit coverage for domain, application errors, use cases, and container wiring. Note application_additional_test.go in the diff appears syntactically invalid (likely non-compiling coverage scaffolding).

This slice of the PR does not show router/main wiring from legacy handlers to the new handler; integration with SvelteKit and real cloud deploys remains follow-up per PR notes.

Reviewed by Cursor Bugbot for commit 2606a1a. Bugbot is set up for automated code reviews on this repo. Configure here.

…layer-C rescue, SvelteKit-aligned)

Surgical path-checkout from feat/merge-oct-2025-rearchitecture onto main.
NO Next.js frontend imported; canonical SvelteKit/Tauri frontend/web retained.

Ported:
- backend/internal/domain/deployment       — domain entities, repository port, service
- backend/internal/application/deployment  — create/get/list/terminate/update-status use-cases + DTOs
- backend/internal/infrastructure/auth     — WorkOS auth service
- backend/internal/infrastructure/clients  — credential validator (AWS/GCP/Azure/SaaS)
- backend/internal/infrastructure/http     — deployment handler + auth middleware
- backend/internal/infrastructure/persistence/postgres — deployment repository (GORM)
- backend/internal/infrastructure/secrets  — AWS/GCP/Azure/Vault secret providers
- backend/internal/container               — DI container
- backend/lib/cloud                        — multi-cloud provider registry + Vercel/Railway/Netlify providers; interfaces, types, errors
- backend/go.mod / go.sum                  — module github.com/byteport/api (Go 1.24, aws-sdk-go-v2, GORM, WorkOS, testcontainers)
- backend/main.go / server.go / handlers.go / types.go / models/ — hexagonal entrypoints

Dropped:
- frontend/web-next (Next.js App Router) — NOT imported; SvelteKit/Tauri stays canonical
- infra/Pulumi — not ported; deferred (stub only in layerc; note as follow-up)
- backend/.history — editor noise excluded

SvelteKit UI follow-ups (not in this PR):
- Provider connect flows (Vercel/Railway/Netlify OAuth) need SvelteKit pages in frontend/web
- Deployments dashboard using new hexagonal API endpoints
- WorkOS PKCE flow integration in SvelteKit auth

Build: go build ./... running (Go 1.24 downloading on first run; integration tests need
Postgres via testcontainers — unit tests run standalone).

Refs: feat/merge-oct-2025-rearchitecture PR #10 (closed, never merged)
Copilot AI review requested due to automatic review settings May 31, 2026 22:52
@gemini-code-assist
Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented May 31, 2026

⚠️ GitGuardian has uncovered 3 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
29483390 Triggered JSON Web Token 2606a1a backend/internal/infrastructure/auth/workos_comprehensive_test.go View secret
29353529 Triggered JSON Web Token 2606a1a backend/internal/infrastructure/auth/workos_service_edge_cases_test.go View secret
29353530 Triggered JSON Web Token 2606a1a backend/internal/infrastructure/auth/workos_service_edge_cases_test.go View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented May 31, 2026

Skipping CodeAnt AI review — this PR changes more than 100 files, which usually means a migration, codemod, or vendored drop. Line-level review on diffs this large produces duplicate findings on the same rewrite pattern and drowns out anything that actually matters.

If you still want a review, comment @codeant-ai : review. For better signal, consider splitting the PR into smaller chunks.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

@socket-security
Copy link
Copy Markdown

@socket-security
Copy link
Copy Markdown

Caution

Review the following alerts detected in dependencies.

According to your organization's Security Policy, you must resolve all "Block" alerts before proceeding. It is recommended to resolve "Warn" alerts too. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Block Critical
Critical CVE: pgx contains memory-safety vulnerability in golang github.com/jackc/pgx/v5

CVE: GHSA-xgrm-4fwx-7qm8 pgx contains memory-safety vulnerability (CRITICAL)

Affected versions: < 5.9.0

Patched version: 5.9.0

From: ?golang/gorm.io/driver/postgres@v1.6.0golang/github.com/testcontainers/testcontainers-go/modules/postgres@v0.39.0golang/github.com/jackc/pgx/v5@v5.6.0

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/jackc/pgx/v5@v5.6.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Block Critical
Critical CVE: Memory-safety vulnerability in golang github.com/jackc/pgx/v5.

CVE: GHSA-9jj7-4m8r-rfcm Memory-safety vulnerability in github.com/jackc/pgx/v5. (CRITICAL)

Affected versions: < 5.9.0

Patched version: 5.9.0

From: ?golang/gorm.io/driver/postgres@v1.6.0golang/github.com/testcontainers/testcontainers-go/modules/postgres@v0.39.0golang/github.com/jackc/pgx/v5@v5.6.0

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/jackc/pgx/v5@v5.6.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Block Critical
Critical CVE: gRPC-Go has an authorization bypass via missing leading slash in :path in golang google.golang.org/grpc

CVE: GHSA-p77j-4mvh-x3m3 gRPC-Go has an authorization bypass via missing leading slash in :path (CRITICAL)

Affected versions: < 1.79.3

Patched version: 1.79.3

From: ?golang/google.golang.org/grpc@v1.75.1

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/google.golang.org/grpc@v1.75.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: Moby has AuthZ plugin bypass when provided oversized request bodies in golang github.com/docker/docker

CVE: GHSA-x744-4wpc-v9h2 Moby has AuthZ plugin bypass when provided oversized request bodies (HIGH)

Affected versions: < 29.3.1

Patched version: 29.3.1

From: ?golang/github.com/testcontainers/testcontainers-go@v0.39.0golang/github.com/docker/docker@v28.3.3+incompatible

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/docker/docker@v28.3.3+incompatible. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: Docker: Race condition in docker cp allows bind mount redirection to host path in golang github.com/docker/docker

CVE: GHSA-rg2x-37c3-w2rh Docker: Race condition in docker cp allows bind mount redirection to host path (HIGH)

Affected versions: <= 28.5.2

Patched version: No patched versions

From: ?golang/github.com/testcontainers/testcontainers-go@v0.39.0golang/github.com/docker/docker@v28.3.3+incompatible

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/docker/docker@v28.3.3+incompatible. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: Docker: PUT /containers/{id}/archive executes container binary on the host in golang `github.com/docker/docker`

CVE: GHSA-x86f-5xw2-fm2r Docker: PUT /containers/{id}/archive executes container binary on the host (HIGH)

Affected versions: <= 28.5.2

Patched version: No patched versions

From: ?golang/github.com/testcontainers/testcontainers-go@v0.39.0golang/github.com/docker/docker@v28.3.3+incompatible

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/docker/docker@v28.3.3+incompatible. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
License policy violation: golang github.com/docker/docker under unrecognized license

License: unrecognized license - This license was not allowed or given any lesser classification by the applicable policy (NOTICE)

From: ?golang/github.com/testcontainers/testcontainers-go@v0.39.0golang/github.com/docker/docker@v28.3.3+incompatible

ℹ Read more on: This package | This alert | What is a license policy violation?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Find a package that does not violate your license policy or adjust your policy to allow this package's license.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/docker/docker@v28.3.3+incompatible. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: Go JOSE Panics in JWE decryption in golang github.com/go-jose/go-jose/v4

CVE: GHSA-78h2-9frx-2jm8 Go JOSE Panics in JWE decryption (HIGH)

Affected versions: < 4.1.4

Patched version: 4.1.4

From: backend/go.modgolang/github.com/go-jose/go-jose/v4@v4.1.1

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/go-jose/go-jose/v4@v4.1.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: OpenTelemetry-Go: multi-value baggage header extraction causes excessive allocations (remote dos amplification) in golang `go.opentelemetry.io/otel`

CVE: GHSA-mh2q-q3fh-2475 OpenTelemetry-Go: multi-value baggage header extraction causes excessive allocations (remote dos amplification) (HIGH)

Affected versions: >= 1.36.0 < 1.41.0

Patched version: 1.41.0

From: ?golang/go.opentelemetry.io/otel@v1.37.0

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/go.opentelemetry.io/otel@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: OpenTelemetry Go SDK Vulnerable to Arbitrary Code Execution via PATH Hijacking in golang go.opentelemetry.io/otel/sdk

CVE: GHSA-9h8m-3fm2-qjrq OpenTelemetry Go SDK Vulnerable to Arbitrary Code Execution via PATH Hijacking (HIGH)

Affected versions: >= 1.21.0 < 1.40.0

Patched version: 1.40.0

From: ?golang/go.opentelemetry.io/otel/sdk@v1.37.0

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/go.opentelemetry.io/otel/sdk@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: opentelemetry-go: BSD kenv command not using absolute path enables PATH hijacking in golang go.opentelemetry.io/otel/sdk

CVE: GHSA-hfvc-g4fc-pqhx opentelemetry-go: BSD kenv command not using absolute path enables PATH hijacking (HIGH)

Affected versions: >= 1.15.0 < 1.43.0

Patched version: 1.43.0

From: ?golang/go.opentelemetry.io/otel/sdk@v1.37.0

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/go.opentelemetry.io/otel/sdk@v1.37.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
High CVE: Non-linear parsing of case-insensitive content in golang golang.org/x/net/html

CVE: GHSA-w32m-9786-jp63 Non-linear parsing of case-insensitive content in golang.org/x/net/html (HIGH)

Affected versions: < 0.33.0

Patched version: 0.33.0

From: ?golang/github.com/gin-gonic/gin@v1.10.0golang/golang.org/x/crypto@v0.40.0golang/github.com/hashicorp/vault/api@v1.22.0golang/golang.org/x/net@v0.27.0

ℹ Read more on: This package | This alert | What is a CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known high severity CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/golang.org/x/net@v0.27.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Deprecated by its maintainer: golang github.com/mitchellh/go-homedir

Reason: Repository has been archived by the owner.

From: ?golang/github.com/hashicorp/vault/api@v1.22.0golang/github.com/mitchellh/go-homedir@v1.1.0

ℹ Read more on: This package | This alert | What is a deprecated package?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/mitchellh/go-homedir@v1.1.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Deprecated by its maintainer: golang github.com/mitchellh/mapstructure

Reason: Repository has been archived by the owner.

From: ?golang/github.com/hashicorp/vault/api@v1.22.0golang/github.com/mitchellh/mapstructure@v1.5.0

ℹ Read more on: This package | This alert | What is a deprecated package?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore golang/github.com/mitchellh/mapstructure@v1.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
10.8% Duplication on New Code (required ≤ 3%)
C Reliability Rating on New Code (required ≥ A)
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 9 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for all 9 issues found in the latest run.

  • ✅ Fixed: Auth context key mismatch
    • getUserUUID now reads user_id and user_info in addition to user_uuid so WorkOS and other middleware keys are recognized.
  • ✅ Resolved by another fix: Client-controlled deployment owner
    • Create flow now binds owner to the authenticated user via the same fix as bug 6.
  • ✅ Fixed: List deployments lacks authorization
    • ListDeployments requires authentication and always scopes results to the caller, rejecting cross-owner owner query params.
  • ✅ Fixed: Owner filter ignores pagination
    • Filtered owner listings now paginate in memory with offset and limit after fetching the caller's deployments.
  • ✅ Fixed: Status list wrong total
    • Total now reflects the count of deployments matching the status filter before pagination.
  • ✅ Fixed: Client sets deployment owner
    • CreateDeploymentUseCase takes authenticatedOwner and ignores client-supplied owner in the JSON body.
  • ✅ Resolved by another fix: List lacks caller scoping
    • Addressed by the same list authorization and caller-scoping changes as bug 3.
  • ✅ Fixed: Async update races readers
    • simulateDeployment now updates status through DeploymentStore.MarkDeployed, which holds the store mutex.
  • ✅ Fixed: Not found returned as 500
    • FindByUUID domain DEPLOYMENT_NOT_FOUND errors are mapped to application NOT_FOUND (404) in get, terminate, and update use cases.

Create PR

Or push these changes by commenting:

@cursor push 7fa2a66522
Preview (7fa2a66522)
diff --git a/backend/handlers.go b/backend/handlers.go
--- a/backend/handlers.go
+++ b/backend/handlers.go
@@ -447,11 +447,5 @@
 func simulateDeployment(store *DeploymentStore, id string) {
 	// Simulate deployment process
 	time.Sleep(3 * time.Second)
-
-	deployment := store.Get(id)
-	if deployment != nil {
-		deployment.Status = "deployed"
-		deployment.UpdatedAt = time.Now()
-		store.Update(deployment)
-	}
+	store.MarkDeployed(id)
 }

diff --git a/backend/internal/application/deployment/application_additional_test.go b/backend/internal/application/deployment/application_additional_test.go
--- a/backend/internal/application/deployment/application_additional_test.go
+++ b/backend/internal/application/deployment/application_additional_test.go
@@ -1,3 +1,5 @@
+//go:build ignore
+
 package deployment
 
 import (

diff --git a/backend/internal/application/deployment/create_deployment.go b/backend/internal/application/deployment/create_deployment.go
--- a/backend/internal/application/deployment/create_deployment.go
+++ b/backend/internal/application/deployment/create_deployment.go
@@ -28,17 +28,18 @@
 func (uc *CreateDeploymentUseCase) Execute(
 	ctx context.Context,
 	req CreateDeploymentRequest,
+	authenticatedOwner string,
 ) (*CreateDeploymentResponse, error) {
 	// Input validation
 	if req.Name == "" {
 		return nil, NewValidationError("deployment name is required")
 	}
-	if req.Owner == "" {
-		return nil, NewValidationError("owner is required")
+	if authenticatedOwner == "" {
+		return nil, NewUnauthorizedError("user authentication required")
 	}
 
 	// Create domain entity
-	dep, err := deployment.NewDeployment(req.Name, req.Owner, req.ProjectUUID)
+	dep, err := deployment.NewDeployment(req.Name, authenticatedOwner, req.ProjectUUID)
 	if err != nil {
 		return nil, NewValidationError(err.Error())
 	}

diff --git a/backend/internal/application/deployment/create_deployment_test.go b/backend/internal/application/deployment/create_deployment_test.go
--- a/backend/internal/application/deployment/create_deployment_test.go
+++ b/backend/internal/application/deployment/create_deployment_test.go
@@ -150,7 +150,7 @@
 		},
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "user-123")
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -195,7 +195,7 @@
 		Owner: "user-123",
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "user-123")
 	
 	if err == nil {
 		t.Fatal("Expected validation error, got nil")
@@ -219,7 +219,7 @@
 	}
 }
 
-// TestCreateDeploymentUseCase_Execute_MissingOwner tests validation error for missing owner
+// TestCreateDeploymentUseCase_Execute_MissingOwner tests unauthorized error without authenticated owner
 func TestCreateDeploymentUseCase_Execute_MissingOwner(t *testing.T) {
 	ctx := context.Background()
 	
@@ -229,14 +229,13 @@
 	useCase := NewCreateDeploymentUseCase(mockRepo, mockService)
 	
 	req := CreateDeploymentRequest{
-		Name:  "test-deployment",
-		Owner: "", // Missing owner
+		Name: "test-deployment",
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "")
 	
 	if err == nil {
-		t.Fatal("Expected validation error, got nil")
+		t.Fatal("Expected unauthorized error, got nil")
 	}
 	
 	if resp != nil {
@@ -248,13 +247,9 @@
 		t.Errorf("Expected ApplicationError, got: %T", err)
 	}
 	
-	if appErr != nil && appErr.Message != "owner is required" {
-		t.Errorf("Expected specific message, got: %s", appErr.Message)
+	if appErr != nil && appErr.Code != "UNAUTHORIZED" {
+		t.Errorf("Expected UNAUTHORIZED code, got: %s", appErr.Code)
 	}
-	
-	if appErr != nil && appErr.Code != "VALIDATION_ERROR" {
-		t.Errorf("Expected VALIDATION_ERROR code, got: %s", appErr.Code)
-	}
 }
 
 // TestCreateDeploymentUseCase_Execute_ValidationError tests domain validation failure
@@ -280,7 +275,7 @@
 		Owner: "user-123",
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "user-123")
 	
 	if err == nil {
 		t.Fatal("Expected conflict error, got nil")
@@ -330,7 +325,7 @@
 		Owner: "user-123",
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "user-123")
 	
 	if err == nil {
 		t.Fatal("Expected internal error, got nil")
@@ -387,7 +382,7 @@
 		EnvVars: envVars,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "user-123")
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -440,7 +435,7 @@
 		EnvVars: nil, // No environment variables
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "user-123")
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -482,7 +477,7 @@
 		ProjectUUID: &projectUUID,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, req, "user-123")
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)

diff --git a/backend/internal/application/deployment/dto.go b/backend/internal/application/deployment/dto.go
--- a/backend/internal/application/deployment/dto.go
+++ b/backend/internal/application/deployment/dto.go
@@ -5,7 +5,7 @@
 // CreateDeploymentRequest represents the input for creating a deployment
 type CreateDeploymentRequest struct {
 	Name        string                 `json:"name" binding:"required"`
-	Owner       string                 `json:"owner" binding:"required"`
+	Owner       string                 `json:"owner,omitempty"`
 	ProjectUUID *string                `json:"project_uuid,omitempty"`
 	EnvVars     map[string]string      `json:"env_vars,omitempty"`
 	Config      map[string]interface{} `json:"config,omitempty"`

diff --git a/backend/internal/application/deployment/errors.go b/backend/internal/application/deployment/errors.go
--- a/backend/internal/application/deployment/errors.go
+++ b/backend/internal/application/deployment/errors.go
@@ -1,7 +1,12 @@
 package deployment
 
-import "fmt"
+import (
+	"errors"
+	"fmt"
 
+	domdeployment "github.com/byteport/api/internal/domain/deployment"
+)
+
 // ApplicationError represents an application-level error
 type ApplicationError struct {
 	Code       string
@@ -81,3 +86,14 @@
 		Err:        err,
 	}
 }
+
+func mapFindByUUIDError(err error) error {
+	if err == nil {
+		return nil
+	}
+	var domainErr *domdeployment.DomainError
+	if errors.As(err, &domainErr) && domainErr.Code == "DEPLOYMENT_NOT_FOUND" {
+		return NewNotFoundError("deployment")
+	}
+	return NewInternalError("failed to retrieve deployment", err)
+}

diff --git a/backend/internal/application/deployment/get_deployment.go b/backend/internal/application/deployment/get_deployment.go
--- a/backend/internal/application/deployment/get_deployment.go
+++ b/backend/internal/application/deployment/get_deployment.go
@@ -40,7 +40,7 @@
 	// Retrieve deployment
 	dep, err := uc.repository.FindByUUID(ctx, uuid)
 	if err != nil {
-		return nil, NewInternalError("failed to retrieve deployment", err)
+		return nil, mapFindByUUIDError(err)
 	}
 	if dep == nil {
 		return nil, NewNotFoundError("deployment")

diff --git a/backend/internal/application/deployment/get_list_test.go b/backend/internal/application/deployment/get_list_test.go
--- a/backend/internal/application/deployment/get_list_test.go
+++ b/backend/internal/application/deployment/get_list_test.go
@@ -93,8 +93,8 @@
 	
 	// Note: The use case wraps repository errors as INTERNAL_ERROR
 	// In production, you'd want proper error handling to distinguish
-	if appErr != nil && appErr.Code != "INTERNAL_ERROR" && appErr.Code != "NOT_FOUND" {
-		t.Errorf("Expected INTERNAL_ERROR or NOT_FOUND code, got: %s", appErr.Code)
+	if appErr.Code != "NOT_FOUND" {
+		t.Errorf("Expected NOT_FOUND code, got: %s", appErr.Code)
 	}
 }
 
@@ -378,21 +378,9 @@
 	deployments := []*deployment.Deployment{dep1, dep2, dep3}
 	
 	mockRepo := &MockRepository{
-		ListFunc: func(ctx context.Context, offset, limit int) ([]*deployment.Deployment, error) {
-			// Simple pagination logic
-			start := offset
-			end := offset + limit
-			if start >= len(deployments) {
-				return []*deployment.Deployment{}, nil
-			}
-			if end > len(deployments) {
-				end = len(deployments)
-			}
-			return deployments[start:end], nil
+		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
+			return deployments, nil
 		},
-		CountFunc: func(ctx context.Context) (int64, error) {
-			return int64(len(deployments)), nil
-		},
 	}
 	
 	useCase := NewListDeploymentsUseCase(mockRepo)
@@ -402,7 +390,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -441,20 +429,9 @@
 	}
 	
 	mockRepo := &MockRepository{
-		ListFunc: func(ctx context.Context, offset, limit int) ([]*deployment.Deployment, error) {
-			start := offset
-			end := offset + limit
-			if start >= len(deployments) {
-				return []*deployment.Deployment{}, nil
-			}
-			if end > len(deployments) {
-				end = len(deployments)
-			}
-			return deployments[start:end], nil
+		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
+			return deployments, nil
 		},
-		CountFunc: func(ctx context.Context) (int64, error) {
-			return int64(len(deployments)), nil
-		},
 	}
 	
 	useCase := NewListDeploymentsUseCase(mockRepo)
@@ -465,7 +442,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -481,7 +458,7 @@
 	
 	// Test second page
 	req.Offset = 10
-	resp2, err := useCase.Execute(ctx, req)
+	resp2, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -493,7 +470,7 @@
 	
 	// Test last page
 	req.Offset = 20
-	resp3, err := useCase.Execute(ctx, req)
+	resp3, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -509,12 +486,9 @@
 	ctx := context.Background()
 	
 	mockRepo := &MockRepository{
-		ListFunc: func(ctx context.Context, offset, limit int) ([]*deployment.Deployment, error) {
+		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
 			return []*deployment.Deployment{}, nil
 		},
-		CountFunc: func(ctx context.Context) (int64, error) {
-			return 0, nil
-		},
 	}
 	
 	useCase := NewListDeploymentsUseCase(mockRepo)
@@ -524,7 +498,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -547,16 +521,10 @@
 func TestListDeploymentsUseCase_Execute_DefaultLimit(t *testing.T) {
 	ctx := context.Background()
 	
-	var actualLimit int
-	
 	mockRepo := &MockRepository{
-		ListFunc: func(ctx context.Context, offset, limit int) ([]*deployment.Deployment, error) {
-			actualLimit = limit
+		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
 			return []*deployment.Deployment{}, nil
 		},
-		CountFunc: func(ctx context.Context) (int64, error) {
-			return 0, nil
-		},
 	}
 	
 	useCase := NewListDeploymentsUseCase(mockRepo)
@@ -567,15 +535,14 @@
 		Limit:  0,
 	}
 	
-	_, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
 	}
 	
-	// Check that a default limit was applied
-	if actualLimit == 0 {
-		t.Error("Expected default limit to be applied, got 0")
+	if resp.Limit != 50 {
+		t.Errorf("Expected default limit 50, got %d", resp.Limit)
 	}
 }
 
@@ -583,16 +550,10 @@
 func TestListDeploymentsUseCase_Execute_MaxLimit(t *testing.T) {
 	ctx := context.Background()
 	
-	var actualLimit int
-	
 	mockRepo := &MockRepository{
-		ListFunc: func(ctx context.Context, offset, limit int) ([]*deployment.Deployment, error) {
-			actualLimit = limit
+		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
 			return []*deployment.Deployment{}, nil
 		},
-		CountFunc: func(ctx context.Context) (int64, error) {
-			return 0, nil
-		},
 	}
 	
 	useCase := NewListDeploymentsUseCase(mockRepo)
@@ -603,15 +564,14 @@
 		Limit:  10000,
 	}
 	
-	_, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
 	}
 	
-	// Check that the limit was capped at maximum
-	if actualLimit > 100 {
-		t.Errorf("Expected limit to be capped at 100, got %d", actualLimit)
+	if resp.Limit != 100 {
+		t.Errorf("Expected limit to be capped at 100, got %d", resp.Limit)
 	}
 }
 
@@ -651,7 +611,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -679,22 +639,17 @@
 	ctx := context.Background()
 	
 	dep1, _ := deployment.NewDeployment("deployment-1", "user-123", nil)
-	dep1.SetStatus(deployment.StatusDeployed)
+	_ = dep1.SetStatus(deployment.StatusDetecting)
+	_ = dep1.SetStatus(deployment.StatusProvisioning)
+	_ = dep1.SetStatus(deployment.StatusDeploying)
+	_ = dep1.SetStatus(deployment.StatusDeployed)
+	dep2, _ := deployment.NewDeployment("deployment-2", "user-123", nil)
+	ownerDeployments := []*deployment.Deployment{dep1, dep2}
 	
-	var requestedStatus deployment.Status
-	deployedDeployments := []*deployment.Deployment{dep1}
-	
 	mockRepo := &MockRepository{
-		FindByStatusFunc: func(ctx context.Context, status deployment.Status) ([]*deployment.Deployment, error) {
-			requestedStatus = status
-			if status == deployment.StatusDeployed {
-				return deployedDeployments, nil
-			}
-			return []*deployment.Deployment{}, nil
+		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
+			return ownerDeployments, nil
 		},
-		CountFunc: func(ctx context.Context) (int64, error) {
-			return 1, nil
-		},
 	}
 	
 	useCase := NewListDeploymentsUseCase(mockRepo)
@@ -706,7 +661,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error, got: %v", err)
@@ -716,10 +671,6 @@
 		t.Fatal("Expected response, got nil")
 	}
 	
-	if requestedStatus != deployment.StatusDeployed {
-		t.Errorf("Expected status filter 'deployed', got %s", requestedStatus)
-	}
-	
 	if len(resp.Deployments) != 1 {
 		t.Errorf("Expected 1 deployment, got %d", len(resp.Deployments))
 	}
@@ -743,7 +694,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err == nil {
 		t.Fatal("Expected validation error for invalid status, got nil")
@@ -774,9 +725,6 @@
 		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
 			return userDeployments, nil
 		},
-		CountByOwnerFunc: func(ctx context.Context, owner string) (int64, error) {
-			return 0, fmt.Errorf("repository failed") // Simulate count error
-		},
 	}
 	
 	useCase := NewListDeploymentsUseCase(mockRepo)
@@ -788,7 +736,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err != nil {
 		t.Fatalf("Expected no error despite count failure, got: %v", err)
@@ -813,7 +761,7 @@
 	ctx := context.Background()
 	
 	mockRepo := &MockRepository{
-		ListFunc: func(ctx context.Context, offset, limit int) ([]*deployment.Deployment, error) {
+		FindByOwnerFunc: func(ctx context.Context, owner string) ([]*deployment.Deployment, error) {
 			return nil, fmt.Errorf("repository failed") // Simulate repository error
 		},
 	}
@@ -825,7 +773,7 @@
 		Limit:  10,
 	}
 	
-	resp, err := useCase.Execute(ctx, req)
+	resp, err := useCase.Execute(ctx, "user-123", req)
 	
 	if err == nil {
 		t.Fatal("Expected repository error, got nil")

diff --git a/backend/internal/application/deployment/list_deployments.go b/backend/internal/application/deployment/list_deployments.go
--- a/backend/internal/application/deployment/list_deployments.go
+++ b/backend/internal/application/deployment/list_deployments.go
@@ -21,8 +21,16 @@
 // Execute lists deployments based on filters
 func (uc *ListDeploymentsUseCase) Execute(
 	ctx context.Context,
+	userUUID string,
 	req ListDeploymentsRequest,
 ) (*ListDeploymentsResponse, error) {
+	if userUUID == "" {
+		return nil, NewUnauthorizedError("user authentication required")
+	}
+	if req.Owner != "" && req.Owner != userUUID {
+		return nil, NewForbiddenError("cannot list deployments for another owner")
+	}
+
 	// Set defaults
 	if req.Limit <= 0 {
 		req.Limit = 50
@@ -30,39 +38,32 @@
 	if req.Limit > 100 {
 		req.Limit = 100
 	}
+	if req.Offset < 0 {
+		req.Offset = 0
+	}
 
-	var deployments []*deployment.Deployment
-	var err error
+	deployments, err := uc.repository.FindByOwner(ctx, userUUID)
+	if err != nil {
+		return nil, NewInternalError("failed to list deployments", err)
+	}
 
-	// Apply filters
-	if req.Owner != "" {
-		deployments, err = uc.repository.FindByOwner(ctx, req.Owner)
-	} else if req.Status != "" {
+	if req.Status != "" {
 		status := deployment.Status(req.Status)
 		if !status.IsValid() {
 			return nil, NewValidationError("invalid status value")
 		}
-		deployments, err = uc.repository.FindByStatus(ctx, status)
-	} else {
-		deployments, err = uc.repository.List(ctx, req.Offset, req.Limit)
+		filtered := make([]*deployment.Deployment, 0, len(deployments))
+		for _, dep := range deployments {
+			if dep.Status() == status {
+				filtered = append(filtered, dep)
+			}
+		}
+		deployments = filtered
 	}
 
-	if err != nil {
-		return nil, NewInternalError("failed to list deployments", err)
-	}
+	total := int64(len(deployments))
+	deployments = paginateDeployments(deployments, req.Offset, req.Limit)
 
-	// Get total count
-	var total int64
-	if req.Owner != "" {
-		total, err = uc.repository.CountByOwner(ctx, req.Owner)
-	} else {
-		total, err = uc.repository.Count(ctx)
-	}
-	if err != nil {
-		// Don't fail if count fails, just log it
-		total = int64(len(deployments))
-	}
-
 	// Map to response DTOs
 	summaries := make([]DeploymentSummaryDTO, 0, len(deployments))
 	for _, dep := range deployments {
@@ -88,3 +89,14 @@
 
 	return response, nil
 }
+
+func paginateDeployments(deployments []*deployment.Deployment, offset, limit int) []*deployment.Deployment {
+	if offset >= len(deployments) {
+		return []*deployment.Deployment{}
+	}
+	end := offset + limit
+	if end > len(deployments) {
+		end = len(deployments)
+	}
+	return deployments[offset:end]
+}

diff --git a/backend/internal/application/deployment/terminate_deployment.go b/backend/internal/application/deployment/terminate_deployment.go
--- a/backend/internal/application/deployment/terminate_deployment.go
+++ b/backend/internal/application/deployment/terminate_deployment.go
@@ -41,7 +41,7 @@
 	// Retrieve deployment
 	dep, err := uc.repository.FindByUUID(ctx, uuid)
 	if err != nil {
-		return nil, NewInternalError("failed to retrieve deployment", err)
+		return nil, mapFindByUUIDError(err)
 	}
 	if dep == nil {
 		return nil, NewNotFoundError("deployment")

diff --git a/backend/internal/application/deployment/update_status.go b/backend/internal/application/deployment/update_status.go
--- a/backend/internal/application/deployment/update_status.go
+++ b/backend/internal/application/deployment/update_status.go
@@ -50,7 +50,7 @@
 	// Retrieve deployment
 	dep, err := uc.repository.FindByUUID(ctx, uuid)
 	if err != nil {
-		return NewInternalError("failed to retrieve deployment", err)
+		return mapFindByUUIDError(err)
 	}
 	if dep == nil {
 		return NewNotFoundError("deployment")

diff --git a/backend/internal/infrastructure/http/handlers/create_test.go b/backend/internal/infrastructure/http/handlers/create_test.go
--- a/backend/internal/infrastructure/http/handlers/create_test.go
+++ b/backend/internal/infrastructure/http/handlers/create_test.go
@@ -14,7 +14,7 @@
 )
 
 func TestCreateDeployment_Success(t *testing.T) {
-	router := setupTestRouter()
+	router := setupAuthenticatedRouter("test-owner")
 	handler, repo, svc := setupTestHandler()
 
 	svc.On("ValidateDeployment", mock.Anything, mock.Anything).Return(nil)
@@ -62,7 +62,7 @@
 }
 
 func TestCreateDeployment_RepositoryError(t *testing.T) {
-	router := setupTestRouter()
+	router := setupAuthenticatedRouter("owner")
 	handler, repo, svc := setupTestHandler()
 
 	svc.On("ValidateDeployment", mock.Anything, mock.Anything).Return(nil)

diff --git a/backend/internal/infrastructure/http/handlers/deployment_handler.go b/backend/internal/infrastructure/http/handlers/deployment_handler.go
--- a/backend/internal/infrastructure/http/handlers/deployment_handler.go
+++ b/backend/internal/infrastructure/http/handlers/deployment_handler.go
@@ -4,6 +4,7 @@
 	"net/http"
 
 	"github.com/byteport/api/internal/application/deployment"
+	"github.com/byteport/api/internal/infrastructure/auth"
 	"github.com/gin-gonic/gin"
 )
 
@@ -65,7 +66,16 @@
 		return
 	}
 
-	response, err := h.createUseCase.Execute(c.Request.Context(), req)
+	userUUID := getUserUUID(c)
+	if userUUID == "" {
+		c.JSON(http.StatusUnauthorized, ErrorResponse{
+			Error: "user authentication required",
+			Code:  "UNAUTHORIZED",
+		})
+		return
+	}
+
+	response, err := h.createUseCase.Execute(c.Request.Context(), req, userUUID)
 	if err != nil {
 		handleApplicationError(c, err)
 		return
@@ -118,7 +128,16 @@
 		return
 	}
 
-	response, err := h.listUseCase.Execute(c.Request.Context(), req)
+	userUUID := getUserUUID(c)
+	if userUUID == "" {
+		c.JSON(http.StatusUnauthorized, ErrorResponse{
+			Error: "user authentication required",
+			Code:  "UNAUTHORIZED",
+		})
+		return
+	}
+
+	response, err := h.listUseCase.Execute(c.Request.Context(), userUUID, req)
 	if err != nil {
 		handleApplicationError(c, err)
 		return
@@ -208,11 +227,20 @@
 
 // getUserUUID extracts user UUID from context (set by auth middleware)
 func getUserUUID(c *gin.Context) string {
-	// This would be set by authentication middleware
 	if userUUID, exists := c.Get("user_uuid"); exists {
-		if uuid, ok := userUUID.(string); ok {
+		if uuid, ok := userUUID.(string); ok && uuid != "" {
 			return uuid
 		}
 	}
+	if userID, exists := c.Get("user_id"); exists {
+		if id, ok := userID.(string); ok && id != "" {
+			return id
+		}
+	}
+	if userInfo, exists := c.Get("user_info"); exists {
+		if info, ok := userInfo.(*auth.UserInfo); ok && info.ID != "" {
+			return info.ID
+		}
+	}
 	return ""
 }

diff --git a/backend/internal/infrastructure/http/handlers/list_test.go b/backend/internal/infrastructure/http/handlers/list_test.go
--- a/backend/internal/infrastructure/http/handlers/list_test.go
+++ b/backend/internal/infrastructure/http/handlers/list_test.go
@@ -12,16 +12,17 @@
 	"github.com/stretchr/testify/mock"
 )
 
+const testListUser = "test-user"
+
 func TestListDeployments_Success(t *testing.T) {
-	router := setupTestRouter()
+	router := setupAuthenticatedRouter(testListUser)
 	handler, repo, _ := setupTestHandler()
 
-	dep1, _ := domain.NewDeployment("dep-1", "owner-1", nil)
-	dep2, _ := domain.NewDeployment("dep-2", "owner-1", nil)
+	dep1, _ := domain.NewDeployment("dep-1", testListUser, nil)
+	dep2, _ := domain.NewDeployment("dep-2", testListUser, nil)
 	deployments := []*domain.Deployment{dep1, dep2}
 
-	repo.On("List", mock.Anything, 0, 10).Return(deployments, nil)
-	repo.On("Count", mock.Anything).Return(int64(2), nil)
+	repo.On("FindByOwner", mock.Anything, testListUser).Return(deployments, nil)
 
 	router.GET("/deployments", handler.ListDeployments)
 
@@ -31,7 +32,7 @@
 	router.ServeHTTP(w, req)
 
 	assert.Equal(t, http.StatusOK, w.Code)
-	
+
 	var response map[string]interface{}
 	err := json.Unmarshal(w.Body.Bytes(), &response)
 	assert.NoError(t, err)
@@ -41,12 +42,11 @@
 }
 
 func TestListDeployments_EmptyResult(t *testing.T) {
-	router := setupTestRouter()
+	router := setupAuthenticatedRouter(testListUser)
 	handler, repo, _ := setupTestHandler()
 
 	deployments := []*domain.Deployment{}
-	repo.On("List", mock.Anything, 0, 10).Return(deployments, nil)
-	repo.On("Count", mock.Anything).Return(int64(0), nil)
+	repo.On("FindByOwner", mock.Anything, testListUser).Return(deployments, nil)
 
 	router.GET("/deployments", handler.ListDeployments)
 
@@ -56,7 +56,7 @@
 	router.ServeHTTP(w, req)
 
 	assert.Equal(t, http.StatusOK, w.Code)
-	
+
 	var response map[string]interface{}
 	err := json.Unmarshal(w.Body.Bytes(), &response)
 	assert.NoError(t, err)
@@ -66,7 +66,7 @@
 }
 
 func TestListDeployments_InvalidQuery(t *testing.T) {
-	router := setupTestRouter()
+	router := setupAuthenticatedRouter(testListUser)
... diff truncated: showing 800 of 954 lines

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

return uuid
}
}
return ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth context key mismatch

High Severity

getUserUUID only reads user_uuid from the Gin context, but the protected routes use lib.AuthMiddleware(), which stores the authenticated user under user, and the WorkOS middleware sets user_id instead. Get, terminate, and status update therefore see an empty user and respond with unauthorized even when the caller passed valid auth.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

return
}

c.JSON(http.StatusCreated, response)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client-controlled deployment owner

High Severity

Create deployment binds owner from the JSON body and persists it without tying ownership to the authenticated user. Any logged-in caller can register deployments under another user’s ID and pass subsequent ownership checks for that resource.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

deployments, err = uc.repository.FindByStatus(ctx, status)
} else {
deployments, err = uc.repository.List(ctx, req.Offset, req.Limit)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List deployments lacks authorization

High Severity

Listing deployments never checks the caller’s identity. Unfiltered queries return every deployment in the database, and the owner query parameter can target any user without proving the requester is that owner or an admin.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

deployments, err = uc.repository.FindByStatus(ctx, status)
} else {
deployments, err = uc.repository.List(ctx, req.Offset, req.Limit)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Owner filter ignores pagination

Medium Severity

When owner or status query params are set, Execute loads the full result set via FindByOwner or FindByStatus and never applies offset or limit, while the response still advertises pagination fields from the request.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

total, err = uc.repository.CountByOwner(ctx, req.Owner)
} else {
total, err = uc.repository.Count(ctx)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status list wrong total

Medium Severity

Listing with a status filter still sets total from Count(ctx), the global deployment count, not the number of rows matching that status.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

}

// Create domain entity
dep, err := deployment.NewDeployment(req.Name, req.Owner, req.ProjectUUID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client sets deployment owner

High Severity

CreateDeploymentUseCase persists req.Owner from the JSON body without binding it to the authenticated user, so any caller who can POST can create deployments owned by another user id.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

deployments, err = uc.repository.FindByStatus(ctx, status)
} else {
deployments, err = uc.repository.List(ctx, req.Offset, req.Limit)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List lacks caller scoping

High Severity

ListDeploymentsUseCase never receives or validates the requesting user. Unfiltered lists return all deployments, and the owner query param can target any owner without proving the caller is that owner.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

Comment thread backend/handlers.go
deployment.Status = "deployed"
deployment.UpdatedAt = time.Now()
store.Update(deployment)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async update races readers

Medium Severity

simulateDeployment mutates deployment.Status and UpdatedAt on a pointer returned from Get before Update, without holding DeploymentStore’s mutex, while HTTP handlers can read the same struct concurrently.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

dep, err := uc.repository.FindByUUID(ctx, uuid)
if err != nil {
return nil, NewInternalError("failed to retrieve deployment", err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not found returned as 500

Medium Severity

Any non-nil error from FindByUUID is mapped to NewInternalError, so domain-style not-found errors (e.g. NewDeploymentNotFoundError) surface as HTTP 500 instead of 404.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2606a1a. Configure here.

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented May 31, 2026

Code Review Summary

Status: Critical Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 3
Issue Details (click to expand)

CRITICAL

File Line Issue
backend/internal/application/deployment/application_additional_test.go 1-399 File contains numerous syntax errors: undefined types/variables (gorm, models, TestDeploymentRepository, repo), mismatched braces, invalid assertions (assert.Equal(t, "code", err.Code) uses wrong argument order), duplicate function definitions (setupTestDB defined twice), and will not compile. This blocks go build ./...

WARNING

File Line Issue
backend/internal/infrastructure/http/middleware/auth.go 152-155 validateTokenLegacy function returns placeholder UUID without actual validation — security vulnerability for production use
backend/internal/infrastructure/secrets/manager.go 191-198 SetSecret error handling may leak sensitive credential info in error messages (fmt.Errorf with update and create errors)
backend/handlers.go + backend/internal/container/container.go architecture Dual deployment API paths exist: legacy handlers.go with in-memory DeploymentStore and hexagonal internal/ with separate DeploymentHandler. No wiring exists between legacy handlers and new hexagonal handlers
Other Observations (not in inline comment scope)
File Line Issue
backend/internal/infrastructure/auth/workos_service.go 196 HTTP call to fetch JWKS has no explicit timeout context – relies on default http.Get which could hang
backend/internal/domain/deployment/deployment.go 16 Private fields (lowercase) with public getters pattern works but differs from GORM models in backend/models/ which use public fields directly – API inconsistency
backend/lib/cloud/ auth Provider credentials stored in Credentials.Data map[string]string – recommend encrypting sensitive values before storage
Files Reviewed (12 files)
  • backend/go.mod – 151 lines, standard dependencies
  • backend/main.go – 66 lines, clean entry point
  • backend/handlers.go – 457 lines, legacy Gin handlers with in-memory store
  • backend/models/deployments.go – 154 lines, GORM models
  • backend/internal/container/container.go – 94 lines, DI container
  • backend/internal/domain/deployment/deployment.go – 328 lines, domain entity
  • backend/internal/infrastructure/secrets/manager.go – 719 lines, secrets manager
  • backend/internal/infrastructure/auth/workos_service.go – 447 lines, WorkOS auth
  • backend/internal/infrastructure/http/middleware/auth.go – 197 lines, middleware
  • backend/internal/application/deployment/errors.go – 83 lines, error types
  • backend/lib/cloud/interfaces.go – 292 lines, provider interfaces
  • backend/lib/cloud/provider_vercel.go – 531 lines, Vercel provider
  • backend/lib/cloud/errors.go – 394 lines, error handling
  • backend/internal/application/deployment/application_additional_test.go – 399 lines, SYNTACTICALLY INVALID

Reviewed by laguna-m.1-20260312:free · 1,586,181 tokens

"github.com/stretchr/testify/require"
)

func TestApplicationDeployment_ApplyUncoveredLines_4_4_16(t *testing.T) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: This test function has malformed brace structure — the t.Run block opens with { but the closing } is missing before the next t.Run on line 49. This will cause compilation failure.

// Test DeploymentRepository.Delete with non-existent deployment (line 126-128 coverage coverage)

// Test DeploymentRepository.Delete with nil GORM repository
mockRepo := &TestDeploymentRepository{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: mockPostgresConnection(t) is used but this function is not defined or imported in this file. Same for models.Deployment — missing import or incorrect package reference.

Status: "running",
}
db.Create(&deployment)
defer mockDB.Close()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: defer mockDB.Close() references undefined variable mockDB — should be db. Also missing import for gorm package used on line 32.

db.Create(&deployment)
defer mockDB.Close()

err := repo.Delete(deployment.UUID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: repo is undefined — should be mockRepo or properly assigned before use. Function body references undefined types/variables throughout.

assert.NoError(t, err)

// Verify the update was recorded
updatedDeployment, err := repo.GetById(deployment.UUID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Variable shadowing issue — updatedDeployment is declared on line 59 but redeclared with := on line 69. Also line 70 has assert.Equal(t, updatedDeployment.Name, updatedDeployment.Name) which compares a value to itself.


// Create test data to simulate user records
legacyUsers := []models.Deployment{
User{"name": "User "+i, "user"+i, "test-aws-access-key", "test-"+i},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Invalid struct literal syntax — User{...} should be models.Deployment{...}. Go struct literals require explicit type names, not inline maps.

}

assert.Equal(t, "test_category", err.Category)
assert.Equal(t, err.Code, err.Code)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: assert.Equal(t, err.Code, err.Code) compares field to itself — should be err.Code vs expected value. Same issue on lines 163-165.

{"resourceid": "resource123", "req123", "network_error"},
}

for _, test := range(tests) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: for _, test := range(tests) has incorrect syntax — should be for _, test := range tests (no parentheses around tests).

Timestamp: time.Now(),
}

jsonBytes, err := json.Marshal(err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: jsonBytes is []byte, not a map — cannot index with string keys. Lines 213-217 will not compile.

}

// Helper function to setup test database
func setupTestDB(t *testing.T) *gorm.DB {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Duplicate parameter name func TestCloudError_Serialization(...) — function already defined on line 147. Go does not allow duplicate declarations in same package.

SecretString: aws.String(value),
})
if createErr != nil {
return fmt.Errorf("failed to set secret in AWS '%s': update=%v, create=%v", key, err, createErr)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Error message includes both update and create error details which may expose sensitive credential information in logs. Consider logging only generic messages in production.

// getWorkOSPublicKey fetches the public key for JWT verification
func (w *WorkOSAuthService) getWorkOSPublicKey(ctx context.Context, kid string) (*rsa.PublicKey, error) {
// Fetch JWKS from WorkOS
resp, err := httpGet("https://api.workos.com/.well-known/jwks.json")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: HTTP GET to WorkOS JWKS endpoint has no explicit timeout — relies on default http.Get. Consider using httpGetWithContext that applies a timeout context to prevent hanging on network issues.

}

// validateTokenLegacy provides the original placeholder validation
func validateTokenLegacy(token string) (string, error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: validateTokenLegacy returns a hardcoded placeholder UUID without any actual token validation. This is a security vulnerability if used in production environments.

@KooshaPari KooshaPari merged commit cb81ebe into main Jun 1, 2026
17 of 31 checks passed
@KooshaPari KooshaPari deleted the rescue/layer-c-hexagonal-port branch June 1, 2026 00:59
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.

2 participants