Skip to content

Chore/security OSV triage safe upgrades#95

Merged
nazarli-shabnam merged 11 commits into
mainfrom
chore/security-osv-triage-safe-upgrades
Jun 23, 2026
Merged

Chore/security OSV triage safe upgrades#95
nazarli-shabnam merged 11 commits into
mainfrom
chore/security-osv-triage-safe-upgrades

Conversation

@nazarli-shabnam

@nazarli-shabnam nazarli-shabnam commented May 1, 2026

Copy link
Copy Markdown
Member

This pull request updates both backend and frontend dependencies to address security, compatibility, and stability. The backend (api) receives minor Go version and dependency upgrades, while the frontend (ui) updates several npm packages, including important libraries like axios and vite. These changes help ensure the project stays current with upstream improvements and security patches.

Backend (Go) dependency and environment updates:

  • Updated Go version in both api/go.mod and api/Dockerfile from 1.25.5/1.25-alpine to 1.25.9/1.25.9-alpine, ensuring the latest minor release is used. [1] [2]
  • Upgraded several Go dependencies for improved stability and security, including github.com/jackc/pgx/v5 (v5.6.0 → v5.9.2), github.com/quic-go/qpack (v0.5.1 → v0.6.0), and github.com/quic-go/quic-go (v0.54.0 → v0.57.0). [1] [2]

Frontend (npm) dependency updates:

  • Upgraded axios from ^1.13.5 to ^1.15.2 in both ui/package.json and ui/package-lock.json, including its dependency proxy-from-env and follow-redirects, to address potential vulnerabilities and bugs. [1] [2] [3] [4] [5]
  • Upgraded vite from ^7.3.1 to ^7.3.2 for the build system, which may include bug fixes and performance improvements. [1] [2] [3]

Other npm dependency bumps:

  • Updated postcss (8.5.6 → 8.5.12) and brace-expansion (multiple minor version bumps), which are indirect dependencies, to pull in upstream fixes and improvements. [1] [2] [3]

Summary by CodeRabbit

  • Security / Authorization
    • Added instance-admin gated access for instance settings and instance admin management; non-admins receive 403. First user is seeded as instance admin during setup; admins can list/add/remove (including protection against removing the last admin).
    • Strengthened workspace/project and issue-level authorization across attachments, issue views, modules, cycles, GitHub syncs, and app installation/uninstallation.
    • Password change/reset now evicts sessions (optionally preserving the current session); OAuth now requires verified emails.
    • Production config now fails closed if required secrets are missing.
  • Frontend Security
    • Added HTML and URL sanitization for rich content and rendered links.
  • New Features / UX
    • Added an “Admins” UI for managing instance administrators.
  • Chores / Tests
    • Updated Go/Vite dependencies and expanded auth/authorization and secret-handling tests.

@nazarli-shabnam nazarli-shabnam added this to the Deadline milestone May 1, 2026
@nazarli-shabnam nazarli-shabnam self-assigned this May 1, 2026
Copilot AI review requested due to automatic review settings May 1, 2026 14:32
@nazarli-shabnam nazarli-shabnam added enhancement New feature or request Vulnerability labels May 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates backend and frontend dependencies to incorporate security/stability fixes and keep the project aligned with upstream patch releases.

Changes:

  • Bumps Go toolchain image/tag and refreshes Go module dependencies (pgx, quic-go, etc.).
  • Updates UI dependencies including axios and vite, with corresponding package-lock.json refresh.
  • Pulls in various indirect dependency updates (e.g., postcss, brace-expansion, follow-redirects, proxy-from-env).

Reviewed changes

Copilot reviewed 3 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
api/go.mod Updates Go version directive and several indirect module versions.
api/go.sum Updates module checksums to match refreshed dependencies.
api/Dockerfile Bumps Go builder image patch version.
ui/package.json Updates direct npm dependency versions (axios, vite).
ui/package-lock.json Updates resolved npm dependency graph for the bumped packages.
Files not reviewed (1)
  • ui/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/go.mod Outdated

