Skip to content

fix: reject invalid signed URL expiries across storage providers - #1781

Merged
kang-heewon merged 3 commits into
trunkfrom
fix/1708-signed-url-expiry
Aug 8, 2026
Merged

fix: reject invalid signed URL expiries across storage providers#1781
kang-heewon merged 3 commits into
trunkfrom
fix/1708-signed-url-expiry

Conversation

@kang-heewon

@kang-heewon kang-heewon commented Aug 8, 2026

Copy link
Copy Markdown
Member

Outcome

Storage providers now share one signed URL expiry contract: a positive safe integer from 1 through 604800 seconds. Invalid values fail before signing with the stable STORAGE_INVALID_SIGNED_URL_EXPIRY Problem instead of reaching provider-specific behavior.

The contract is enforced by in-memory, R2, Cloudflare Images, and Cloudinary providers, published from @croco/storage-core, covered by provider conformance and boundary tests, and reflected in generated API and Problem registry artifacts.

Verification

  • Full pre-push test gate: 234/234 tasks passed
  • Full pre-push typecheck gate: 233/233 tasks passed
  • Affected package lint and typecheck passed
  • Storage provider suites passed, including 67 storage-core and 169 conformance tests
  • Problem registry: 582/582 discoveries
  • Public API snapshots: 115/115 matched
  • API docs trigger and strict-contract baseline checks passed
  • Changeset coverage passed

Review gates

  • Correctness: invalid, fractional, non-finite, unsafe, and above-limit values share one failure contract; 1 and 604800 preserve exact seconds.
  • API/security/release: additive public exports, fixed non-sensitive diagnostic text, generated registry/docs updates, and changesets for all affected publishable packages.
  • Maintainability: one validator is called at every provider boundary; an independent adversarial review passed after direct in-memory regression coverage was added.

pnpm check also completed 22/25 repository checks. The three aggregate-runner failures were isolated to a verification-test timeout, temporary SARIF generation, and a moved strict-contract line; their exact isolated gates passed after regeneration or direct rerun.

Fixes #1708

Summary by CodeRabbit

  • 새 기능

    • Signed URL 만료 시간을 1초~7일 범위의 안전한 정수로 검증합니다.
    • 잘못된 만료 시간에 대해 일관된 오류 코드와 400 오류를 제공합니다.
    • 만료 시간 검증 함수, 최대 만료 시간 상수 및 오류 유형을 공개 API로 제공합니다.
  • 문서

    • Signed URL 만료 시간 규칙과 새로운 오류 정보를 API 및 문제 해결 문서에 추가했습니다.
  • 버그 수정

    • 스토리지 제공자별 서명 전에 잘못된 만료 시간이 거부되도록 개선했습니다.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 44 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: ASSERTIVE

Plan: Pro Plus

Run ID: 29f645a0-ee01-4c65-9669-669e73e5554c

📥 Commits

Reviewing files that changed from the base of the PR and between 1559810 and bc4da47.

📒 Files selected for processing (4)
  • packages/docs/src/content/docs/api/storage-core/src/functions/validateSignedUrlExpiry.md
  • packages/storage-core/src/libs/signedUrlExpiry.ts
  • packages/storage-core/src/tests/InMemoryStorageProvider.spec.ts
  • packages/testing/src/libs/provider-conformance.ts
📝 Walkthrough

Walkthrough

스토리지 코어에 signed URL 만료값 검증 계약이 추가되었습니다. 모든 storage provider는 서명 전에 값을 검증합니다. 잘못된 값은 공통 문제 코드로 거부합니다. 적합성 테스트와 API·문서·릴리스 메타데이터가 갱신되었습니다.

Changes

Signed URL 만료 검증

Layer / File(s) Summary
공통 만료값 계약
packages/storage-core/src/libs/problems/InvalidSignedUrlExpiryProblem.ts, packages/storage-core/src/libs/signedUrlExpiry.ts, packages/storage-core/src/libs/types.ts, packages/storage-core/src/index.ts, packages/storage-core/src/tests/SignedUrlExpiry.spec.ts, public-api-surface.snapshot.json
validateSignedUrlExpiry가 1초 이상 604,800초 이하의 안전한 정수만 허용합니다. 잘못된 값은 InvalidSignedUrlExpiryProblemSTORAGE_INVALID_SIGNED_URL_EXPIRY를 사용합니다.
Provider 경계 적용
packages/storage-core/src/libs/InMemoryStorageProvider.ts, packages/storage-core/src/tests/InMemoryStorageProvider.spec.ts, packages/storage-r2/..., packages/storage-cloudflare/..., packages/storage-cloudinary/...
모든 provider가 signed URL 생성 전에 만료값을 검증합니다. 테스트는 최대값과 검증된 만료값 전달을 확인합니다.
Provider 적합성 검증
packages/testing/src/libs/provider-conformance.ts, packages/testing/src/tests/ConformanceContract.spec.ts, tsconfig/contract-strict.baseline.json
공통 적합성 테스트가 음수, 0, 소수, 비유한값, 안전하지 않은 정수, 최대값 초과를 검증합니다.
문서와 문제 코드 반영
packages/storage-core/README.md, packages/docs/src/content/docs/api/storage-core/..., docs/problem-code-registry.json, packages/docs/src/content/docs/en/reference/problem-recovery-cookbook.md, .changeset/valid-signed-url-expiry.md
새 API, 만료값 범위, 문제 코드와 복구 정보를 문서화합니다.

