Skip to content

fix(rbac): return the documented { error: { code, message } } shape from forbiddenByRole - #355

Merged
seonghobae merged 4 commits into
developmentalfrom
claude/cwlab-pr-audit-governance-1hdcp5
Aug 3, 2026
Merged

fix(rbac): return the documented { error: { code, message } } shape from forbiddenByRole#355
seonghobae merged 4 commits into
developmentalfrom
claude/cwlab-pr-audit-governance-1hdcp5

Conversation

@seonghobae

@seonghobae seonghobae commented Jul 30, 2026

Copy link
Copy Markdown

Problem

packages/web/src/lib/server/rbac.ts forbiddenByRole() emitted:

{ error: 'forbidden', message: '현재 역할(...)에서는 ...' }

error is a string, which the documented API contract explicitly forbids (CLAUDE.md: "모든 API 에러 응답은 { error: { code, message } }", "직접 { error: 'string' } 패턴 사용 금지"). The browser api-client reads data.error?.code and data.error?.message, so for the string shape both are undefined — the RBAC denial's code and its Korean role-explanation message are silently dropped and the user sees only the generic "API request failed: 403 Forbidden" fallback instead of why they were denied and what role is required.

Fix

Return the documented nested shape:

{ error: { code: 'FORBIDDEN', message: '현재 역할(...)에서는 ...' } }

Built inline (not via the jsonError helper) on purpose: error-helper.ts begins with import 'server-only', and pulling it into rbac.ts drags that throwing module into rbac.test.ts's import graph (the vitest config has no server-only stub). The codebase's other inline error sites do the same, and the contract is about the response shape, which this satisfies.

rbac.test.ts had pinned the wrong shape (body.error === 'forbidden', top-level body.message); updated to assert body.error.code === 'FORBIDDEN' and the nested body.error.message.

Scope

This fixes the shared RBAC denial helper — the common, test-pinned path. The same { error: 'string' } pattern remains at ~40 other route sites (e.g. orgs/[orgSlug]/members/[memberUserId]/route.ts:52,119) and is a follow-up sweep, intentionally out of scope here to keep this change small, test-backed, and low-risk.

Verification

pnpm --filter @argos/web exec vitest run src/lib/server/rbac.test.ts   # 35 passed
pnpm --filter @argos/web exec tsc --noEmit                             # clean

(CI builds @argos/shared before typecheck/test; done locally too.)

🤖 Generated with Claude Code

https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH


Generated by Claude Code

Summary by CodeRabbit

  • 버그 수정

    • 권한 부족으로 인한 403 오류 응답이 일관된 오류 코드와 메시지를 포함하도록 개선되었습니다.
  • 업데이트

    • 웹 애플리케이션과 인증 관련 구성 요소가 최신 버전으로 업데이트되었습니다.
    • 보안 및 안정성 향상을 위해 일부 라이브러리 버전이 조정되었습니다.

forbiddenByRole emitted { error: 'forbidden', message } — a string `error`,
which the documented API contract forbids ("직접 { error: 'string' } 패턴 사용
금지"). The browser api-client reads data.error?.code and data.error?.message,
so for the string shape BOTH are undefined: the RBAC denial's code and its
Korean role-explanation message are silently dropped and the user sees only the
generic "API request failed: 403" fallback.

Return { error: { code: 'FORBIDDEN', message } } (built inline to keep this
DB-free module importable in the vitest server env — the codebase's other
inline sites do the same). Update rbac.test.ts, which pinned the wrong shape
(body.error === 'forbidden', top-level body.message), to assert
body.error.code === 'FORBIDDEN' and the nested body.error.message.

Scope: this fixes the shared RBAC denial helper (the common path, pinned by
tests). The same { error: 'string' } pattern remains at ~40 other route sites
(e.g. orgs/[orgSlug]/members/[memberUserId]/route.ts) and is a follow-up sweep.

Verification: pnpm --filter @argos/web exec vitest run src/lib/server/rbac.test.ts
→ 35 passed; tsc --noEmit → clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a285c6b3-cd0b-44a8-995a-2b5fa7f0732b

📥 Commits

Reviewing files that changed from the base of the PR and between 9225cc1 and 1c2bd93.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • package.json
📝 Walkthrough

Walkthrough

루트 및 웹 패키지 의존성을 업데이트하고, RBAC 역할 거부 응답을 문자열 오류에서 코드와 메시지를 포함하는 객체 구조로 변경했습니다. 관련 테스트도 새 응답 형식에 맞게 갱신했습니다.

Changes

의존성 업데이트

Layer / File(s) Summary
패키지 의존성 버전 갱신
package.json, packages/web/package.json
Hono, js-yaml 및 여러 오버라이드를 업데이트하고, Next.js와 next-auth 버전을 변경합니다.

RBAC 오류 응답 계약

Layer / File(s) Summary
역할 거부 오류 응답 변경
packages/web/src/lib/server/rbac.ts, packages/web/src/lib/server/rbac.test.ts
403 응답의 error 필드를 FORBIDDEN 코드와 메시지를 포함하는 객체로 변경하고, 역할 및 권한 설명이 중첩된 메시지에 포함되는지 검증합니다.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

Suggested reviewers: greatsumini

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 forbiddenByRole의 문서화된 중첩 오류 응답 형태 반환이라는 PR의 핵심 변경을 정확하고 구체적으로 요약합니다.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/cwlab-pr-audit-governance-1hdcp5

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

Copy link
Copy Markdown
Author

CI status: the red checks are pre-existing base issues, not this diff

This PR is a 2-line TypeScript error-shape fix (forbiddenByRole{ error: { code, message } }) plus its test. It changes zero dependencies and adds no new code patterns, and it's verified locally: pnpm --filter @argos/web exec vitest run src/lib/server/rbac.test.ts35 passed, tsc --noEmit → clean.

Flagging once rather than re-reacting to each re-run. If any of these produces a new, non-empty finding that actually points at rbac.ts/rbac.test.ts, I'll address it immediately.


Generated by Claude Code

…highs)

