Skip to content

feat: Add server option readOnlyMasterKeyIps to restrict readOnlyMasterKey by IP#10115

Merged
mtrezza merged 5 commits intoparse-community:alphafrom
mtrezza:feat/readOnlyMasterKeyIps
Mar 6, 2026
Merged

feat: Add server option readOnlyMasterKeyIps to restrict readOnlyMasterKey by IP#10115
mtrezza merged 5 commits intoparse-community:alphafrom
mtrezza:feat/readOnlyMasterKeyIps

Conversation

@mtrezza
Copy link
Member

@mtrezza mtrezza commented Mar 6, 2026

Pull Request

Issue

Add server option readOnlyMasterKeyIps to restrict readOnlyMasterKey by IP.

Security Note

This is not a vulnerability fix, as the existing masterKeyIps option only restricts the masterKey and was never designed to apply to readOnlyMasterKey. Each master-level key has its own independent IP restriction option:

  • masterKeymasterKeyIps
  • maintenanceKeymaintenanceKeyIps
  • readOnlyMasterKey → (no IP restriction option existed)

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

  • New Features

    • IP-based access control for the read-only master key via a new configuration option (defaults allow all).
  • Documentation

    • Expanded docs detailing formats, CIDR support, defaults, edge cases, and environment-variable usage.
  • Bug Fixes

    • Improved IPv6 wide-address handling (treats ::0 alongside ::/0 and ::).
  • Tests

    • Added tests covering allowed and denied IP scenarios for read-only master key usage.
  • Chores / Deprecations

    • Added a deprecation entry guiding a future default change for the new option.

@parse-github-assistant
Copy link

parse-github-assistant bot commented Mar 6, 2026

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Mar 6, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b678f00-1d16-4ee1-a33e-8aa4464d4e7e

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd508f and cd282d5.

📒 Files selected for processing (1)
  • src/Security/CheckGroups/CheckGroupServerConfig.js

📝 Walkthrough

Walkthrough

Adds a new public option readOnlyMasterKeyIps (validation, docs, default), exposes runtime readOnlyMasterKeyIpsStore, enforces IP-based gating in middleware for requests using the read-only master key (403 on disallowed IPs), adds security checks, tests, and a deprecation entry.

Changes

Cohort / File(s) Summary
Options & Docs
src/Options/Definitions.js, src/Options/docs.js, src/Options/index.js
Add public option readOnlyMasterKeyIps (env PARSE_SERVER_READ_ONLY_MASTER_KEY_IPS), parser/defaults (["0.0.0.0/0","::0"]) and extended documentation describing formats, CIDR, IPv4/IPv6, special cases and environment usage.
Config Validation
src/Config.js
validateOptions now accepts readOnlyMasterKeyIps and calls this.validateIps('readOnlyMasterKeyIps', readOnlyMasterKeyIps).
Server Runtime
src/ParseServer.ts
Initialize and expose readOnlyMasterKeyIpsStore (Map) on server config.
Middleware Enforcement
src/middlewares.js
Enforce IP-based restriction for requests using the read-only master key: check client IP against readOnlyMasterKeyIps/store, treat 0.0.0.0/0, ::/0, ::0 as wildcards, set req.auth.isReadOnly when allowed, log and throw 403 when disallowed.
Security Checks
src/Security/CheckGroups/CheckGroupServerConfig.js
New check "Read-only master key IP range restricted": when readOnlyMasterKey enabled, disallow permissive wildcards in readOnlyMasterKeyIps.
Tests
spec/Middlewares.spec.js, spec/SecurityCheckGroups.spec.js
Add tests for read-only master key IP allow/deny/wildcard cases and include readOnlyMasterKey/readOnlyMasterKeyIps in security-check test scenarios.
Deprecations
DEPRECATIONS.md, src/Deprecator/Deprecations.js
Add deprecation entry for readOnlyMasterKeyIps with new-default migration note (["127.0.0.1","::1"]).

