Repository navigation
v5.8.2 - Batch Credentials, Regex Checks, Agent and Webhook Trust - #63
Merged
Merged
Conversation
A whitespace-only MCP API key now counts as no key, so a public bind refuses to start and requests never authenticate against a blank key. HTTPS and WebSocket scheme checks parse the URL, so HTTP://, HTTPS:// with TLS verification off, and WS:// are caught. LiveQuery validates an explicit or configured URL like a derived one. Parse::Response#inspect shows only status and shape, and to_s filters credential fields.
A session-token agent whose token did not resolve at construction had a nil scope, which the SDK-side CLP check read as master. It now retries resolution on first use and raises Parse::Agent::UnresolvedIdentity if it still cannot resolve. Agent#inspect, to_s, and pp print a redacted summary without the token, client keys, conversation, or last request/response.
A cached answer was served without contacting Parse Server, so a revoked session, or a user who lost role or row access, kept reading cached rows until the entry expired. The caching middleware, which sees the final session for explicit, ambient, and client-bound sessions, now bypasses session reads unless cache_session_requests is enabled. Master-key and anonymous reads cache as before.
Raw $regex in REST where hashes (eq, not, $elemMatch, OR branches) and every mongo-direct filter and pipeline (Regexp, BSON regex, $regexMatch family) now run the ReDoS check, with $options limited to imxsu. SDK-built escaped literal patterns are always accepted. The agent constraint translator applies the check too. Parse::MongoDB.find raises ACLRequired inside without_master_key; indexes stays available as metadata.
Under allow_unauthenticated with no signature, a body claiming master no longer passes master-only field guards, ACL owner resolution, or handler payload.master? checks. payload.claimed_master? keeps the raw claim for Ruby-initiated callback dedup, and the string "false" is not master.
Parse Server runs every /batch sub-request under the outer call's credentials, and the SDK sent all batched writes through the default client, so writes built for a user session ran as master. Requests now carry their client and explicit session options; a batch with mixed credentials is sent as separate calls in request order, and a transaction mixing credentials raises MixedAuthorityError before sending. Array#save, Array#destroy, and transaction accept session:.
Bump the version and add the 5.8.2 CHANGELOG entry.
… fingerprint Batched requests resolve credentials exactly as a single request does: session_token/use_master_key options, the X-Parse-Session-Token header, and the master-key suppression header, which wins over use_master_key. Direct Parse::Client#batch_request calls get the same grouping and mixed-transaction refusal. Clients are compared by a credential fingerprint instead of object identity, so classes holding identically configured clients after a second Parse.setup share one transaction.
Agent and Parse::Client as_json, to_json, and to_yaml return a redacted summary. A sub-agent holding its parent's token reuses the parent's scope, and a failed different token reports UnresolvedIdentity rather than a widening. Scope resolution, impersonate, and refresh_scope! share one reentrant lock; acl_scope checks and reads under it and acl_permission_strings never returns nil for a session agent. Failed lazy resolution backs off for 5 seconds, and an invalid token gets its own message. Client also gains the TLS, loopback, LiveQuery URL, and per-client cache_session_requests changes from the transport review.
Parse::File trusted-host checks parse the scheme and treat malformed http(s) URLs as untrusted. Redaction covers MFA recovery codes, secrets, and storage-form credential columns, and Response error text is redacted and escaped. The TLS guard refuses verify_mode/verify_hostname and SSLOptions forms. MCP keys strip Unicode whitespace and warn when short on a public bind. LiveQuery maps http/https to ws/wss with a warning. Webhook endpoints warn on cleartext http, and the cache bypass event carries cache_tenant.
RegexSecurity tokenizes each caller pattern and refuses groups repeated more than once whose body has a variable-count quantifier (including ?), an alternation, or a backreference, plus (?# comments and extended mode. Single-atom repeats and simple lookarounds are accepted. Escaped literals get a longer cap, $regexMatch-family operands must be literal, and one options allowlist (imsu) is shared by every gate.
A forged _RB_ request id with master: true on unauthenticated ingress no longer skips model callbacks, including a rejecting before_save. claimed_master? is diagnostic only.
…options A client chosen with BatchOperation#client= or as the batch_request receiver is used for every request, as for a single request; an object's class client applies only to implicit batches. use_master_key counts only when exactly true or false, a session that resolves to no token fails closed, and narrowing call options override a request's own use_master_key: true. Fingerprints use a per-process HMAC key and are memoized per submit; transaction(session:) ignores master keys and bound tokens when grouping. Session deletes look up rows as the deleting user, and a successful delete of a row that user cannot read still invalidates its recorded owner or falls back to the rate-limited reset.
Construction-time resolution failures are recorded, a rejected token is not retried, and lookups run outside the scope lock with compare-and-bind. Sub-agents reuse a parent scope only from a locked snapshot. stop_impersonating! is a no-op unless impersonating. Client and Agent refuse Marshal.dump, faraday: ConnectionOptions get the TLS and proxy guards, loopback_host? parses addresses, and the unused LiveQuery loopback constant is removed.
…whole File URLs drop tabs and newlines, strip leading control characters, check scheme-relative values against trusted_url_hosts, and treat non-http(s) schemes as untrusted; trust_legacy_tfss_on_any_host can turn off the tfss exception. Redaction runs on string values before encoding, covers header-shaped text, and takes quoted values whole, so Response#to_s and logged JSON stay valid and leak no quoted secrets.
The parser reads POSIX brackets like PCRE2, refuses \g<n> subroutine calls, more than three wide quantifiers in a row, and spaced repeat counts, and allows fixed-literal alternations and separator runs whose branches cannot overlap even ignoring case. Regexp and BSON values under $not/$in/$nin/$all are checked on REST. Mongo-direct reads carrying a non-literal regex get a default max_time_ms of 5000 ms (PipelineSecurity.default_regex_max_time_ms).
…laims The test Parse Server gets PARSE_SERVER_WEBHOOK_KEY and the webhook integration tests set Parse::Webhooks.key instead of allowing unauthenticated ingress. claimed_master? accepts only a JSON true, and signature verification and the authenticated flag come from one read of the signing secret. Removes a stale LiveQuery note and unused constant.
normalize_api_key strips Unicode whitespace with a linear scan from each
end instead of an alternation that rescans every position, and the
regex parser reads {n,m} counts with a linear scanner instead of an
optional-comma pattern that backtracks on long digit runs. Clears
CodeQL rb/polynomial-redos alerts on both.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release 5.8.2: a security and correctness patch scoped from the 5.8.1 backlog survey. It fixes batch writes that ran with the master key, agents that fell back to master posture when their session could not be resolved, session reads served from the response cache after revocation, ReDoS checks that most regex paths skipped, and webhook master trust on unauthenticated ingress, plus transport and redaction gaps found in review. No new features beyond the
session:arguments and the cache opt-in the fixes need; intentional master-key and analytics access is unchanged.Batches and transactions
POST /batchunder the outer call's credentials.Array#save,Array#destroy,Parse.batch, andtransactionsent everything through the default client, so writes built for a user session ran as master. Each write now goes out under the credentials it would have as a single request (options, session header, master-key suppression header, class client, ambient session), grouped by a credential fingerprint.BatchOperation#client=or thebatch_requestreceiver) is used for every request. Mixed-credential non-transactional batches are split per group with responses in order; a transaction that mixes credentials raisesMixedAuthorityErrorbefore anything is sent.use_master_keycounts only when exactlytrueorfalse, and a session that resolves to no token fails closed.Array#save,Array#destroy, andtransactionacceptsession:.Agents
Parse::Agent::UnresolvedIdentity.impersonate, andrefresh_scope!are consistent under concurrency; sub-agents reuse a parent scope only from a locked snapshot;stop_impersonating!is a no-op unless impersonating.AgentandClientinspect,to_s,as_json,to_json, andto_yamlare redacted, and both refuseMarshal.dump.Response cache
cache_session_requests(globally or per client). Master-key and anonymous caching is unchanged.Regex and direct reads
$regexin REST where hashes and every mongo-direct filter and pipeline now go throughParse::RegexSecurity, which parses each pattern instead of matching substrings: it refuses nested, alternation, optional, and subroutine repeats, long runs of wide quantifiers, comments, and extended mode, and accepts single-atom repeats, simple lookarounds, and non-overlapping alternations and separator runs.max_time_ms.Parse::MongoDB.findraisesACLRequiredinsidewithout_master_key.Webhooks
masterclaim is trusted only on authenticated ingress (webhook key or verified signature). Forged claims underallow_unauthenticatedno longer pass master-only guards, ACL owner resolution, handler checks, or callback dedup.Transport and redaction
verify_mode,verify_hostname,SSLOptions, andConnectionOptions. LiveQuery URLs are validated, andhttp(s)://maps tows(s)://with a deprecation warning.Parse::FileURLs are normalized the way a browser reads them and checked againsttrusted_url_hosts, including scheme-relative and non-http(s) values.Response#to_sstays valid JSON.Behavior changes for upgrading apps
CHANGELOG.md lists each under Behavior Notes. The ones most likely to need changes:
agent.acl_scopefor truthiness should useagent.acl_scope?.cache: trueno longer hit the response cache.$optionsacceptsi,m,s,u.allow_unauthenticatedwith no signing secret,payload.master?is false; configure a webhook key or signing secret.Response#to_sfilters credential values; read#resultfor raw data.Testing
bundle exec rake test:unitpasses.PARSE_SERVER_WEBHOOK_KEY.