v5.1.0 - File URL Hardening, Locks, Image Embeddings
Feature Release
Hardens Parse::File so signed URLs never persist in @url, promotes the Redis-backed lock to a public Parse::Lock primitive, scopes LiveQuery to ACL by default, adds URL-based image embeddings (embed_image) for Voyage and Cohere multimodal models, tenant-aware cache namespacing, and a new canonical ACL/CLP reference guide. Several Parse.setup / LiveQuery client-setup bugs are fixed along the way.
Breaking Changes
- BREAKING: LiveQuery connections are now ACL-scoped by default. The connect frame no longer carries the master key merely because one is configured — Parse Server resolves master-key (ACL/CLP-bypass) authorization once per connection from the connect frame, so a
Parse.setup(master_key: …)process previously elevated every session-token subscription the caller believed was ACL-scoped. To restore the admin/event-tap behavior, build an explicit admin connection withParse::LiveQuery::Client.new(use_master_key: true)orParse::LiveQuery.configure { |c| c.use_master_key = true }. Admin connections emit a one-time[Parse::LiveQuery:SECURITY]warning at connect.
Changes
Parse::File: presigned-URL stash and leak hardening
- NEW:
Parse::File#url=and#attributes=route every write through a single normalization point. When an incoming URL carries a recognized signed-URL parameter (X-Amz-Signature,X-Amz-Credential,X-Amz-Security-Token,AWSAccessKeyId,Key-Pair-Id), the query string is stripped, the bare canonical URL is stored in@url, and the original signed URL is stashed with a data-driven expiry parsed from the URL itself (SigV4X-Amz-Date + X-Amz-Expires, or SigV2 / CloudFrontExpires). - NEW:
Parse::File#presigned_url,#presigned_url_expires_at, and#presigned_url_valid?(buffer: 60)expose the stashed signed URL and gate refetches. The stash is invalidated automatically on any URL reassignment, so callers are never handed a value staler than@url. - NEW:
Parse::File.signed_url_policy(:stripdefault, or:raisefor strict mode),Parse::File::SignedUrlError, andParse::File.parse_presigned_expiry(url)for caller-side detection and enforcement. - NEW:
Parse::File.log_filter,.log_filter_strict, and.filter_parameter_namesproduce ready-to-use regexes forlograge/semantic_logger/ error-reporter scrubbers andRails.application.config.filter_parameters, so an accidentalRails.logger.info(file_url)does not leak read capability into log aggregators. - FIXED:
Parse::File#saved?,Hash#parse_file?, andParse::File#savenow strip the URL query string before basename comparison, so the short-TTL presigned URLs Parse Server'sS3FilesAdapterreturns on read and on create no longer cause false negatives or bake signature bytes into@name. - CHANGED:
Parse::File#inspectno longer includes the URL. The new format emits@name,@mime_type,@contents(presence), and@url=set|blank— enough to debug, none of the URL content.#to_sis deliberately unchanged so ERB and<img src="<%= file %>">callers keep working.
Parse::Lock: public TTL-bounded mutual-exclusion primitive
- NEW:
Parse::Lock.acquire(key, ttl:, wait:, on_degraded:) { |token| … }exposes the Redis-backed lock previously hidden insidefirst_or_create!as a first-class, block-form primitive. TTL-bounded (1..30s, default 3s), with an in-processMutexfallback for process-local caches, fails closed, and hashes keys with SHA-256 before they reach the store. - NEW:
Parse::Lock.acquire(secret:)HMAC keying picks up the operator-configuredPARSE_STACK_LOCK_SECRET/Parse.synchronize_create_secret, so one secret hardens bothParse::LockandParse::CreateLock. Explicitsecret:values are length-validated againstParse::Lock::SECRET_MIN_BYTES. - NEW:
Parse::Lock::TimeoutError/UnavailableError(both inheritParse::Error), and the sharedParse::LockBackend(@api private) now consumed by bothParse::LockandParse::CreateLock, eliminating the previous.send(:private)coupling.Parse::Lockis autoloaded. - FIXED: lock release is now an atomic compare-and-delete via a Lua script (
Parse::Cache::Redis#lock_acquire/#lock_release), so a holder whose lease expired and was re-acquired by another holder can no longer delete the new holder's live lock. - FIXED:
Parse::Lock.acquireno longer over-promises exactly-once execution — the contract is documented as mutual exclusion with a deadline, the block receives its owner token for fencing, and a warning is emitted on release when the critical section overran its TTL.
LiveQuery: ergonomics
- NEW:
Parse::LiveQueryis autoloaded; the autoload opens no socket (one only opens when LiveQuery is enabled and a client is instantiated). - NEW:
Parse::LiveQuery::Client.new(use_master_key: true), theconfig.use_master_keytoggle, andClient#admin_connection?make the ACL-bypassing posture explicit and inspectable. - NEW:
Klass.subscribe/Query#subscribe/Client#subscribeaccept a block yielded theSubscriptionbefore the subscribe frame is sent, so callbacks are wired before any server event can arrive. If the block raises, the subscription is rolled back out of the client registry before the exception propagates. - NEW:
Parse::LiveQuery.run_until_signal!(client:, signals:, …) { |client| … }is a signal-safe shutdown helper for long-running subscribe sessions that avoids theThreadError: can't be called from trap contextraised by callingclient.closedirectly from aSignal.trapblock. - CHANGED:
use_master_key:on a subscribe call is now an intent assertion, not a per-subscription wire credential — the subscribe frame never carriesmasterKey. On a non-admin connection it warns once and the subscription stays ACL-scoped. - FIXED:
Client#inspectandSubscription#inspectredact@master_key,@client_key, and@session_tokeninstead of dumping them in plaintext.SubscriptionErrornow carries structuredrequest_id/class_namecontext.
Image embedding: embed_image DSL + Voyage / Cohere multimodal (URL-only)
- NEW:
embed_image source_field, into: :vector_property, …class macro mirrorsembedfor:file-typed sources. Onbefore_saveit extracts the file URL, validates it, and embeds — the digest is the SHA-256 of the URL (resaving the same URL is a no-op; pointing at a different URL re-embeds). - NEW:
Parse::Embeddings::Voyage#embed_image(voyage-multimodal-3) andParse::Embeddings::Cohere#embed_image(embed-v4.0) route image URLs to each provider's multimodal endpoint. URL-only — the SDK does not download image bytes in this release. Text-only models raise a clearBadRequestErrorbefore any network call. - NEW:
Parse::Embeddings.validate_image_url!is the canonical SSRF-hardened URL validator — sentinel gate,https://scheme, no userinfo, obfuscated-IP rejection, host allowlist (empty denies everything), port check, and CIDR resolution delegated toParse::File.assert_host_allowed!. - NEW:
Parse::Embeddings.trust_provider_url_fetch=(frozen-string sentinel"PROVIDER_EGRESS_VERIFIED") andParse::Embeddings.allowed_image_hosts=gate URL forwarding to providers, mirroring theacl: :offsentinel pattern so accidental enablement viaENVinterpolation is impossible.
MCP: structuredContent for 5 more tools
- NEW:
output_schemadeclarations onaggregate,export_data,atlas_text_search,atlas_autocomplete, andatlas_faceted_searchso the dispatcher auto-mirrors their result Hash intostructuredContentper MCP 2025-06-18, bringing built-in tool coverage to sixteen.
Caching: tenant-aware namespacing
- NEW:
Parse.with_cache_tenant(scope) { … }/Parse.current_cache_tenantcompose a tenant into the cache key as<base-namespace>:T:<tenant>:…, giving a multi-tenant app sharing one Redis per-tenant key isolation and per-tenant SCAN-eviction without per-tenant client plumbing. Fiber-local, restored on block exit even when the block raises; carries:cache_tenanton theparse.cache.*ActiveSupport::Notificationspayload.
Parse::Installation / Parse::User associations and field visibility
- NEW:
Parse::Installation belongs_to :userandParse::User has_many :installationsexpose the installationuserpointer for targeted push grouping (master-key only on thehas_manyside, since Parse Server hardcodes_Installationfind to master-key). - NEW:
Parse::User.master_only_fields(*fields)andParse::User.self_visible_fields(*fields, via: :self)declare field visibility on_User, with one-time advisories documenting theprotectedFieldsOwnerExempt: falseserver-option and self-pointer prerequisites. - NEW: One-time CLP advisory on
Parse::Installationenumerating which operations CLP actually controls, since Parse Server hardcodesfind/deleteto master-key and gatescreate/updateon the installation-id header rather than CLP.
Client setup fixes
- FIXED:
Parse.setupsilently no-op'd on every call after the first (it routed throughParse::Client.new's||=default-client guard). It now delegates toParse::Client.setup, so re-configuring the default client works without manually clearing the:defaultslot. - FIXED: Passing
live_query_url:/live_query: { … }toParse.setupno longer raisesArgumentError— the configuration is now applied through the block form. Top-levellive_query_url:correctly wins overlive_query: { url: … }, an explicitws://URL against a non-loopback host is refused unlessallow_insecure: true, and unknownlive_query:keys now warn instead of being silently dropped.
Documentation
- NEW:
docs/acl_clp_guide.mdis the canonical reference for ACL, CLP,protectedFields, role hierarchy, and field-guard write protection — covering the five enforcement layers, the system-class CLP matrix, the_Installationhardcoded asymmetry, the_Userfield-visibility recipe, and the REST-aggregate vs mongo-direct enforcement asymmetry. The client SDK guide and YARD@notes on the master-key-only system classes were updated to match.
Commit: 456373c
Author: Adrian Curtin
Date: June 3, 2026