@nazarli-shabnam nazarli-shabnam left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@nazarli-shabnam nazarli-shabnam requested a review from a team May 1, 2026 15:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 5 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • ui/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Rafetikus Rafetikus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Well done!

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements comprehensive security hardening across the platform: session eviction on password operations to protect compromised accounts, instance-admin authorization for settings endpoints with secret sanitization to prevent plaintext leakage, email verification enforcement in OAuth providers, role-based access control tightened across workspace/project/integration services, cross-workspace issue validation in module and cycle operations, frontend XSS/URL injection defenses through HTML and URL sanitization, and a new instance admin management interface. Dependency versions are also updated (Go 1.26.4, pgx, quic-go, DOMPurify, Vite).

Changes

Security Hardening and Authorization Enforcement

Layer / File(s) Summary
Dependency and infrastructure updates
api/Dockerfile, api/go.mod, ui/package.json
Go base image pinned to golang:1.26.4-alpine, go.mod Go directive raised to 1.26.4 with indirect dependency updates (pgx/v5 v5.6.0→v5.9.2, quic-go v0.54.0→v0.59.1, removal of go.uber.org/mock, golang.org/x/mod, golang.org/x/tools). DOMPurify (^3.2.7) added to UI dependencies, Vite bumped to ^7.3.2.
Role level constants for authorization
api/internal/model/workspace.go
Adds RoleGuest=5, RoleMember=10, RoleAdmin=15, RoleOwner=20 constants for role-based authorization across services.
Instance admin data model and persistence
api/internal/model/instance_admin.go, api/internal/store/instance_admin.go, api/migrations/000006_instance_admins.*, api/internal/testutil/factory.go
New InstanceAdmin GORM model with soft-delete support and join fields. InstanceAdminStore provides CRUD and role-checking methods. Migration makes instance_id nullable, adds soft-delete deleted_at column, and creates unique partial index on active users. Test helper SeedInstanceAdmin creates fixtures for authorization testing.
Session eviction on password operations
api/internal/auth/service.go, api/internal/handler/auth.go, api/internal/store/session.go
ChangePassword, ResetPassword, and SetPassword now evict all user sessions except the caller's (via new keepSessionKey parameter). SessionStore gains DeleteByUserID and DeleteByUserIDExcept methods for JSONB-based session deletion by user ID.
Production configuration and secret validation
api/internal/config/config.go
Load() enforces MAGIC_CODE_SECRET and INSTANCE_ENCRYPTION_KEY in non-development environments (fail-closed).
Instance settings authorization and secret sanitization
api/internal/handler/instance.go, api/internal/handler/instance_test.go, api/internal/handler/oauth_config.go, api/internal/router/router.go
InstanceSettingsHandler extended with Admins dependency. GetSettings and UpdateSetting require instance admin access with fail-closed authorization. Secret sanitization refactored: decryptSectionSecretsInternal decrypts configured secret fields per section. InstanceSetup optionally creates initial instance admin for first user. Admin management endpoints (ListAdmins, AddAdmin, RemoveAdmin) with admin-only gating. OAuth settings decryption switched to internal path. Tests validate authorization, secret visibility, and admin CRUD operations.
OAuth provider email verification
api/internal/oauth/oauth.go, api/internal/oauth/github.go, api/internal/oauth/google.go
GitHub OAuth now always resolves verified email via /user/emails (rejecting unverified). Google OAuth rejects unverified accounts. New ErrEmailNotVerified error constant tracks verification failures.
Workspace authorization and membership role enforcement
api/internal/service/workspace.go
Workspace mutations now require admin. UpdateMemberRole enforces admin gate, prevents owner demotion by non-owners, caps granted roles to caller's level, restricts Owner grants to owner. DeleteMember requires admin and prevents owner removal. Invites gated to admins with role ceiling enforcement.
Project authorization with admin role enforcement
api/internal/service/project.go
New requireProjectAdmin helper gates project mutations (update, delete, member/invite operations) to workspace admins/owners or project RoleAdmin+ members.
GitHub integration authorization
api/internal/service/integration.go, api/internal/service/github_sync.go
InstallGitHub requires admin instead of membership. Re-pointing installations to different workspaces rejected with ErrInstallationAlreadyLinked. UpdateSync/DeleteSync gain admin authorization checks.
Cross-workspace issue validation in service operations
api/internal/service/module.go, api/internal/service/cycle.go, api/internal/service/attachment.go, api/internal/service/issue_view.go
Module and Cycle services gain optional IssueStore validation: AddModuleIssue/AddCycleIssue reject issues not belonging to target project. Attachment operations enforce issue-level access. IssueViewService validates view's workspace matches request. Router wires issueStore to cycleSvc and moduleSvc.
Frontend HTML and URL sanitization
ui/src/lib/sanitize.ts, ui/src/pages/EpicDetailPage.tsx, ui/src/pages/IssueDetailPage.tsx, ui/src/pages/PageDetailPage.tsx
New sanitization module exports sanitizeHtml (DOMPurify-backed, handles null) and safeUrl (validates links, allows site-relative paths and http:/https:/mailto:, returns # for invalid). Pages updated to sanitize comment HTML and link URLs before rendering.
Instance admin management UI and routing
ui/src/api/types.ts, ui/src/components/layout/InstanceAdminLayout.tsx, ui/src/pages/instance-admin/InstanceAdminAdminsPage.tsx, ui/src/pages/instance-admin/index.ts, ui/src/routes/index.tsx, ui/src/services/instanceService.ts, ui/src/services/index.ts
New InstanceAdminAdminsPage React component manages instance administrators with list, add, and remove workflows. API response type and service methods for listing/adding/removing admins. Layout sidebar adds "Admins" section with icon. Routes and services updated for new admin management feature with loading states, error handling, and single-admin removal protection.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐇 Sessions evict when passwords change,
Admins guard the settings range,
Verified emails, cross-workspace walls,
HTML scrubbed in API calls—
Security hardened from core to display! 🔒✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. Required sections like 'Type of change', 'Surface', 'What changed', 'Database / migrations', 'Breaking changes', and 'Test plan' from the template are entirely missing or not properly filled out. Complete all required template sections: mark the type of change (Chore), specify surfaces affected (API and Infra/Docker/CI), detail what changed, confirm no migrations needed, state no breaking changes, and document test plan steps executed.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Chore/security OSV triage safe upgrades' is vague and generic, using non-descriptive terms that don't clearly convey what was actually changed. Replace with a specific, descriptive title following Conventional Commits format, e.g., 'chore: upgrade Go to 1.26.4 and address dependency security advisories' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/security-osv-triage-safe-upgrades

Comment @coderabbitai help to get the list of available commands.

@nazarli-shabnam

Copy link
Copy Markdown
Member Author

Code review

No issues found. This PR is scoped purely to dependency and toolchain upgrades (OSV-triage safe upgrades); there are no application-logic changes, so no new behavioral or security surface is introduced. I resolved the outstanding merge conflicts with main (keeping the highest/most-secure version of each package) and verified the result end to end.

What changed and why it is valid

  • Go toolchain pingo 1.25.5 to 1.25.9, with the Dockerfile builder pinned to the matching golang:1.25.9-alpine instead of the floating golang:1.25-alpine. Manifest and build image are consistent.
    FROM golang:1.25.9-alpine AS builder
    WORKDIR /src

    devlane/api/go.mod

    Lines 1 to 4 in e5e9c5f

    module github.com/Devlaner/devlane/api
    go 1.25.9
  • Go modulesquic-go 0.54.0 to 0.57.0 (past the quic-go advisory line), jackc/pgx/v5 5.6.0 to 5.9.2. go.sum was regenerated with go mod tidy; unused indirect entries were pruned. All forward moves, no downgrades.
    github.com/jackc/pgx/v5 v5.9.2 // indirect

    devlane/api/go.mod

    Lines 91 to 93 in e5e9c5f

    github.com/quic-go/qpack v0.6.0 // indirect
    github.com/quic-go/quic-go v0.57.0 // indirect
    github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
  • UI dependenciesaxios to ^1.16.0 (resolves the axios advisory series; supersedes the earlier 1.15.2 bump on this branch) and vite to ^7.3.2. package-lock.json was regenerated so the lockfile matches the manifest.

    devlane/ui/package.json

    Lines 39 to 41 in e5e9c5f

    "@tiptap/suggestion": "^3.22.3",
    "axios": "^1.16.0",
    "clsx": "^2.1.1",

    devlane/ui/package.json

    Lines 62 to 64 in e5e9c5f

    "typescript-eslint": "^8.48.0",
    "vite": "^7.3.2"
    }