Sequence Diagram

sequenceDiagram
    participant Client
    participant Middleware
    participant ConfigStore as readOnlyMasterKeyIpsStore

    Client->>Middleware: HTTP request with read-only master key
    Middleware->>Middleware: detect read-only master key usage
    alt read-only master key used
        Middleware->>ConfigStore: check client IP against allowed list
        alt IP allowed (explicit or wildcard)
            ConfigStore-->>Middleware: allowed
            Middleware->>Middleware: set req.auth.isReadOnly = true
            Middleware-->>Client: continue normal request processing
        else IP not allowed
            ConfigStore-->>Middleware: not allowed
            Middleware->>Client: respond 403 Forbidden (throw)
        end
    else not using read-only master key
        Middleware-->>Client: continue normal flow
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Moumouls
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new server option readOnlyMasterKeyIps to restrict the readOnlyMasterKey by IP address.
Description check ✅ Passed The description includes the required issue section, approach section with clear explanation, and completed tasks checklist reflecting the work done in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

@codecov
Copy link

codecov bot commented Mar 6, 2026

Codecov Report

❌ Patch coverage is 93.75000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.69%. Comparing base (9f8d3f3) to head (cd282d5).
⚠️ Report is 8 commits behind head on alpha.

Files with missing lines Patch % Lines
src/Security/CheckGroups/CheckGroupServerConfig.js 83.33% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            alpha   #10115   +/-   ##
=======================================
  Coverage   92.69%   92.69%           
=======================================
  Files         191      191           
  Lines       15883    15898   +15     
  Branches      180      180           
=======================================
+ Hits        14722    14737   +15     
  Misses       1149     1149           
  Partials       12       12           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8f9508c-83d8-4be0-8878-e42be2b8f0f3

📥 Commits

Reviewing files that changed from the base of the PR and between 59ec921 and 01775e2.

📒 Files selected for processing (7)
  • spec/Middlewares.spec.js
  • src/Config.js
  • src/Options/Definitions.js
  • src/Options/docs.js
  • src/Options/index.js
  • src/ParseServer.ts
  • src/middlewares.js

Copy link

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

🧹 Nitpick comments (1)
src/middlewares.js (1)

273-281: Extract the privileged-key IP rejection path into a shared helper.

This new branch now duplicates the masterKeyIps deny/log/throw flow almost verbatim. Keeping these separate makes it easy for one key type to drift from the others the next time auth handling changes.