Workspace dependency override

Layer / File(s) Summary
nanoid 버전 고정
pnpm-workspace.yaml
workspace override에 nanoid: 3.3.17을 추가합니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant StorageProvider
  participant validateSignedUrlExpiry
  participant Signer
  Caller->>StorageProvider: getSignedUrl(expiresIn)
  StorageProvider->>validateSignedUrlExpiry: 만료값 검증
  validateSignedUrlExpiry-->>StorageProvider: 검증된 초 단위 만료값
  StorageProvider->>Signer: 검증된 만료값 전달
  Signer-->>Caller: signed URL 반환
Loading

Possibly related PRs

  • croco-dev/framework#1065: CloudflareImagesProviderCloudinaryProvider의 signed URL 처리 변경과 관련됩니다.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning pnpm-workspace.yaml의 nanoid 3.3.17 override 추가는 연결된 이슈의 signed URL 만료 검증 범위와 직접 관련이 없습니다. nanoid override를 제거하거나 별도의 이슈와 근거를 제시하여 변경 범위를 분리하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 모든 스토리지 제공자의 잘못된 signed URL 만료값 거부라는 주요 변경을 정확히 설명합니다.
Linked Issues check ✅ Passed 모든 대상 제공자에 공통 검증을 적용하고, 동일한 오류와 만료값 전파를 테스트하여 이슈 #1708의 요구사항을 충족합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1708-signed-url-expiry

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.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

📊 Benchmark Results

✅ All benchmarks passed

Benchmark p75 Threshold Baseline vs Baseline Status Notes
CrocoApp constructor 37.2μs 30.0ms 8.2μs +355.0% -
CrocoApp lambdaHandler (10 controllers) 2.1ms 50.0ms 258.4μs +707.0% -
Lambda cold-start simulation 1.6ms 80.0ms 418.1μs +290.5% -
Lambda cold-start with headers 1.6ms 80.0ms 369.7μs +324.9% -
Lambda cold-start with binary body 1.5ms 80.0ms 339.1μs +335.3% -
Lambda cold-start with query params 1.5ms 80.0ms 301.3μs +383.2% -
Lambda cold-start with authorizer context 1.4ms 80.0ms 299.8μs +378.0% -
Lambda cold-start realistic scenario 1.4ms 80.0ms 299.2μs +380.0% -
EventBusConfig.start (10 handlers) 1.9μs 10.0ms 1.4μs +31.4% -
EventPublisher.publishNow single event 2.1μs 2.0ms 1.7μs +26.7% -
DefaultHandlerResolver.resolve × 10 0.1μs 5.0ms 0.1μs +0.0% -
Container.get singleton (cold) 67.4μs 5.0ms 70.3μs -4.0% -
Container.register × 50 components 3.2ms 10.0ms 3.2ms -2.1% -
Container.validate (50 components) 3.6ms 20.0ms 3.4ms +7.2% -
Container.get singleton (warm) 1.6μs 500.0μs 1.6μs +0.6% -
TelemetryRuntime.init (lambda preset) 2.3μs 200.0ms 1.1ms -99.8% -
lambdaPreset config creation 1.4μs 2.0ms 1.4μs -1.4% -

Updated: 2026-08-08T09:30:15.484Z · Commit: 5d7355b

@kang-heewon

Copy link
Copy Markdown
Member Author

CI follow-up: the publish profile began blocking the newly reported GHSA-2v37-7h3g-55p8 through transitive nanoid@3.3.16 paths. Commit 1559810 pins the patched 3.3.17 release at the workspace override. pnpm security:audit-policy, its 57 policy tests, frozen-lockfile installation, and the full pre-push test/typecheck gates pass.

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/docs/src/content/docs/api/storage-core/src/functions/validateSignedUrlExpiry.md (1)

8-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

@throws 태그를 추가하여 Throws 섹션을 생성하십시오.

이 문서에는 expiresIn 값이 유효하지 않을 때 발생하는 예외에 대한 설명이 없습니다. 구현은 안전 정수가 아니거나, 0 이하이거나, MAX_SIGNED_URL_EXPIRY_SECONDS를 초과하면 InvalidSignedUrlExpiryProblem을 던집니다.

소스 함수(packages/storage-core/src/libs/signedUrlExpiry.ts)에 @throws {InvalidSignedUrlExpiryProblem} JSDoc 태그를 추가하십시오. TypeDoc이 이 태그를 사용해 "## Throws" 섹션을 자동으로 생성합니다. 이 생성된 파일은 직접 편집하지 마십시오.