Verification performed

  • go build ./... and go vet ./... — clean.
  • Full go test ./... (real Postgres testcontainer + migrations) — passing (ran via pre-commit and pre-push hooks).
  • UI tsc -b && vite build and tsc typecheck — clean.
  • Live runtime boot: infra (Postgres/Redis/RabbitMQ/MinIO) healthy, API came up with migrations applied and all services connected (/health and /ready both 200), UI served on Vite 7.3.5, login page rendered and the client-to-API call path worked (the only console entries were the expected 401 on /api/users/me/ while unauthenticated, which correctly redirects to /login).

Branch is now MERGEABLE against main with version manifests internally consistent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/internal/service/github_sync.go (1)

143-150: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Authorize before loading the sync row.

GetByProject can return ErrRepoSyncNotFound before the new admin checks run, so non-admin workspace members can infer whether a project has a GitHub sync. Mirror CreateSync: resolve the workspace/project and enforce RoleAdmin before loading the sync record.

Also applies to: 204-211

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/internal/service/github_sync.go` around lines 143 - 150, The UpdateSync
function currently calls GetByProject before performing the admin authorization
check, which allows non-admin users to infer whether a GitHub sync exists by
observing the ErrRepoSyncNotFound error. Move the authorization check earlier in
the UpdateSync function by first resolving the workspace/project details and
enforcing the RoleAdmin check before calling GetByProject, mirroring the pattern
used in the CreateSync method. This ensures that unauthorized users receive an
ErrWorkspaceForbidden error regardless of whether the sync record exists.
🧹 Nitpick comments (3)
api/internal/handler/instance_test.go (1)

126-130: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Assert the PATCH response is sanitized too.

Line 126 stores the secret, but the test only checks the later GET response. Since UpdateSetting now sanitizes its immediate PATCH response as well, assert that rr does not echo super-secret.

🧪 Proposed test hardening
 	rr := ts.PATCH("/api/instance/settings/email", map[string]any{
 		"value": map[string]any{"host": "smtp.example.com", "password": "super-secret"},
 	}, session)
 	require.Equal(t, http.StatusOK, rr.Code, "body=%s", rr.Body.String())
+	assert.NotContains(t, rr.Body.String(), "super-secret")
+	patchBody := testutil.MustJSONMap(t, rr)
+	patchValue, _ := patchBody["value"].(map[string]any)
+	require.NotNil(t, patchValue)
+	assert.Equal(t, "", patchValue["password"])
+	assert.Equal(t, true, patchValue["password_set"])
 
 	// GET must never echo the plaintext secret back — only password_set.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/internal/handler/instance_test.go` around lines 126 - 130, The test for
the PATCH request to "/api/instance/settings/email" only verifies the HTTP
status code but does not assert that sensitive data is sanitized in the response
body. After the PATCH call that sends the password "super-secret", add an
assertion to verify that the response body (rr.Body.String()) does not contain
the sensitive password value "super-secret", ensuring the UpdateSetting endpoint
properly sanitizes its immediate response.
api/internal/model/workspace.go (1)

29-36: 🔒 Security & Privacy | 🔵 Trivial

Consolidate role constants: replace testutil.Role* with model.Role* imports.

The role constants are duplicated in api/internal/testutil/factory.go (lines 141–144) with identical values, but tests actively reference testutil.RoleMember, testutil.RoleAdmin, etc. instead of importing from the model package. If the testutil constants drift from model constants in a future update, tests will silently pass with incorrect authorization thresholds. Replace the testutil definitions with imports from model and update test files to use model.Role*.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/internal/model/workspace.go` around lines 29 - 36, The role constants
RoleGuest, RoleMember, RoleAdmin, and RoleOwner are duplicated in both the model
package (workspace.go) and the testutil package (factory.go) with identical
values, creating a maintenance risk if they diverge. Remove the duplicate role
constant definitions from the testutil package and instead import these
constants from the model package. Then update all test files that currently
reference testutil.RoleGuest, testutil.RoleMember, testutil.RoleAdmin, and
testutil.RoleOwner to use model.RoleGuest, model.RoleMember, model.RoleAdmin,
and model.RoleOwner respectively, ensuring a single source of truth for
authorization thresholds.
api/internal/oauth/github.go (1)

66-68: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Preserve GitHub email lookup failures instead of reporting all of them as unverified.

Line 67 converts network errors, non-2xx responses, malformed JSON, and “no verified email” into the same ErrEmailNotVerified, which makes GitHub OAuth outages or scope/config issues hard to diagnose now that this lookup is mandatory.

♻️ Proposed fix
 	email, err := g.fetchPrimaryEmail(ctx, token.AccessToken)
-	if err != nil || email == "" {
+	if err != nil {
+		return nil, err
+	}
+	if email == "" {
 		return nil, ErrEmailNotVerified
 	}
 	defer resp.Body.Close()
-	body, _ := io.ReadAll(resp.Body)
+	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
+		return "", fmt.Errorf("github email lookup failed: status %d", resp.StatusCode)
+	}
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
 	var emails []struct {
 		Email    string `json:"email"`
 		Primary  bool   `json:"primary"`
 		Verified bool   `json:"verified"`
@@
-	return "", fmt.Errorf("no verified email found")
+	return "", ErrEmailNotVerified
 }

Also applies to: 104-110

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/internal/oauth/github.go` around lines 66 - 68, The error handling in the
GitHub OAuth flow is masking all errors from the fetchPrimaryEmail method call
as ErrEmailNotVerified, making it difficult to diagnose network failures, scope
issues, or API problems. Instead of treating all errors the same, you need to
preserve the actual error returned by fetchPrimaryEmail and only return
ErrEmailNotVerified when the error is specifically about no verified email being
found. Check if the error is nil and email is empty, then return the actual
error; only return ErrEmailNotVerified when appropriate. Apply the same fix to
the similar pattern mentioned at lines 104-110.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/internal/auth/service.go`:
- Around line 278-280: The session deletion error from DeleteByUserIDExcept is
being silently ignored using the blank identifier, which means failed session
evictions are not detected and the operation still reports success to the
caller. Remove the blank identifier and instead capture the error return value
from the DeleteByUserIDExcept call in the sessionStore deletion block. Check if
an error is returned and propagate it back to the caller so that password
changes or account recovery operations fail appropriately when session eviction
fails. Apply this same fix to all occurrences of this pattern throughout the
service file where sessionStore.DeleteByUserIDExcept is called.

In `@api/internal/config/config.go`:
- Around line 109-113: The validation checks for MAGIC_CODE_SECRET and
INSTANCE_ENCRYPTION_KEY at lines 109-113 only reject empty strings but not
whitespace-only strings. Use strings.TrimSpace() to trim whitespace from these
environment variables before checking if they are empty, so that values
containing only spaces are correctly rejected as missing. Keep the original
untrimmed values for actual use, only trim them for the validation check. Ensure
the strings package is imported in the imports section.

In `@api/internal/oauth/google.go`:
- Around line 66-70: The email verification check in the Google OAuth handler is
not implementing fail-closed security. Currently, the code block checking the
verified_email field only rejects when the value is explicitly false, but if the
field is missing or not a boolean type, the code continues and links the email
anyway. Refactor the conditional logic around the verified_email field check to
ensure the code returns ErrEmailNotVerified unless verified_email is both
present and explicitly true. This means any case where the field is missing, has
a non-boolean value, or is false should be treated as unverified.

In `@api/internal/service/attachment.go`:
- Around line 76-83: The `ensureIssueAccess` method currently returns
`ErrAttachmentNotFound` for all three conditions (when `s.is.GetByID` returns an
error, when issue is nil, or when project IDs don't match), which masks
unexpected store errors and conflicts with the handler's error mapping logic
that only translates project-level errors to 404. Modify the method to return
the actual error from `s.is.GetByID` when it occurs (to surface real store
errors), and only return `ErrAttachmentNotFound` when the issue is nil or the
project ID doesn't match. This aligns the service/handler contract so legitimate
not-found scenarios are properly distinguished from unexpected errors.

In `@api/internal/service/cycle.go`:
- Around line 189-191: The GetByID call in AddCycleIssue currently returns
ErrIssueNotFound for all errors, including transient database failures. Refactor
the error handling to first check if err is not nil and propagate that error
directly, then separately check if issue is nil or issue.ProjectID does not
match projectID to return ErrIssueNotFound only for those true missing or
mismatched cases.

In `@api/internal/service/module.go`:
- Around line 163-165: The issue validation in the GetByID call is masking all
errors as ErrIssueNotFound, including real storage and runtime failures. Modify
the condition to differentiate between actual not-found scenarios and other
errors: return the actual error from s.is.GetByID when it represents a real
storage/runtime failure, and only return ErrIssueNotFound when the issue is
genuinely missing (when issue is nil) or when the issue.ProjectID does not match
the expected projectID. This preserves diagnostic information for actual system
failures while still correctly handling the 404 case for missing issues.

In `@api/internal/service/project.go`:
- Around line 200-211: The code assigns a role value to a project member without
validating that the caller has permission to grant that role level. Before the
line where m.Role is assigned in this section, retrieve the caller's role (the
user making the update), then validate that the role parameter is both a
valid/known role value and does not exceed the caller's role level, similar to
how workspace role validation works. Reject the operation with an appropriate
error if either validation fails.

In `@api/internal/service/workspace.go`:
- Around line 196-203: The first permission check only prevents non-owners from
changing the workspace owner's role, but it does not prevent the owner from
demoting themselves. This breaks the invariant that the workspace owner's role
must always remain as Owner. Add an additional check that prevents the owner
from being demoted by verifying that if the member being modified (m.MemberID)
is the workspace owner (w.OwnerID), the new role being assigned must be at the
Owner level or higher; if attempting to assign a role below Owner to the owner
themselves, return ErrWorkspaceForbidden.

In `@api/internal/store/session.go`:
- Around line 55-68: The DeleteByUserID and DeleteByUserIDExcept methods use
PostgreSQL-specific JSONB syntax (session_data::jsonb->>'user_id') that is
incompatible with SQLite used in tests. To fix this, refactor the session
filtering logic to be database-agnostic by either adding a dedicated user_id
column to the Session model and filtering directly on that column instead of
extracting from JSONB, or by using GORM query builders that automatically handle
dialect differences. This will ensure both methods work correctly in both
PostgreSQL and SQLite test environments when called from ResetPassword,
ChangePassword, or SetPassword.

---

Outside diff comments:
In `@api/internal/service/github_sync.go`:
- Around line 143-150: The UpdateSync function currently calls GetByProject
before performing the admin authorization check, which allows non-admin users to
infer whether a GitHub sync exists by observing the ErrRepoSyncNotFound error.
Move the authorization check earlier in the UpdateSync function by first
resolving the workspace/project details and enforcing the RoleAdmin check before
calling GetByProject, mirroring the pattern used in the CreateSync method. This
ensures that unauthorized users receive an ErrWorkspaceForbidden error
regardless of whether the sync record exists.

---

Nitpick comments:
In `@api/internal/handler/instance_test.go`:
- Around line 126-130: The test for the PATCH request to
"/api/instance/settings/email" only verifies the HTTP status code but does not
assert that sensitive data is sanitized in the response body. After the PATCH
call that sends the password "super-secret", add an assertion to verify that the
response body (rr.Body.String()) does not contain the sensitive password value
"super-secret", ensuring the UpdateSetting endpoint properly sanitizes its
immediate response.

In `@api/internal/model/workspace.go`:
- Around line 29-36: The role constants RoleGuest, RoleMember, RoleAdmin, and
RoleOwner are duplicated in both the model package (workspace.go) and the
testutil package (factory.go) with identical values, creating a maintenance risk
if they diverge. Remove the duplicate role constant definitions from the
testutil package and instead import these constants from the model package. Then
update all test files that currently reference testutil.RoleGuest,
testutil.RoleMember, testutil.RoleAdmin, and testutil.RoleOwner to use
model.RoleGuest, model.RoleMember, model.RoleAdmin, and model.RoleOwner
respectively, ensuring a single source of truth for authorization thresholds.

In `@api/internal/oauth/github.go`:
- Around line 66-68: The error handling in the GitHub OAuth flow is masking all
errors from the fetchPrimaryEmail method call as ErrEmailNotVerified, making it
difficult to diagnose network failures, scope issues, or API problems. Instead
of treating all errors the same, you need to preserve the actual error returned
by fetchPrimaryEmail and only return ErrEmailNotVerified when the error is
specifically about no verified email being found. Check if the error is nil and
email is empty, then return the actual error; only return ErrEmailNotVerified
when appropriate. Apply the same fix to the similar pattern mentioned at lines
104-110.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b52a6870-2fa1-4f2d-95a0-a901af689fe1

📥 Commits

Reviewing files that changed from the base of the PR and between e5e9c5f and 71aedbc.

⛔ Files ignored due to path filters (1)
  • ui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • api/internal/auth/service.go
  • api/internal/config/config.go
  • api/internal/handler/auth.go
  • api/internal/handler/instance.go
  • api/internal/handler/instance_test.go
  • api/internal/handler/oauth_config.go
  • api/internal/model/workspace.go
  • api/internal/oauth/github.go
  • api/internal/oauth/google.go
  • api/internal/oauth/oauth.go
  • api/internal/router/router.go
  • api/internal/service/attachment.go
  • api/internal/service/cycle.go
  • api/internal/service/github_sync.go
  • api/internal/service/integration.go
  • api/internal/service/issue_view.go
  • api/internal/service/module.go
  • api/internal/service/project.go
  • api/internal/service/workspace.go
  • api/internal/store/session.go
  • api/internal/testutil/factory.go
  • ui/package.json
  • ui/src/lib/sanitize.ts
  • ui/src/pages/EpicDetailPage.tsx
  • ui/src/pages/IssueDetailPage.tsx
  • ui/src/pages/PageDetailPage.tsx
✅ Files skipped from review due to trivial changes (1)
  • ui/src/lib/sanitize.ts

Comment thread api/internal/auth/service.go
Comment thread api/internal/config/config.go
Comment thread api/internal/oauth/google.go
Comment thread api/internal/service/attachment.go
Comment thread api/internal/service/cycle.go
Comment thread api/internal/service/module.go
Comment thread api/internal/service/project.go
Comment thread api/internal/service/workspace.go
Comment thread api/internal/store/session.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/internal/handler/instance.go (1)

76-116: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make first-admin seeding fail-closed and atomic.

h.Admins.Create is best-effort here. If it fails, setup still returns 201, but the created user makes future setup attempts hit count > 0, leaving the instance without an admin for the newly gated settings. The Count → SignUp → Create admin flow also lets concurrent setup requests seed multiple first admins. Wrap setup in a transaction/advisory lock or equivalent setup sentinel, require h.Admins to be configured before user creation, and return/rollback on admin seed failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/internal/handler/instance.go` around lines 76 - 116, The setup flow in
the InstanceSetupRequest handler ignores the error from h.Admins.Create, which
can leave the instance without an admin if the call fails, and concurrent
requests can create multiple first admins. Replace the underscore error discard
with proper error handling in the h.Admins.Create call to return an error
response, require h.Admins to be non-nil before attempting user creation with
h.Auth.SignUp, and wrap the entire flow (the h.Users.Count check through
h.Admins.Create) in a database transaction or advisory lock to ensure atomicity
and prevent concurrent setup requests from both succeeding.
🧹 Nitpick comments (1)
api/internal/testutil/factory.go (1)

131-134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the model role constant in the fixture.

SeedInstanceAdmin creates a model.InstanceAdmin, so using model.RoleOwner keeps security tests aligned with the production authorization constants if testutil role aliases ever drift.

♻️ Proposed fix
 	if err := store.NewInstanceAdminStore(db).Create(context.Background(), &model.InstanceAdmin{
 		UserID:     user.ID,
-		Role:       RoleOwner,
+		Role:       model.RoleOwner,
 		IsVerified: true,
 	}); err != nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/internal/testutil/factory.go` around lines 131 - 134, In the
SeedInstanceAdmin function (or the InstanceAdmin creation block), replace the
testutil role alias RoleOwner with the production model constant model.RoleOwner
when setting the Role field. This ensures that security tests use the actual
authorization constants from the model package rather than testutil aliases,
preventing potential misalignment if the aliases and production constants ever
diverge.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/internal/handler/instance.go`:
- Around line 236-245: The current implementation has a race condition where two
concurrent requests can both observe count == 2 in the Count() call and proceed
to DeleteByPK(), leaving zero admins. Replace the separate h.Admins.Count() and
h.Admins.DeleteByPK() calls with a single atomic transactional method in the
Admins store (e.g., DeleteByPKIfAdminsRemain) that performs the count check and
deletion within a transaction, or uses a conditional delete query that fails if
it would remove the last admin, ensuring only one request succeeds when multiple
admins exist.
- Around line 212-216: The current role assignment logic in the InstanceAdmin
creation silently defaults to RoleOwner when an invalid role is provided or
promotes roles without proper validation. Replace the conditional check that
only validates if req.Role is greater than or equal to model.RoleAdmin with
explicit validation that rejects any invalid roles. Add validation logic that
checks if req.Role is provided and ensures it is one of the valid allowed roles
(such as explicitly checking against RoleAdmin and RoleOwner), returning an
error response if the role is invalid rather than silently accepting or
defaulting it. This prevents bad client input from over-granting permissions or
persisting invalid authorization states in the model.InstanceAdmin struct.

---

Outside diff comments:
In `@api/internal/handler/instance.go`:
- Around line 76-116: The setup flow in the InstanceSetupRequest handler ignores
the error from h.Admins.Create, which can leave the instance without an admin if
the call fails, and concurrent requests can create multiple first admins.
Replace the underscore error discard with proper error handling in the
h.Admins.Create call to return an error response, require h.Admins to be non-nil
before attempting user creation with h.Auth.SignUp, and wrap the entire flow
(the h.Users.Count check through h.Admins.Create) in a database transaction or
advisory lock to ensure atomicity and prevent concurrent setup requests from
both succeeding.

---

Nitpick comments:
In `@api/internal/testutil/factory.go`:
- Around line 131-134: In the SeedInstanceAdmin function (or the InstanceAdmin
creation block), replace the testutil role alias RoleOwner with the production
model constant model.RoleOwner when setting the Role field. This ensures that
security tests use the actual authorization constants from the model package
rather than testutil aliases, preventing potential misalignment if the aliases
and production constants ever diverge.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fd99721-3b5e-4545-804d-8be9d59a3d15

📥 Commits

Reviewing files that changed from the base of the PR and between 71aedbc and 77727f8.

📒 Files selected for processing (8)
  • api/internal/handler/instance.go
  • api/internal/handler/instance_test.go
  • api/internal/model/instance_admin.go
  • api/internal/router/router.go
  • api/internal/store/instance_admin.go
  • api/internal/testutil/factory.go
  • api/migrations/000006_instance_admins.down.sql
  • api/migrations/000006_instance_admins.up.sql