Clears the org Security Scan / npm-audit findings that are cleanly fixable:

Runtime (packages/web + overrides):
- next-auth 5.0.0-beta.30 -> 5.0.0-beta.32 and @auth/core -> 0.41.3
  (clears the two CRITICAL Auth.js advisories GHSA-8fpg-xm3f-6cx3 and
  GHSA-7rqj-j65f-68wh, plus GHSA-xmf8-cvqr-rfgj high and GHSA-x445-f3h2-j279)
- next 15 -> ^15.5.22 (clears CVE-2026-64641/64649/64645 high + 64648/64647/
  64646/64644/64643 moderate)
- sharp -> ^0.35.0 (GHSA-f88m-g3jw-g9cj high), postcss -> ^8.5.18
  (GHSA-r28c-9q8g-f849 high)

Dev/build overrides refreshed/added:
- hono 4.12.25 -> 4.12.27 (the two prior overrides pinned vulnerable versions)
- js-yaml 4.2.0 -> 4.3.0 (GHSA-52cp-r559-cp3m), fast-uri -> ^3.1.4
  (GHSA-v2hh-gcrm-f6hx + GHSA-4c8g-83qw-93j6)
- brace-expansion per-major (@1 ^1.1.16, @2 ^2.0.2, @5 ^5.0.8): a blanket
  ^5.0.8 breaks minimatch@3.1.5 (v1 callable export; "expand is not a function"
  -> lint crash), so each major is patched to its own line. This clears the
  ReDoS (GHSA-3jxr-9vmj-r5cp).

Verified: pnpm --filter @argos/web typecheck (0), test (230 passed / 13 skipped),
lint (0). pnpm audit: all critical/high runtime advisories cleared.

Known residuals (not cleanly fixable here, dev-only, flagged on the PR):
- brace-expansion OOM (GHSA-mh99-v99m-4gvg) is patched only >=5.0.8, but reaches
  via @eslint/eslintrc -> minimatch@3.1.5 which requires brace-expansion 1.x's
  export; clearing it needs an eslint-toolchain upgrade.
- @hono/node-server path-traversal (GHSA-frvp-7c67-39w9) needs the breaking
  v1->v2 major.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH

Copy link
Copy Markdown
Author

