Skip to content

Conversation

kevinansfield
Copy link
Member

@kevinansfield kevinansfield commented Jun 26, 2025

ref https://linear.app/ghost/issue/PROD-1605/

  • adds max-limit-cap middleware that rewrites ?limit parameters to a capped limit, including ?limit=all
    • has an exceptions list for endpoints that still require a larger limit param
    • has config escape valves for exceptional use-cases after 6.0 upgrade
    • by rewriting the query.limit on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
  • sets default capped limit to 100
  • applies the middleware to Admin API, Content API, and Comments API

Copy link
Contributor

coderabbitai bot commented Jun 26, 2025

Walkthrough

A new middleware, maxLimitCap, has been introduced to enforce a maximum cap on the limit query parameter for API requests across Ghost's Content, Admin, and Comments APIs. This middleware references new configuration options, maxLimit and allowLimitAll, which default to 100 and false, respectively. The middleware is applied to relevant API routes, with exceptions for specific endpoints such as certain admin export and email-related paths. The shared middleware index and configuration defaults have been updated to include this middleware and its settings. Comprehensive unit and end-to-end tests have been added to verify the middleware's behavior, including its handling of edge cases, exception routes, and correct modification of request parameters.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a78732 and d9b0697.

📒 Files selected for processing (1)
  • ghost/core/test/e2e-api/admin/max-limit-cap.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/test/e2e-api/admin/max-limit-cap.test.js
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Setup
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the migration [pull request] Includes migration for review label Jun 26, 2025
@kevinansfield kevinansfield force-pushed the capped-limit-middleware branch from 2012ed0 to b225e08 Compare June 26, 2025 16:47
@kevinansfield kevinansfield changed the base branch from main to 6.x June 26, 2025 16:47
@kevinansfield kevinansfield force-pushed the capped-limit-middleware branch from b225e08 to 2f9043b Compare June 26, 2025 16:48
@kevinansfield kevinansfield changed the title capped limit middleware 🔥 Enforced max ?limit of 100 in API requests Jun 26, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js (1)

40-40: Fix test description comment.

The comment says "browsing posts" but this test is actually browsing comments for a specific post.

-    it('should call maxLimitCap middleware when browsing posts', async function () {
+    it('should call maxLimitCap middleware when browsing comments', async function () {
ghost/core/test/unit/server/web/shared/middleware/max-limit-cap.test.js (2)

87-97: Remove misleading comment about skipped test.

The comment says this test is skipped, but it's actually running. The configUtils.set() call should work for testing dynamic config changes.

         it('should allow "all" when allowLimitAll is true', function () {
-            // This test is skipped because config values are loaded at module load time
-            // and cannot be changed dynamically in tests
             configUtils.set('optimization:allowLimitAll', true);

101-111: Remove misleading comment about skipped test.

Similar to the previous test, this comment is misleading since the test is actually running and should work with configUtils.set().

         it('should use custom maxLimit value', function () {
-            // This test is skipped because config values are loaded at module load time
-            // and cannot be changed dynamically in tests
             configUtils.set('optimization:maxLimit', 50);
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (2)

308-310: Fix inconsistent comment.

The comment mentions 14 posts but the assertion allows up to 15, which is inconsistent.

-            // Since we have 14 posts in test data, we get all 14
-            body.posts.length.should.be.lessThanOrEqual(15);
+            // limit=0 uses Ghost's default behavior, actual count may vary
+            body.posts.length.should.be.greaterThan(0);

188-192: Consider more robust assertions for member count tests.

The conditional assertion pattern if (body.members.length === 5) could make tests less reliable. Consider ensuring you have sufficient test data or using more deterministic assertions.

-            // Even though we requested 10, we should only get max 5
-            body.members.length.should.be.lessThanOrEqual(5);
-            if (body.members.length === 5) {
-                body.meta.pagination.limit.should.equal(5);
-            }
+            // Even though we requested 10, we should only get max 5
+            body.members.length.should.be.lessThanOrEqual(5);
+            body.meta.pagination.limit.should.equal(5);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 564a35a and 2f9043b.

📒 Files selected for processing (9)
  • ghost/core/core/server/web/api/app.js (2 hunks)
  • ghost/core/core/server/web/comments/routes.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/index.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/max-limit-cap.js (1 hunks)
  • ghost/core/core/shared/config/defaults.json (1 hunks)
  • ghost/core/test/e2e-api/admin/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/e2e-api/content/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/unit/server/web/shared/middleware/max-limit-cap.test.js (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.
ghost/core/core/server/web/comments/routes.js (1)
Learnt from: 55sketch
PR: TryGhost/Ghost#23894
File: ghost/core/core/server/api/endpoints/comments.js:16-22
Timestamp: 2025-06-18T10:56:19.906Z
Learning: The validateCommentData function in ghost/core/core/server/api/endpoints/comments.js is not duplicated elsewhere in the codebase - it's a unique validation function that ensures either post_id or parent_id is provided for comment creation.
ghost/core/core/server/web/api/app.js (1)
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.
ghost/core/core/shared/config/defaults.json (1)
Learnt from: allouis
PR: TryGhost/Ghost#22127
File: .github/scripts/release-apps.js:72-74
Timestamp: 2025-02-06T10:30:53.440Z
Learning: In the Ghost repository, the path to defaults.json is correctly structured as 'ghost/core/core/shared/config/defaults.json' with an intentional double 'core' in the path.
ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js (2)
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Learnt from: 55sketch
PR: TryGhost/Ghost#23894
File: ghost/core/core/server/api/endpoints/comments.js:16-22
Timestamp: 2025-06-18T10:56:19.906Z
Learning: The validateCommentData function in ghost/core/core/server/api/endpoints/comments.js is not duplicated elsewhere in the codebase - it's a unique validation function that ensures either post_id or parent_id is provided for comment creation.
ghost/core/test/e2e-api/content/max-limit-cap.test.js (5)
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.
Learnt from: ErisDS
PR: TryGhost/Ghost#23588
File: ghost/core/test/e2e-api/admin/backup.test.js:136-148
Timestamp: 2025-05-29T10:37:26.369Z
Learning: In the Ghost test framework, when testing CSV exports via the admin API, the response `body` field is empty while the actual CSV data is provided through the `text` field. Tests should use `expectEmptyBody()` and then validate the CSV content via `.expect(({text}) => ...)` - this is not contradictory behavior.
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.
ghost/core/test/unit/server/web/shared/middleware/max-limit-cap.test.js (4)
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (4)
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.
Learnt from: ErisDS
PR: TryGhost/Ghost#23588
File: ghost/core/test/e2e-api/admin/backup.test.js:136-148
Timestamp: 2025-05-29T10:37:26.369Z
Learning: In the Ghost test framework, when testing CSV exports via the admin API, the response `body` field is empty while the actual CSV data is provided through the `text` field. Tests should use `expectEmptyBody()` and then validate the CSV content via `.expect(({text}) => ...)` - this is not contradictory behavior.
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.
🧬 Code Graph Analysis (1)
ghost/core/test/e2e-api/content/max-limit-cap.test.js (2)
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (12)
  • require (1-1)
  • should (2-2)
  • agent (8-8)
  • agent (150-151)
  • agent (159-160)
  • agent (168-169)
  • agent (184-185)
  • agent (195-196)
  • agent (208-209)
  • agent (219-220)
  • agent (295-296)
  • agent (304-305)
ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js (8)
  • require (1-1)
  • should (2-2)
  • sinon (3-3)
  • sharedMiddleware (5-5)
  • agent (8-8)
  • originalMiddleware (10-10)
  • middlewareSpy (11-11)
  • req (49-49)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: E2E tests
  • GitHub Check: Acceptance tests (Node 22.13.1, mysql8)
  • GitHub Check: Legacy tests (Node 22.13.1, mysql8)
  • GitHub Check: Unit tests (Node 22.13.1)
  • GitHub Check: Acceptance tests (Node 22.13.1, sqlite3)
  • GitHub Check: Legacy tests (Node 22.13.1, sqlite3)
  • GitHub Check: Ghost-CLI tests
  • GitHub Check: Lint
🔇 Additional comments (5)
ghost/core/core/shared/config/defaults.json (1)

174-175: Configuration values look appropriate for API limit capping.

The default values of maxLimit: 100 and allowLimitAll: false align well with the PR objectives and provide reasonable performance constraints for API pagination.

ghost/core/core/server/web/shared/middleware/index.js (1)

14-16: Middleware export follows established patterns.

The new maxLimitCap getter follows the same lazy-loading pattern as other middleware exports and maintains consistent code structure.

ghost/core/core/server/web/api/app.js (1)

23-24: Excellent middleware placement and documentation.

The maxLimitCap middleware is correctly positioned after version compatibility checks but before endpoint routing, ensuring all API requests are processed. The descriptive comment clearly explains its purpose.

ghost/core/core/server/web/comments/routes.js (1)

26-27: Proper middleware placement for authenticated comment routes.

The middleware is correctly positioned after member session loading but before route handlers, ensuring limit capping applies to all authenticated comment operations. The implementation is consistent with the main API app.

ghost/core/test/e2e-api/content/max-limit-cap.test.js (1)

1-41: Comprehensive e2e test coverage for Content API limit capping.

The test implementation follows established Ghost testing patterns and provides thorough coverage:

  • Proper setup with authenticated agent and fixtures
  • Smart use of Sinon spy to monitor middleware behavior without disrupting functionality
  • Verifies both middleware invocation and actual query parameter modification
  • Clean teardown to restore original middleware

The test effectively validates that limit=all requests are properly capped at 100, ensuring the middleware works as expected in the Content API context.


const limit = req.query.limit;

if (!limit) {
Copy link
Member

@ErisDS ErisDS Jun 26, 2025

Choose a reason for hiding this comment

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

If not removing the config reads, maybe move them below this line 🤔

But I would probably move them up. We can override the config in testing with them outside the function so seems worth it.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍 I've moved them up. I had it that way before but tests were failing and I thought it was because the module-level variables weren't being overridden after the require but they seem to be passing now so maybe it was from something else not being right at the time 🤔

Copy link
Member Author

@kevinansfield kevinansfield Jun 27, 2025

Choose a reason for hiding this comment

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

No, I was right that we couldn't override in tests when they were module-scope. Added a workaround in cd0a383 which feels ok although maybe a bit magical 😅

@kevinansfield kevinansfield force-pushed the capped-limit-middleware branch from 2f9043b to 727997c Compare June 27, 2025 09:21
@kevinansfield kevinansfield removed the migration [pull request] Includes migration for review label Jun 27, 2025
@TryGhost TryGhost deleted a comment from github-actions bot Jun 27, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
ghost/core/core/server/web/shared/middleware/max-limit-cap.js (2)

41-47: Improve numeric validation for better edge case handling.

The current logic handles most cases well, but could be more explicit about edge cases like negative numbers and very large numbers.

Consider this more explicit approach:

-    // Convert to number for comparison
-    const numericLimit = parseInt(limit, 10);
-
-    // If it's not a valid number or exceeds maxLimit, cap it
-    if (isNaN(numericLimit) || numericLimit > maxLimit) {
+    // Convert to number for comparison
+    const numericLimit = parseInt(limit, 10);
+
+    // If it's not a valid number, negative, or exceeds maxLimit, cap it
+    if (isNaN(numericLimit) || numericLimit < 1 || numericLimit > maxLimit) {
         req.query.limit = maxLimit;
     }

This makes the handling of negative numbers more explicit and ensures limits are positive.


18-50: Consider adding logging for limit cap enforcement.

The middleware silently caps limits without logging, which could make debugging difficult in production.

Consider adding debug logging when limits are capped:

const logging = require('../../../../shared/logging');

// In the middleware function, add logging where limits are capped:
if (limit === 'all') {
    logging.debug(`Capping limit 'all' to ${maxLimit} for ${req.originalUrl}`);
    req.query.limit = maxLimit;
    return next();
}

// And for numeric limits:
if (isNaN(numericLimit) || numericLimit > maxLimit) {
    logging.debug(`Capping limit ${limit} to ${maxLimit} for ${req.originalUrl}`);
    req.query.limit = maxLimit;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9043b and 727997c.

📒 Files selected for processing (9)
  • ghost/core/core/server/web/api/app.js (2 hunks)
  • ghost/core/core/server/web/comments/routes.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/index.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/max-limit-cap.js (1 hunks)
  • ghost/core/core/shared/config/defaults.json (1 hunks)
  • ghost/core/test/e2e-api/admin/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/e2e-api/content/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/unit/server/web/shared/middleware/max-limit-cap.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • ghost/core/core/server/web/comments/routes.js
  • ghost/core/core/server/web/api/app.js
  • ghost/core/core/server/web/shared/middleware/index.js
  • ghost/core/test/e2e-api/content/max-limit-cap.test.js
  • ghost/core/core/shared/config/defaults.json
  • ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js
  • ghost/core/test/unit/server/web/shared/middleware/max-limit-cap.test.js
  • ghost/core/test/e2e-api/admin/max-limit-cap.test.js
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.
ghost/core/core/server/web/shared/middleware/max-limit-cap.js (1)
Learnt from: aileen
PR: TryGhost/Ghost#24073
File: apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx:22-32
Timestamp: 2025-06-27T06:04:37.910Z
Learning: In Ghost's limiter implementation, the pattern of first checking `limiter.isLimited(limitName)` and then calling `limiter.errorIfWouldGoOverLimit(limitName)` is required and not redundant. This pattern allows for early bailout when no limit exists while still performing the full validation that can throw HostLimitError when needed.
🔇 Additional comments (1)
ghost/core/core/server/web/shared/middleware/max-limit-cap.js (1)

15-16: Good performance optimization - config reads moved to module level.

The configuration values are now read at module load time rather than on each request, which addresses the performance concern raised in previous reviews.

Comment on lines 30 to 34
// Check exception endpoints - they bypass all limits
if (EXCEPTION_ENDPOINTS.some(endpoint => req.originalUrl.startsWith(endpoint))) {
return next();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential security risk: exception endpoint matching is too broad.

The startsWith check could match unintended endpoints. For example, /ghost/api/admin/posts/export/ would also match /ghost/api/admin/posts/export/malicious-endpoint.

Consider using exact path matching or a more precise pattern:

-    if (EXCEPTION_ENDPOINTS.some(endpoint => req.originalUrl.startsWith(endpoint))) {
+    if (EXCEPTION_ENDPOINTS.some(endpoint => {
+        const url = req.originalUrl;
+        return url === endpoint || url.startsWith(endpoint) && url[endpoint.length] === '?';
+    })) {

Or define more specific patterns for the email endpoint:

 const EXCEPTION_ENDPOINTS = [
     '/ghost/api/admin/posts/export/',
-    '/ghost/api/admin/emails/' // /:id/batches/ and /:id/recipient-failures/
+    '/ghost/api/admin/emails/',
+    '/ghost/api/admin/emails/*/batches/',
+    '/ghost/api/admin/emails/*/recipient-failures/'
 ];

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In ghost/core/core/server/web/shared/middleware/max-limit-cap.js around lines 30
to 33, the current use of startsWith for matching exception endpoints is too
broad and may allow unintended endpoints to bypass limits. Replace the
startsWith check with exact path matching or use a more precise pattern matching
approach such as regular expressions to ensure only the intended endpoints are
matched. Update the EXCEPTION_ENDPOINTS list or matching logic to define
specific patterns that accurately represent the allowed exception paths.

ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
  - has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
@kevinansfield kevinansfield force-pushed the capped-limit-middleware branch from 6260717 to cd0a383 Compare June 27, 2025 10:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (2)

183-205: Consider more robust assertions for members API tests.

The use of lessThanOrEqual with conditional limit checking suggests uncertainty about test data. Consider ensuring predictable test data or using more specific assertions.

-            // Even though we requested 10, we should only get max 5
-            body.members.length.should.be.lessThanOrEqual(5);
-            if (body.members.length === 5) {
-                body.meta.pagination.limit.should.equal(5);
-            }
+            // Even though we requested 10, we should only get max 5
+            body.members.length.should.be.lessThanOrEqual(5);
+            body.meta.pagination.limit.should.equal(5);

308-311: Clarify the expected behavior for limit=0.

The comment suggests uncertainty about Ghost's internal handling of limit=0. Consider verifying the exact behavior or making the assertion more specific.

-            // limit=0 is treated as no limit by Ghost, which uses default page size
-            // The actual behavior depends on Ghost's internal handling
-            // Since we have 14 posts in test data, we get all 14
-            body.posts.length.should.be.lessThanOrEqual(15);
+            // limit=0 should use Ghost's default page size (typically 15)
+            // or return all available posts if less than default
+            body.posts.length.should.be.greaterThan(0);
+            body.posts.length.should.be.lessThanOrEqual(15);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6260717 and cd0a383.

📒 Files selected for processing (8)
  • ghost/core/core/server/web/api/app.js (2 hunks)
  • ghost/core/core/server/web/comments/routes.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/index.js (1 hunks)
  • ghost/core/core/server/web/shared/middleware/max-limit-cap.js (1 hunks)
  • ghost/core/core/shared/config/defaults.json (1 hunks)
  • ghost/core/test/e2e-api/admin/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/e2e-api/content/max-limit-cap.test.js (1 hunks)
  • ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • ghost/core/core/server/web/shared/middleware/index.js
  • ghost/core/core/server/web/comments/routes.js
  • ghost/core/core/server/web/api/app.js
  • ghost/core/core/shared/config/defaults.json
  • ghost/core/test/e2e-api/content/max-limit-cap.test.js
  • ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js
  • ghost/core/core/server/web/shared/middleware/max-limit-cap.js
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.
Learnt from: aileen
PR: TryGhost/Ghost#24073
File: apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx:22-32
Timestamp: 2025-06-27T06:04:37.910Z
Learning: In Ghost's limiter implementation, the pattern of first checking `limiter.isLimited(limitName)` and then calling `limiter.errorIfWouldGoOverLimit(limitName)` is required and not redundant. This pattern allows for early bailout when no limit exists while still performing the full validation that can throw HostLimitError when needed.
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (4)
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.
Learnt from: ErisDS
PR: TryGhost/Ghost#23588
File: ghost/core/test/e2e-api/admin/backup.test.js:136-148
Timestamp: 2025-05-29T10:37:26.369Z
Learning: In the Ghost test framework, when testing CSV exports via the admin API, the response `body` field is empty while the actual CSV data is provided through the `text` field. Tests should use `expectEmptyBody()` and then validate the CSV content via `.expect(({text}) => ...)` - this is not contradictory behavior.
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.
🧬 Code Graph Analysis (1)
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (4)
ghost/core/core/server/web/comments/routes.js (1)
  • require (4-4)
ghost/core/test/e2e-api/content/max-limit-cap.test.js (4)
  • require (1-1)
  • should (2-2)
  • sinon (3-3)
  • agent (7-7)
ghost/core/test/e2e-api/members-comments/max-limit-cap.test.js (5)
  • require (1-1)
  • should (2-2)
  • sinon (3-3)
  • agent (8-8)
  • postId (9-9)
ghost/core/test/unit/server/web/shared/middleware/max-limit-cap.test.js (2)
  • sinon (2-2)
  • maxLimitCap (6-6)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Setup
🔇 Additional comments (5)
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (5)

1-6: LGTM - Standard test setup with appropriate imports.

The imports are correctly structured using Ghost's E2E test framework utilities, including the necessary testing libraries and database access.


13-14: Good practice stubbing configuration for isolated testing.

Stubbing the maxLimit to 5 provides predictable test conditions and makes assertions more reliable.


149-181: Comprehensive posts API testing with good coverage.

The tests effectively cover both limit capping scenarios and exception handling for the export endpoint.


232-255: Excellent exception endpoint testing with proper verification.

The test correctly verifies that regular endpoints are capped while exception endpoints bypass the limit. The dual verification approach strengthens the test.


294-313: Good edge case coverage for robustness.

The tests handle non-numeric limits and special cases like limit=0, which are important for API resilience.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (2)

217-237: Consider more consistent assertion patterns.

The use of lessThanOrEqual(5) with conditional limit checks creates inconsistent test behavior compared to the Posts API tests that use exact equality. This pattern might mask issues where the limit cap isn't properly applied.

Consider either ensuring sufficient test data exists to use exact assertions, or adding explicit checks for the expected scenario.

 it('should cap limit to 5 when limit exceeds max', async function () {
     const {body} = await agent.get('members/?limit=10')
         .expectStatus(200);
 
-    // Even though we requested 10, we should only get max 5
-    body.members.length.should.be.lessThanOrEqual(5);
-    if (body.members.length === 5) {
-        body.meta.pagination.limit.should.equal(5);
-    }
+    // Even though we requested 10, we should only get max 5
+    body.members.length.should.be.lessThanOrEqual(5);
+    body.meta.pagination.limit.should.equal(5); // Always check the limit was capped
 });

337-345: Fix inconsistency between comment and assertion.

The comment states "we have 14 posts in test data, we get all 14" but the assertion checks for lessThanOrEqual(15). This inconsistency could be confusing for maintainers.

         // limit=0 is treated as no limit by Ghost, which uses default page size
         // The actual behavior depends on Ghost's internal handling
-        // Since we have 14 posts in test data, we get all 14
-        body.posts.length.should.be.lessThanOrEqual(15);
+        // The exact count depends on Ghost's default behavior for limit=0
+        body.posts.length.should.be.lessThanOrEqual(15);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd0a383 and 9a78732.

📒 Files selected for processing (1)
  • ghost/core/test/e2e-api/admin/max-limit-cap.test.js (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:28-28
Timestamp: 2025-05-29T07:47:00.748Z
Learning: In the Ghost codebase, middleware directories should be included in unit test coverage because they contain actual business logic that should be tested, not just routing or setup code.
Learnt from: aileen
PR: TryGhost/Ghost#24073
File: apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx:22-32
Timestamp: 2025-06-27T06:04:37.910Z
Learning: In Ghost's limiter implementation, the pattern of first checking `limiter.isLimited(limitName)` and then calling `limiter.errorIfWouldGoOverLimit(limitName)` is required and not redundant. This pattern allows for early bailout when no limit exists while still performing the full validation that can throw HostLimitError when needed.
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (4)
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Learnt from: mike182uk
PR: TryGhost/Ghost#22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.
Learnt from: ErisDS
PR: TryGhost/Ghost#23588
File: ghost/core/test/e2e-api/admin/backup.test.js:136-148
Timestamp: 2025-05-29T10:37:26.369Z
Learning: In the Ghost test framework, when testing CSV exports via the admin API, the response `body` field is empty while the actual CSV data is provided through the `text` field. Tests should use `expectEmptyBody()` and then validate the CSV content via `.expect(({text}) => ...)` - this is not contradictory behavior.
Learnt from: ErisDS
PR: TryGhost/Ghost#23582
File: ghost/core/.c8rc.json:24-24
Timestamp: 2025-05-29T07:45:35.714Z
Learning: In Ghost project, app.js files under core/server/web are intentionally excluded from unit test coverage because they are not easily unit-testable due to being entry points with initialization code and side effects.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Setup
🔇 Additional comments (3)
ghost/core/test/e2e-api/admin/max-limit-cap.test.js (3)

1-27: Setup and imports look good.

The test setup properly stubs the max limit configuration for testing and follows Ghost's E2E testing patterns with appropriate cleanup.


28-181: Excellent refactoring of test data creation.

The helper functions are well-organized and focused, successfully addressing the previous feedback about breaking down the large createBulkTestData function. The separation of concerns makes the code much more maintainable.


264-325: Exception endpoint tests are well-designed.

The tests properly verify that exception endpoints bypass the limit cap while confirming regular endpoints remain capped. Good defensive programming with the testEmail existence checks.

@kevinansfield kevinansfield merged commit 4d57c80 into 6.x Jun 27, 2025
20 of 21 checks passed
@kevinansfield kevinansfield deleted the capped-limit-middleware branch June 27, 2025 11:56
ErisDS pushed a commit that referenced this pull request Jun 30, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 1, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 2, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 2, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 10, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 10, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 11, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 14, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 15, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 16, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 17, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 17, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 18, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 20, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 21, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
ErisDS pushed a commit that referenced this pull request Jul 21, 2025
ref https://linear.app/ghost/issue/PROD-1605/

- adds `max-limit-cap` middleware that rewrites `?limit` parameters to a capped limit, including `?limit=all`
- has an exceptions list for endpoints that still require a larger limit param
  - has config escape valves for exceptional use-cases after 6.0 upgrade
  - by rewriting the `query.limit` on the request it allows everything lower in the stack to behave as if the user requested the capped limit, avoiding need to adjust code anywhere else
- sets default capped limit to 100
- applies the middleware to Admin API, Content API, and Comments API
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.

3 participants