🤖 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
`@packages/docs/src/content/docs/api/storage-core/src/functions/validateSignedUrlExpiry.md`
around lines 8 - 23, Update the source function validateSignedUrlExpiry in
signedUrlExpiry.ts by adding a JSDoc `@throws` annotation for
InvalidSignedUrlExpiryProblem, covering invalid, non-positive, or over-limit
expiry values; do not edit the generated Markdown documentation directly.
🤖 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 `@packages/storage-core/src/tests/InMemoryStorageProvider.spec.ts`:
- Around line 205-208: Update the rejection assertion in the affected test to
use rejects.toThrow(InvalidSignedUrlExpiryProblem) instead of
rejects.toBeInstanceOf, matching the existing async error assertion pattern;
retain the separate code and message toMatchObject validation.

In `@packages/storage-core/src/tests/SignedUrlExpiry.spec.ts`:
- Around line 21-45: Update the test setup around the “signed URL expiry
contract” suite to add a beforeEach that resets the DI Container and explicitly
initializes the test instance/state for every test. Use the project’s existing
container reset and test initialization helpers, if available, and leave the
expiry assertions unchanged.

In `@packages/testing/src/libs/provider-conformance.ts`:
- Around line 45-56: Rename the constants invalidSignedUrlExpiryCases and
invalidSignedUrlExpiryMessage to INVALID_SIGNED_URL_EXPIRY_CASES and
INVALID_SIGNED_URL_EXPIRY_MESSAGE, respectively, and update every reference to
these symbols throughout the conformance tests.

---

Outside diff comments:
In
`@packages/docs/src/content/docs/api/storage-core/src/functions/validateSignedUrlExpiry.md`:
- Around line 8-23: Update the source function validateSignedUrlExpiry in
signedUrlExpiry.ts by adding a JSDoc `@throws` annotation for
InvalidSignedUrlExpiryProblem, covering invalid, non-positive, or over-limit
expiry values; do not edit the generated Markdown documentation directly.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: ee173d09-668a-482a-aa31-7d5c1ca7d684

📥 Commits

Reviewing files that changed from the base of the PR and between 6795b4d and 1559810.

⛔ Files ignored due to path filters (2)
  • packages/problems-core/src/generated/problem-code-registry.ts is excluded by !**/generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .changeset/valid-signed-url-expiry.md
  • docs/problem-code-registry.json
  • packages/docs/src/content/docs/api/problems-core/src/variables/CROCO_PROBLEM_CODE_REGISTRY.md
  • packages/docs/src/content/docs/api/storage-core/src/classes/InvalidSignedUrlExpiryProblem.md
  • packages/docs/src/content/docs/api/storage-core/src/classes/StorageProblem.md
  • packages/docs/src/content/docs/api/storage-core/src/functions/validateSignedUrlExpiry.md
  • packages/docs/src/content/docs/api/storage-core/src/type-aliases/SignedUrlOptions.md
  • packages/docs/src/content/docs/api/storage-core/src/variables/MAX_SIGNED_URL_EXPIRY_SECONDS.md
  • packages/docs/src/content/docs/en/reference/problem-recovery-cookbook.md
  • packages/storage-cloudflare/src/libs/CloudflareImagesProvider.ts
  • packages/storage-cloudflare/src/tests/CloudflareImagesProvider.spec.ts
  • packages/storage-cloudinary/src/libs/CloudinaryProvider.ts
  • packages/storage-cloudinary/src/tests/CloudinaryProvider.spec.ts
  • packages/storage-core/README.md
  • packages/storage-core/src/index.ts
  • packages/storage-core/src/libs/InMemoryStorageProvider.ts
  • packages/storage-core/src/libs/problems/InvalidSignedUrlExpiryProblem.ts
  • packages/storage-core/src/libs/signedUrlExpiry.ts
  • packages/storage-core/src/libs/types.ts
  • packages/storage-core/src/tests/InMemoryStorageProvider.spec.ts
  • packages/storage-core/src/tests/SignedUrlExpiry.spec.ts
  • packages/storage-r2/src/libs/R2StorageProvider.ts
  • packages/storage-r2/src/tests/R2StorageProvider.spec.ts
  • packages/testing/src/libs/provider-conformance.ts
  • packages/testing/src/tests/ConformanceContract.spec.ts
  • pnpm-workspace.yaml
  • public-api-surface.snapshot.json
  • tsconfig/contract-strict.baseline.json

Comment thread packages/storage-core/src/tests/InMemoryStorageProvider.spec.ts Outdated
Comment thread packages/storage-core/src/tests/SignedUrlExpiry.spec.ts
Comment thread packages/testing/src/libs/provider-conformance.ts Outdated
@kang-heewon

Copy link
Copy Markdown
Member Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@kang-heewon
kang-heewon merged commit 7e88b45 into trunk Aug 8, 2026
12 checks passed
@kang-heewon
kang-heewon deleted the fix/1708-signed-url-expiry branch August 8, 2026 10:18
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.

[storage-core] Make signed-URL expiry a validated cross-provider contract

1 participant