♻️ Possible refactor
-    if (!checkIp(clientIp, req.config.readOnlyMasterKeyIps || [], req.config.readOnlyMasterKeyIpsStore)) {
-      const log = req.config?.loggerController || defaultLogger;
-      log.error(
-        `Request using read-only master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'readOnlyMasterKeyIps'.`
-      );
-      const error = new Error();
-      error.status = 403;
-      error.message = 'unauthorized';
-      throw error;
-    }
+    assertPrivilegedKeyIpAllowed({
+      clientIp,
+      ipRangeList: req.config.readOnlyMasterKeyIps || [],
+      store: req.config.readOnlyMasterKeyIpsStore,
+      keyLabel: 'read-only master key',
+      optionName: 'readOnlyMasterKeyIps',
+      config: req.config,
+    });
function assertPrivilegedKeyIpAllowed({ clientIp, ipRangeList, store, keyLabel, optionName, config }) {
  if (checkIp(clientIp, ipRangeList, store)) {
    return;
  }
  const log = config?.loggerController || defaultLogger;
  log.error(
    `Request using ${keyLabel} rejected as the request IP address '${clientIp}' is not set in Parse Server option '${optionName}'.`
  );
  throw createSanitizedHttpError(403, 'unauthorized', config);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middlewares.js` around lines 273 - 281, The read-only master key IP
rejection logic duplicates the masterKeyIps deny/log/throw flow; extract this
into a shared helper (e.g., assertPrivilegedKeyIpAllowed) and replace the
duplicated branches to call it. The helper should accept clientIp, ipRangeList
(readOnlyMasterKeyIps or masterKeyIps), store (readOnlyMasterKeyIpsStore),
keyLabel and optionName, use checkIp to validate, log via
config?.loggerController || defaultLogger on failure, and throw a sanitized 403
'unauthorized' error (use createSanitizedHttpError or the existing error
creation pattern) so both the read-only and master key checks share the same
deny/log/throw behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/middlewares.js`:
- Around line 273-281: The read-only master key IP rejection logic duplicates
the masterKeyIps deny/log/throw flow; extract this into a shared helper (e.g.,
assertPrivilegedKeyIpAllowed) and replace the duplicated branches to call it.
The helper should accept clientIp, ipRangeList (readOnlyMasterKeyIps or
masterKeyIps), store (readOnlyMasterKeyIpsStore), keyLabel and optionName, use
checkIp to validate, log via config?.loggerController || defaultLogger on
failure, and throw a sanitized 403 'unauthorized' error (use
createSanitizedHttpError or the existing error creation pattern) so both the
read-only and master key checks share the same deny/log/throw behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55b64466-308b-4439-ad25-d9703aa31abb

📥 Commits

Reviewing files that changed from the base of the PR and between 01775e2 and afbbc21.

📒 Files selected for processing (1)
  • src/middlewares.js

Copy link

@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 (1)
spec/SecurityCheckGroups.spec.js (1)

50-50: Avoid coupling these assertions to check index 8.

This PR had to update the expected position as soon as one more check was inserted. Looking up the check by its title/identity instead of array index would make the spec resilient to future additions or reordering.

Also applies to: 72-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/SecurityCheckGroups.spec.js` at line 50, The test is brittle because it
asserts on group.checks()[8] by index; instead locate the check by a stable
identifier (e.g., title or id) and assert its state. Update the assertions that
reference group.checks()[8] (and the similar one around line 72) to find the
check via group.checks().find(c => c.title === 'EXPECTED_TITLE' || c.id ===
'EXPECTED_ID') and then call .checkState() on that found check and compare to
CheckState.success; ensure you handle the case where find returns undefined with
a clear test failure message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Security/CheckGroups/CheckGroupServerConfig.js`:
- Around line 130-132: The current check in CheckGroupServerConfig.js uses
wildcards = ['0.0.0.0/0','::/0','::','::0'] and then tests ips.some(ip =>
wildcards.includes(ip)), which misses the plain '0.0.0.0' sentinel; update the
wildcard detection to reject plain IPv4 allow-all too by adding '0.0.0.0' to the
wildcards array (or normalize entries to strip CIDR suffixes and compare
canonical forms) so the ips.some(...) check in the readOnlyMasterKeyIps
validation correctly flags both '0.0.0.0' and '0.0.0.0/0'.

---

Nitpick comments:
In `@spec/SecurityCheckGroups.spec.js`:
- Line 50: The test is brittle because it asserts on group.checks()[8] by index;
instead locate the check by a stable identifier (e.g., title or id) and assert
its state. Update the assertions that reference group.checks()[8] (and the
similar one around line 72) to find the check via group.checks().find(c =>
c.title === 'EXPECTED_TITLE' || c.id === 'EXPECTED_ID') and then call
.checkState() on that found check and compare to CheckState.success; ensure you
handle the case where find returns undefined with a clear test failure message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 294f6bae-fed6-4ffb-bb1d-249cfc4ff7c1

📥 Commits

Reviewing files that changed from the base of the PR and between e880fcd and 2fd508f.

📒 Files selected for processing (2)
  • spec/SecurityCheckGroups.spec.js
  • src/Security/CheckGroups/CheckGroupServerConfig.js

@mtrezza mtrezza merged commit cbff6b4 into parse-community:alpha Mar 6, 2026
23 of 24 checks passed
parseplatformorg pushed a commit that referenced this pull request Mar 6, 2026
# [9.5.0-alpha.12](9.5.0-alpha.11...9.5.0-alpha.12) (2026-03-06)

### Features

* Add server option `readOnlyMasterKeyIps` to restrict `readOnlyMasterKey` by IP ([#10115](#10115)) ([cbff6b4](cbff6b4))
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 9.5.0-alpha.12

@parseplatformorg parseplatformorg added the state:released-alpha Released as alpha version label Mar 6, 2026
@mtrezza mtrezza deleted the feat/readOnlyMasterKeyIps branch March 6, 2026 18:16
parseplatformorg pushed a commit that referenced this pull request Mar 7, 2026
# [9.5.0](9.4.1...9.5.0) (2026-03-07)

### Bug Fixes

* `PagesRouter` path traversal allows reading files outside configured pages directory ([GHSA-hm3f-q6rw-m6wh](GHSA-hm3f-q6rw-m6wh)) ([#10104](#10104)) ([e772543](e772543))
* Endpoint `/loginAs` allows `readOnlyMasterKey` to gain full read and write access as any user ([GHSA-79wj-8rqv-jvp5](GHSA-79wj-8rqv-jvp5)) ([#10098](#10098)) ([bc20945](bc20945))
* File creation and deletion bypasses `readOnlyMasterKey` write restriction ([GHSA-xfh7-phr7-gr2x](GHSA-xfh7-phr7-gr2x)) ([#10095](#10095)) ([036365a](036365a))
* File metadata endpoint bypasses `beforeFind` / `afterFind` trigger authorization ([GHSA-hwx8-q9cg-mqmc](GHSA-hwx8-q9cg-mqmc)) ([#10106](#10106)) ([72e7707](72e7707))
* GraphQL `__type` introspection bypass via inline fragments when public introspection is disabled ([GHSA-q5q9-2rhp-33qw](GHSA-q5q9-2rhp-33qw)) ([#10111](#10111)) ([61261a5](61261a5))
* JWT audience validation bypass in Google, Apple, and Facebook authentication adapters ([GHSA-x6fw-778m-wr9v](GHSA-x6fw-778m-wr9v)) ([#10113](#10113)) ([9f8d3f3](9f8d3f3))
* Malformed `$regex` query leaks database error details in API response ([GHSA-9cp7-3q5w-j92g](GHSA-9cp7-3q5w-j92g)) ([#10101](#10101)) ([9792d24](9792d24))
* Regular Expression Denial of Service (ReDoS) via `$regex` query in LiveQuery ([GHSA-mf3j-86qx-cq5j](https://github.com/parse-community/parse-server/security/advisories/GHSA-mf3j-86qx-cq5j)) ([#10118](#10118)) ([5e113c2](5e113c2))

### Features

* Add `Parse.File` option `maxUploadSize` to override the Parse Server option `maxUploadSize` per file upload ([#10093](#10093)) ([3d8807b](3d8807b))
* Add security check for server option `mountPlayground` for GraphQL development ([#10103](#10103)) ([2ae5db1](2ae5db1))
* Add server option `readOnlyMasterKeyIps` to restrict `readOnlyMasterKey` by IP ([#10115](#10115)) ([cbff6b4](cbff6b4))
* Add support for `Parse.File.setDirectory`, `setMetadata`, `setTags` with stream-based file upload ([#10092](#10092)) ([ca666b0](ca666b0))
* Allow to identify `readOnlyMasterKey` invocation of Cloud Function via `request.isReadOnly` ([#10100](#10100)) ([2c48751](2c48751))
* Deprecate GraphQL Playground that exposes master key in HTTP response  ([#10112](#10112)) ([d54d800](d54d800))
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 9.5.0

@parseplatformorg parseplatformorg added the state:released Released as stable version label Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:released Released as stable version state:released-alpha Released as alpha version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants