Skip to content

fix(planning): require signed workspace authority on all routes [superseded by #168] - #160

Closed
seonghobae wants to merge 7 commits into
mainfrom
fix/planning-trusted-workspace-authority
Closed

fix(planning): require signed workspace authority on all routes [superseded by #168]#160
seonghobae wants to merge 7 commits into
mainfrom
fix/planning-trusted-workspace-authority

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Security outcome

Close the remaining client-selected planning tenant boundary identified in #158. Goal, project and task create/list routes now derive workspace ownership from the same short-lived signed life-os.workspace.v1 context already used by planning search and durable Today.

Test-first contract

A new controller-source contract was committed first and fails on protected main because six routes still bind @Headers('x-workspace-id') and requireWorkspaceId(). It requires:

  • zero bare x-workspace-id decorators;
  • no legacy requireWorkspaceId helper;
  • all nine workspace-scoped controller paths (search, Today GET/PUT, six Goal/Project/Task operations) to bind x-life-os-workspace-id, issued-at, signature and call requireTrustedWorkspaceContext.

Existing http-boundary.test.ts independently exercises fresh, stale, future, malformed, forged and unconfigured signed context plus sanitized HTTP failure mapping.

Implementation

  • removes BadRequestException and the legacy bare-workspace helper;
  • adds signed workspace context headers to all six legacy Goal/Project/Task create/list routes;
  • verifies the context before calling the planning domain service;
  • keeps path/body IDs as object locators and leaves repository tenant predicates unchanged;
  • does not alter search or durable Today behavior.

Standards / doctoring

docs/research/2026-08-10-planning-workspace-authority.md records the final NIST SP 800-53 Rev. 5 basis and explicitly avoids a conformance claim. CHANGELOG.md records the security boundary change.

Merge gate

Merge only after the unchanged exact head passes planning tests/typecheck/build, configured exact owned-code coverage/docstrings, CI, AppGuardrail, SAST Semgrep, Security Scan, Commercial Readiness, CodeRabbit/current review findings, current live-base compatibility and actual repository policy without administrative bypass.

Closes #158.

Summary by CodeRabbit

  • 보안 강화

    • 목표, 프로젝트, 작업의 생성 및 조회에 서명된 작업공간 컨텍스트 검증을 적용했습니다.
    • 클라이언트가 직접 지정한 작업공간 ID만으로는 접근할 수 없습니다.
    • 신뢰할 수 있는 작업공간 범위에서만 계획 데이터에 접근할 수 있습니다.
  • 새 기능

    • 캘린더 연결 해제 기능을 추가했습니다.
    • 인증 정보 없이도 연결 해제 결과를 확인할 수 있습니다.
  • 버그 수정

    • 오늘 보기 통합을 사용할 수 없을 때 부정확한 성공 응답 대신 제한 상태를 안내합니다.
  • 문서화

    • 작업공간 권한 검증 기준과 보안 원칙을 문서화했습니다.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

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: 9af60b5e-e91b-4b31-adf3-2a98ac2be471

📥 Commits

Reviewing files that changed from the base of the PR and between d0ec337 and b1b26bd.

📒 Files selected for processing (1)
  • CHANGELOG.md

📝 Walkthrough

Walkthrough

Planning 목표·프로젝트·작업 라우트가 클라이언트 지정 x-workspace-id 대신 서명된 life-os.workspace.v1 컨텍스트를 사용합니다. 컨트롤러 계약 테스트와 권한 추적 문서가 추가되었습니다. 변경 기록에는 관련 캘린더 및 Today 동작도 기록되었습니다.

Changes

Planning workspace 권한 강화

Layer / File(s) Summary
서명된 컨텍스트 라우트
apps/planning-service/src/main.ts
목표·프로젝트·작업 생성 및 목록 라우트가 workspace ID, 발급 시각, 서명 헤더를 검증합니다. 검증된 workspace ID를 planning 서비스 호출에 사용합니다. 레거시 x-workspace-id 검증은 제거했습니다. 기존 제목, goal ID, project ID 검증은 유지합니다.
권한 계약 및 추적성 검증
apps/planning-service/src/planning-controller-authority.test.ts, docs/research/..., CHANGELOG.md
컨트롤러 테스트가 서명 컨텍스트 헤더와 requireTrustedWorkspaceContext 사용을 확인합니다. Planning 권한 결정과 NIST SP 800-53 Rev. 5 근거를 문서화했습니다. 변경 기록에 캘린더 연결 해제와 Today 응답 변경도 반영했습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PlanningController
  participant requireTrustedWorkspaceContext
  participant PlanningService
  Client->>PlanningController: 서명된 workspace 컨텍스트 헤더 전송
  PlanningController->>requireTrustedWorkspaceContext: 컨텍스트 검증 요청
  requireTrustedWorkspaceContext-->>PlanningController: 검증된 workspace ID 반환
  PlanningController->>PlanningService: workspace ID와 요청 데이터 전달
  PlanningService-->>PlanningController: 리소스 결과 반환
  PlanningController-->>Client: HTTP 응답 반환
Loading

Possibly related issues

Possibly related PRs

🚥 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 제목은 모든 planning 라우트에 서명된 workspace 권한을 요구하는 주요 변경을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed Issue #158의 6개 goal·project·task 라우트가 서명된 workspace 컨텍스트를 사용하고 레거시 헤더 권한을 제거하도록 변경되었습니다.
Out of Scope Changes check ✅ Passed 변경된 컨트롤러, 테스트, 문서는 Issue #158의 workspace 권한 강화와 직접 관련되며 식별 가능한 범위 외 코드 변경은 없습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/planning-trusted-workspace-authority
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/planning-trusted-workspace-authority

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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 `@apps/planning-service/src/planning-controller-authority.test.ts`:
- Around line 20-32: Extend the tests around the PlanningController HTTP
boundary instead of only counting source tokens. Exercise every workspace-scoped
route with malformed, stale, future, forged, and unconfigured signed contexts,
asserting rejection status and response bodies while confirming no unverified
workspace ID reaches the service. Cover the six mutating Goal/Project/Task
routes to verify the signed workspace context overrides workspace identifiers
supplied in the request body or path.
🪄 Autofix

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: dcb2c2be-e850-4d03-8534-b0db3e355617

📥 Commits

Reviewing files that changed from the base of the PR and between c88016e and d0ec337.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • apps/planning-service/src/main.ts
  • apps/planning-service/src/planning-controller-authority.test.ts
  • docs/research/2026-08-10-planning-workspace-authority.md

Comment on lines +20 to +32
describe('PlanningController workspace authority contract', () => {
it('never accepts a bare client-selected workspace header', () => {
expect(count(LEGACY_WORKSPACE_HEADER)).toBe(0);
expect(CONTROLLER_SOURCE).not.toContain('function requireWorkspaceId');
});

it('binds every workspace-scoped planning route to the signed workspace context', () => {
// search + Today GET/PUT + six Goal/Project/Task routes.
expect(count(TRUSTED_WORKSPACE_HEADER)).toBe(9);
expect(count(TRUSTED_ISSUED_AT_HEADER)).toBe(9);
expect(count(TRUSTED_SIGNATURE_HEADER)).toBe(9);
expect(CONTROLLER_SOURCE.match(/requireTrustedWorkspaceContext\(/gu)).toHaveLength(9);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

HTTP 경계 실패 동작을 검증하는 테스트를 추가하십시오.

이 테스트는 main.ts의 토큰 수만 검사합니다. 이 테스트는 malformed, stale, future, forged, unconfigured context가 각 라우트에서 거부되는지 증명하지 않습니다. 이 테스트는 검증된 workspace ID만 서비스 호출에 전달되는지도 증명하지 않습니다.

실제 컨트롤러 또는 HTTP 경계 테스트를 추가하십시오. 각 실패 유형의 credential-free 상태와 응답 본문을 검증하십시오. 여섯 개 변경 라우트에서 서명된 context가 body 또는 path ID보다 workspace ownership에 우선하는지도 검증하십시오.

As per coding guidelines, “Tests must prove realistic domain accuracy and failure behavior, not only mocked call counts.”

🤖 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 `@apps/planning-service/src/planning-controller-authority.test.ts` around lines
20 - 32, Extend the tests around the PlanningController HTTP boundary instead of
only counting source tokens. Exercise every workspace-scoped route with
malformed, stale, future, forged, and unconfigured signed contexts, asserting
rejection status and response bodies while confirming no unverified workspace ID
reaches the service. Cover the six mutating Goal/Project/Task routes to verify
the signed workspace context overrides workspace identifiers supplied in the
request body or path.

Source: Coding guidelines

@seonghobae seonghobae changed the title fix(planning): require signed workspace authority on all routes fix(planning): require signed workspace authority on all routes [superseded by #168] Aug 10, 2026
@seonghobae seonghobae closed this Aug 10, 2026
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.

Replace legacy client-selected planning ownership with signed workspace authority

1 participant