✅ Files skipped from review due to trivial changes (2)
  • api/migrations/000006_instance_admins.down.sql
  • api/migrations/000006_instance_admins.up.sql

Comment thread api/internal/handler/instance.go
Comment thread api/internal/handler/instance.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/src/pages/instance-admin/InstanceAdminAdminsPage.tsx`:
- Line 112: The className on the email input field in InstanceAdminAdminsPage
contains focus:outline-none which removes the focus indicator without providing
a replacement visible focus state, breaking keyboard accessibility. Replace or
supplement the focus:outline-none class with appropriate focus styles (such as
focus:ring or focus:border styles) that provide a clear visual indicator when
the input receives keyboard focus, ensuring keyboard users can see which form
field is active.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c815debd-af17-43c8-9ac1-bb56526e5cf0

📥 Commits

Reviewing files that changed from the base of the PR and between 77727f8 and 8da047c.

📒 Files selected for processing (7)
  • ui/src/api/types.ts
  • ui/src/components/layout/InstanceAdminLayout.tsx
  • ui/src/pages/instance-admin/InstanceAdminAdminsPage.tsx
  • ui/src/pages/instance-admin/index.ts
  • ui/src/routes/index.tsx
  • ui/src/services/index.ts
  • ui/src/services/instanceService.ts
✅ Files skipped from review due to trivial changes (1)
  • ui/src/pages/instance-admin/index.ts

Comment thread ui/src/pages/instance-admin/InstanceAdminAdminsPage.tsx Outdated
@nazarli-shabnam nazarli-shabnam merged commit b3ab709 into main Jun 23, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Vulnerability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants