Skip to content

fix(issues): validate related ids on work-item create and update - #257

Merged
nazarli-shabnam merged 2 commits into
mainfrom
fix/validate-related-ids
Jul 5, 2026
Merged

fix(issues): validate related ids on work-item create and update#257
nazarli-shabnam merged 2 commits into
mainfrom
fix/validate-related-ids

Conversation

@martian56

@martian56 martian56 commented Jul 5, 2026

Copy link
Copy Markdown
Member

What

Closes #124. Issue create and update accepted state_id, label_ids, parent_id, and assignee_ids without checking they belonged to the right scope. A stale or malicious client could attach a state or label from another project, a parent from another project/workspace, or an assignee who isn't a workspace member — leaving the DB in a confusing state and skewing boards, filters, analytics, and notifications.

How

Both create and update now run a validateRelations check up front:

  • state_id and every label_id must belong to the same project.
  • parent_id must be another issue in the same project.
  • every assignee_id must be a member of the workspace.

Invalid ids return 400 with the specific reason instead of being written silently. The label store is wired into the issue service so label ownership can be checked; ownership stores are optional, so a check is skipped only if its store isn't configured (the router always wires them).

Testing

New internal/handler/issue_relation_validation_test.go:

  • Create rejects a foreign-project state, a foreign-project label, a foreign-project parent, and a non-member assignee (each 400); a well-scoped create still returns 201.
  • Update rejects the same foreign state/label/assignee (400); a well-scoped update still returns 200.

Full go test ./internal/handler ./internal/service green, no regressions.

AI assistance

Produced with the help of Claude Code (Claude Opus 4.8). AI-assisted commits carry a Co-Authored-By trailer.

Summary by CodeRabbit

  • Bug Fixes
    • Issue creation and updates now validate related fields (state, labels, parent, assignees) more strictly and reject out-of-scope references.
    • Invalid state/label/parent/assignee inputs now return 400 Bad Request with a clear error message instead of a generic server error.
  • Tests
    • Added coverage ensuring cross-project and non-member relation IDs are rejected for both create and update.

Create and update accepted state_id, label_ids, parent_id, and assignee_ids
without checking they belonged to the right scope, so a stale or malicious
client could attach a state/label from another project, a parent from another
project/workspace, or an assignee who isn't a workspace member — corrupting
boards, filters, analytics, and notifications.

Both paths now run a validateRelations check up front: state and labels must
belong to the same project, a parent must be another issue in the same project,
and every assignee must be a workspace member. Invalid ids return 400 instead of
being silently written. Wired the label store into the issue service so label
ownership can be checked.

Closes #124

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@martian56
martian56 requested a review from a team as a code owner July 5, 2026 09:23
@martian56 martian56 added this to the Finish w Enhancements milestone Jul 5, 2026
@martian56 martian56 added bug Something isn't working API labels Jul 5, 2026
@martian56 martian56 self-assigned this Jul 5, 2026
@strix-security

strix-security Bot commented Jul 5, 2026

Copy link
Copy Markdown

Strix Security Review

No security issues found.

Updated for 71d3c97.


Reviewed by Strix
Re-run review · Configure security review settings

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 123a53cc-0043-409f-945f-36643004ebfc

📥 Commits

Reviewing files that changed from the base of the PR and between a1cc8af and 71d3c97.

📒 Files selected for processing (2)
  • apps/api/internal/handler/issue_relation_validation_test.go
  • apps/api/internal/service/issue.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/internal/handler/issue_relation_validation_test.go
  • apps/api/internal/service/issue.go

📝 Walkthrough

Walkthrough

Adds server-side validation for issue state, label, parent, and assignee references on create and update, maps invalid relation errors to HTTP 400, wires the label store into issue service setup, and adds handler tests covering rejected and valid relation inputs.

Changes

Issue relation validation

Layer / File(s) Summary
Validation errors and helper
apps/api/internal/service/issue.go
Adds exported invalid-relation sentinel errors, an optional label store dependency, SetLabelStore, and validateRelations(...) for state, label, parent, and assignee checks.
Create and update validation
apps/api/internal/service/issue.go
Create and Update call validateRelations(...) before issue mutation, using update inputs to derive the target labels and assignees.
400 response mapping
apps/api/internal/handler/issue.go
Adds invalidRelationError and returns HTTP 400 with the service error text for invalid relation failures in Create and Update.
Router wiring and tests
apps/api/internal/router/router.go, apps/api/internal/handler/issue_relation_validation_test.go
Router setup now injects the label store into the issue service, and handler tests cover rejected foreign/self relations plus valid create/update requests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant IssueHandler
    participant IssueService
    participant StateStore
    participant LabelStore

    Client->>IssueHandler: POST/PATCH issue with relation IDs
    IssueHandler->>IssueService: Create(...) / Update(...)
    IssueService->>StateStore: validate state/project scope
    IssueService->>LabelStore: validate label/project scope
    IssueService->>IssueService: validate parent and assignee scope
    alt invalid relation
        IssueService-->>IssueHandler: ErrInvalid*
        IssueHandler-->>Client: 400 Bad Request
    else valid relation
        IssueService-->>IssueHandler: success
        IssueHandler-->>Client: 201 Created / 200 OK
    end
Loading

Possibly related issues

Suggested reviewers: strix-security

Poem

A rabbit nibbled each ID,
“Belongs here? Let it be.”
Bad relations hop away,
Good ones stay and pass today.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change and testing, but it omits many template sections like Summary, Type of change, Surface, and Why this approach. Add the missing template sections, especially Summary, Type of change, Surface, Why this approach, Breaking changes, and Checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is conventional, concise, and accurately summarizes the main change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/validate-related-ids

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 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 `@apps/api/internal/handler/issue_relation_validation_test.go`:
- Around line 51-74: `TestIssue_UpdateRejectsForeignRelations` is missing
coverage for rejecting a foreign `parent_id` on the Issue update path. Add an
update assertion in this test that PATCHes the same issue with a parent issue
from `otherProject` and expects `http.StatusBadRequest`, using the existing
`base`, `testutil.CreateIssue`, and `ts.PATCH` flow to match the other
foreign-relation checks.

In `@apps/api/internal/service/issue.go`:
- Around line 83-101: `validateRelations` currently only verifies that
`parent_id` belongs to the same project, so `IssueService.Update` can still
assign an issue as its own parent. Update the call path to pass the current
issue ID into `validateRelations` for update operations, and add an early
equality check in `validateRelations` to return `ErrInvalidParent` when
`parentID` matches the issue’s own ID before calling `s.is.GetByID`.
- Around line 84-107: In the validation path inside issue service logic (the
checks using s.states.GetByID, s.labels.GetByID, s.is.GetByID, and
s.ws.IsMember), stop collapsing every lookup failure into the invalid-*
sentinels. Keep mapping ErrRecordNotFound to
ErrInvalidState/ErrInvalidLabel/ErrInvalidParent/ErrInvalidAssignee, but return
any other error directly so database or query failures bubble up instead of
being treated as bad input.
🪄 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: 78167b76-791d-4d77-823a-7af6eb876730

📥 Commits

Reviewing files that changed from the base of the PR and between c4e5094 and a1cc8af.

📒 Files selected for processing (4)
  • apps/api/internal/handler/issue.go
  • apps/api/internal/handler/issue_relation_validation_test.go
  • apps/api/internal/router/router.go
  • apps/api/internal/service/issue.go

Comment thread apps/api/internal/handler/issue_relation_validation_test.go
Comment thread apps/api/internal/service/issue.go Outdated
Comment thread apps/api/internal/service/issue.go
…hecks

CodeRabbit on PR #257:
- reject setting an issue's parent to itself (validateRelations now takes the
  issue id and refuses a self-parent on update).
- only "record not found" maps to the invalid-* sentinels; any other datastore
  error from the state/label/parent lookups or the membership check is returned
  so real failures surface as 5xx instead of 400.
- cover the update-path parent rejection (foreign project + self) in the tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nazarli-shabnam
nazarli-shabnam merged commit 86529d4 into main Jul 5, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Validate related IDs when creating or updating work items

2 participants