Added a second, thematically-separate commit to this branch (9225cc1) — a dependency-CVE remediation — because the audit branch is confined to one branch per repo, so it co-locates here with the rbac error-shape fix. (Happy to split it into its own PR against developmental if preferred; it's an isolated package.json / pnpm-lock.yaml change that cherry-picks cleanly.)

What it clears (org Security Scan / pnpm audit — 2 critical + several high, all verified):

  • next-auth 5.0.0-beta.30 → beta.32 + @auth/core → 0.41.3 → clears the two CRITICAL Auth.js advisories (GHSA-8fpg-xm3f-6cx3, GHSA-7rqj-j65f-68wh) + GHSA-xmf8-cvqr-rfgj (high) + GHSA-x445-f3h2-j279 (moderate)
  • next 15 → ^15.5.22 → clears CVE-2026-64641/64649/64645 (high) + five moderates
  • sharp → ^0.35.0 (high), postcss → ^8.5.18 (high)
  • hono 4.12.25 → 4.12.27 (the prior override pinned a vulnerable version), js-yaml 4.2.0 → 4.3.0 (high), fast-uri → ^3.1.4 (high)
  • brace-expansion per-major (@1 ^1.1.16, @2 ^2.0.2, @5 ^5.0.8) → clears the ReDoS (GHSA-3jxr-9vmj-r5cp). A blanket ^5.0.8 breaks minimatch@3.1.5 (expand is not a function → lint crash), so each major is patched to its own line.

Verified: pnpm --filter @argos/web typecheck (0), test (230 passed / 13 skipped), lint (0). All critical/high runtime advisories cleared.

Known residuals (dev-only, flagged not silently dropped):

  • brace-expansion OOM (GHSA-mh99-v99m-4gvg) is patched only ≥5.0.8, but reaches via @eslint/eslintrc → minimatch@3.1.5, which requires brace-expansion 1.x's callable export — clearing it needs an eslint-toolchain upgrade, not a lock bump. Dev/build-tooling only.
  • @hono/node-server path-traversal (GHSA-frvp-7c67-39w9) needs the breaking v1→v2 major.

Generated by Claude Code

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

🧹 Nitpick comments (1)
package.json (1)

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

@auth/core 오버라이드의 상한을 제한하세요.

>=0.41.3은 향후 호환성이 검증되지 않은 버전까지 허용합니다. 인증 의존성은 검증된 패치/마이너 범위로 고정하거나 상한을 추가해, lockfile 재생성 시 예기치 않은 동작 변경을 방지하는 편이 안전합니다.

🤖 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 `@package.json` at line 25, Update the `@auth/core` dependency constraint in
package.json to retain the minimum supported version while adding a tested upper
bound, or pin it to the verified patch/minor range, so lockfile regeneration
cannot select unvalidated future releases.
🤖 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.

Nitpick comments:
In `@package.json`:
- Line 25: Update the `@auth/core` dependency constraint in package.json to retain
the minimum supported version while adding a tested upper bound, or pin it to
the verified patch/minor range, so lockfile regeneration cannot select
unvalidated future releases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b37000f1-e354-4823-a3f6-aa5a1a64f83d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ef092b and 9225cc1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json
  • packages/web/package.json
  • packages/web/src/lib/server/rbac.test.ts
  • packages/web/src/lib/server/rbac.ts

Address review feedback: the `>=0.41.3` override was open-ended, letting a
lockfile regeneration pick unvalidated future @auth/core releases. Pin to
^0.41.3 (allows 0.41.x patches, resolves to 0.41.3, compatible with the pinned
next-auth 5.0.0-beta.32) so an auth dependency cannot drift unvalidated.

Verified: pnpm --filter @argos/web typecheck (0); @auth/core resolves 0.41.3;
critical/high runtime advisories remain cleared.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH

Copy link
Copy Markdown
Author

Addressed the review + triaged the failing CI (drive-to-green on the security commit):

Review nitpick — done (1c2bd93): bounded @auth/core override >=0.41.3^0.41.3 (allows 0.41.x patches, resolves to 0.41.3, compatible with the pinned next-auth@5.0.0-beta.32) so an auth dep can't drift to an unvalidated future release. Re-verified typecheck (0).

Failing security gates — the documented residual, not a regression:

  • scan / dependency-review red on brace-expansion OOM (GHSA-mh99-v99m-4gvg). This is the pre-existing base-branch residual flagged above: the OOM fix is published only in ≥5.0.8, but it reaches via @eslint/eslintrc → minimatch@3.1.5, which requires brace-expansion 1.x's callable export — a blanket ^5.0.8 override crashes eslint (expand is not a function), so per-major is the max safe patch (it does clear the separate ReDoS GHSA-3jxr-9vmj-r5cp). Fully clearing the OOM needs an eslint-toolchain upgrade (@eslint/eslintrc/minimatch), out of scope for a lock bump. @hono/node-server (moderate) similarly needs the breaking v1→v2 major.
  • Semgrep — the job log shows only runner-sandbox output, no code finding; this commit is dependency-only (package.json/pnpm-lock.yaml), so the SAST result is unrelated to the bump (infra/pre-existing).

Net effect of this commit: the two CRITICAL Auth.js advisories + all runtime HIGHs (next-auth, next, sharp, postcss, @auth/core) are cleared and verified (typecheck + 230 tests + lint green). The remaining red is confined to dev-only, toolchain-coupled residuals that predate this PR.

Note (ONE SOURCE): CodeRabbit flags #344 / #356 / #353 as overlapping dependency/RBAC PRs — this branch and those should be de-duplicated so only one dependency-bump lands.


Generated by Claude Code

…-pr-audit-governance-1hdcp5

# Conflicts:
#	package.json
#	pnpm-lock.yaml
@seonghobae
seonghobae merged commit 085aea3 into developmental Aug 3, 2026
29 checks passed
@seonghobae
seonghobae deleted the claude/cwlab-pr-audit-governance-1hdcp5 branch August 3, 2026 10:29
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