QueryProxy sürüm notları
QueryProxy’nin güncel sürümü 0.4.1 (4 Kasım 2026). Bu sayfa yayımlanmış 18 sürümün tamamının notlarını, en yenisi üstte olacak şekilde listeler.
Sürüm notları ürün deposundan olduğu gibi, İngilizce olarak aktarılır.
Sürüm 0.4.1
GitHub’da görüntüle (0.4.1) (harici site)
Added
- External secret references for connection passwords (ADR-028): a
connection's password can be
vault://<mount>/<path>#<key>,aws-sm://<secret-id>[#<json-key>]orazure-kv://<vault-name>/<secret-name>[/<version>]instead of a value. It is resolved when the connection is opened and never stored. HashiCorp Vault (KV v2/v1; token, AppRole, Kubernetes auth), AWS Secrets Manager (default credential chain, IRSA included) and Azure Key Vault (client secret, workload identity, managed identity) are supported. Resolution is fail-closed: the request fails with a generic message and the newsecret.resolve_failedaudit event records the reference, never the value. Values are cached in process memory only (QUERYPROXY_SECRET_CACHE_TTL, default 300 s). A system admin sets the reference prefixes each team may use in Admin > Teams (audited, with an overlap warning) or withqueryproxy:team:secret-prefixes; a team without prefixes cannot use references. The connection form offers the credential source (stored password, Vault, AWS Secrets Manager or Azure Key Vault) with a format hint, and the connection list shows the source and the reference, never the password. "Test connection" resolves the reference;queryproxy:doctorchecks the store settings. qp, the command-line client for the REST API (separate repositoryQueryProxy/qp, ADR-030):qp submit --waitfiles a request, waits for approval and prints the result;qp status,qp results. Static binaries for Linux, macOS and Windows (amd64, arm64), checksums signed keylessly with cosign. CI gains an end-to-end job that runsqpagainst a live server.- Logging out of an SSO session now also ends the identity provider session
(RP-initiated logout) when the provider publishes an
end_session_endpoint; register<APP_URL>/loginas a post-logout redirect URI at the provider. - Helm chart is now published on every release tag as
oci://ghcr.io/queryproxy/charts/queryproxy:<version>, signed keyless with cosign; the release fails before anything is pushed if the tag differs fromChart.yamlversion/appVersion. Pull requests dry-runhelm package. The chart README documents installing from the registry and verifying the signature. - CockroachDB and YugabyteDB are tested in CI as target databases through
the PostgreSQL driver;
docs/compatibility.mdlists the status (Aurora PostgreSQL, Neon, Supabase: not tested) and the known differences. - SCIM 2.0 provisioning (ADR-026) under
/scim/v2: Users and Groups with filter (userName/externalIdeq), pagination, PATCH and PUT;ServiceProviderConfig,Schemas,ResourceTypes. Off by default (QUERYPROXY_SCIM_ENABLED). Dedicated bearer tokens, stored as SHA-256 only, are managed in Settings > SCIM (system admins: the endpoint URL, the token list with creator, last use and status; a new token's value is shown once, after a fresh password, TOTP or SSO confirmation as for API tokens; revoking) or withphp artisan queryproxy:scim-token create|revoke|list.active=falseandDELETEsuspend the user, never delete;active=truelifts only a suspension SCIM itself set, never one an administrator set. SCIM sees only users it created or SSO-linked users, never local accounts. Group membership appliesQUERYPROXY_OIDC_ROLE_MAP. New audit eventsscim.*. - Schema explorer and smart editor in the Query Studio (ADR-029): a
collapsible side panel lists schemas, tables, views and columns of the
selected connection with search and refresh; columns covered by a masking
rule show a lock, and clicking a name inserts it into the editor. The SQL
editor completes table and column names in the dialect of the connection's
driver (PostgreSQL, MySQL, MariaDB, SQL Server, SQLite), and a "Format"
button (Ctrl/Cmd+Shift+F) reformats the statement. On narrow screens the
panel opens as a drawer.
GET /connections/{connection}/schemareads the catalog through fixed queries in a read-only session with its own short timeout (QUERYPROXY_SCHEMA_TIMEOUT, default 5 s) and without an approval. Only users with access to the connection can read it; on MySQL/MariaDB only the connection's own database is listed. Large catalogs are cut atQUERYPROXY_SCHEMA_MAX_COLUMNS(default 20000) with the default schema kept first. The result is cached per connection (QUERYPROXY_SCHEMA_CACHE_TTL, default 3600 s); a rate-limited manual refresh skips the cache (QUERYPROXY_SCHEMA_REFRESH_LIMIT, default 3 per minute). Each live read is recorded asschema.readin the audit log; reads served from the cache are not. New dependency:sql-formatter, loaded only when formatting. - Slack app: OAuth v2 install (
/integrations/slack/install), bot-token messages that update in place (approved, rejected, cancelled, running, completed, failed), a thread summary with status, row count, duration and link (no SQL), opt-in direct messages to approvers, the/queryproxyslash command with a request modal, Events API handling forapp_uninstalledandtokens_revoked, and a weekly e-mail based identity suggestion scan. The incoming-webhook mode is unchanged. The ChatOps page gains an Add to Slack button, the installed workspace and channel, an opt-in switch for direct messages to approvers and a Remove app action; admins confirm or dismiss the Slack ID suggestions on the Users page. New audit eventsslack.installedandslack.uninstalled(the latter also recordsremoved_by_user). New settings:QUERYPROXY_SLACK_CLIENT_ID,QUERYPROXY_SLACK_CLIENT_SECRET,QUERYPROXY_SLACK_SIGNING_SECRET,QUERYPROXY_SLACK_RATE_LIMIT. Slack endpoints are limited to 120 requests per minute per address before signature verification and to a per-workspace budget (default 300 per minute) after it (ADR-004 amendment); Teams keepsthrottle:60,1. Two migrations makechat_integrations.webhook_urlandsigning_secretnullable.
Changed
- The profile page hides the password form for users without a local password.
usersgetsscim_provisioned_at,scim_external_id,scim_user_nameandsuspended_by_scim; new tablesscim_tokens,scim_groups,scim_group_user(migrations are reversible). Accounts created by SCIM count as SSO users: no local password through reset flows.
Fixed
- On CockroachDB a read request's read-only transaction did not stop DDL
(the server commits before DDL); the read session is now also read-only
session-wide there. "Stop" now works for running statements on
CockroachDB (
CANCEL QUERIES). - Saving a Slack signing secret on a row that has none (an app-only install) no longer asks for a current secret, and a webhook URL can no longer be saved without a signing secret.
Security
- An SSO-linked user can no longer be given a local password through the
admin "Reset password" action or the e-mail reset flow, regardless of
QUERYPROXY_LOCAL_LOGIN.
Sürüm 0.4.0
GitHub’da görüntüle (0.4.0) (harici site)
Step-by-step upgrade and rollback instructions, with the migration list, are
on the upgrading page. Upgrade from
0.3.3, the tested starting point: the container runs five new migrations on
start (2026_10_26_000001, 2026_10_26_000010, 2026_10_26_000011,
2026_10_26_000020 and 2026_10_31_072351), so back up the data volume and
the application database first. Every new setting is optional and every
feature is off or empty until you use it: QUERYPROXY_OIDC_* (single
sign-on), QUERYPROXY_LOCAL_LOGIN, QUERYPROXY_API_TOKEN_MAX_DAYS,
and QUERYPROXY_API_RATE_LIMIT. Keep the scheduler running: it now
removes expired connection grants every minute.
Three changes below are listed as Breaking; read them before you
upgrade.
Breaking (0.4.0)
users.passwordcan be empty. SSO-only accounts have no local password, andusersgainsauth_provider,external_issuerandexternal_id. A script or integration that reads theuserstable directly must expect an empty password. ChangingQUERYPROXY_OIDC_ISSUERlater does not carry existing SSO accounts over: they are tied to the old issuer and are refused rather than taken over by e-mail. No password can be set for an account without one through the reset link. Migration2026_10_26_000001adds the columns; rolling it back stops while such accounts exist.connection_userhas new columns:expires_at,granted_by,reasonandaccess_level. Existing rows stay permanent andwrite. A script that inserts grant rows without them gets the same defaults, and a row whoseexpires_athas passed no longer gives access. Migration2026_10_26_000010adds the columns; rolling it back stops while a timed orreadgrant exists.QUERYPROXY_LOCAL_LOGINis new. The defaultallchanges nothing, but an unknown value (a typo, for example) is treated asadmins_only, which limits password login to system administrators.
Added (0.4.0)
php artisan queryproxy:migrate-appdb --to=<dsn>moves the application database to another server. It is meant for installations that started on the default SQLite file and now want PostgreSQL, MySQL, MariaDB or SQL Server (--to=pgsql://user:password@host:5432/queryproxy; URL-encode special characters in the password). It runs the migrations on the target, copies every table in foreign key order in chunks (ids, audit hashes and the chain head unchanged, PostgreSQL sequences reset), then compares the row count of every table and runsaudit:verifyon the target. The audit triggers (ADR-020) are never dropped: they are installed by the migrations before the copy, which only inserts. The whole copy is one transaction, so a failure leaves a migrated, empty target you can retry. The target must be empty (a database that was only migrated is fine);--dry-runchecks everything and writes nothing; without--forcethe command asks you to confirm that QueryProxy is stopped. The target password is never printed. Thecachetables are not copied. After the move, switchDB_CONNECTIONand theDB_*settings and start QueryProxy again.- An image with SQL Server support:
queryproxy/queryproxy:<version>-mssqland:mssql. It adds the Microsoft ODBC Driver 18 and thepdo_sqlsrvextension (build it yourself with--build-arg INSTALL_SQLSRV=1).:mssqlmoves only with stable releases and never takeslatest; a pre-release publishes its-mssqlversion tag only. The variant is built forlinux/amd64andlinux/arm64(Microsoft ships the driver for Alpine on both), scanned, signed and attested like the default image. The Microsoft driver is under Microsoft's own licence (https://aka.ms/odbc18eula); using the variant means accepting it. The default image is unchanged. - SQL Server is now supported as the application database. The
application schema could not be created there: SQL Server refuses foreign
keys that give a table more than one cascade path, and the schema had
several. On SQL Server only, eight foreign keys are created with
ON DELETE NO ACTION(query_requests.team_id,masking_rules.team_id,connection_grant_requests.team_id,connections.created_by,connection_user.granted_by,connection_grant_requests.decided_by, and the twoquery_requestskeys to users that areRESTRICTon every driver since ADR-018), and the application does what the database would have done on a delete: deleting a team removes its requests, masking rules and grant requests first, and hard deleting a user who has no requests sets the keys that referenced them to NULL. Raw SQL that deletes a team or user is refused on SQL Server only while rows still point at it through one of those eight keys (for a team: requests, masking rules or grant requests; for a user: connections it created, grants it gave, grant requests it decided, or requests it filed or reviewed). For the six keys that cascade or set NULL elsewhere, the delete is refused there instead of cascading or setting NULL; the two request keys to users are refused on every driver. Nothing changes on PostgreSQL, MySQL, MariaDB or SQLite: no new migration, no change to an existing installation, same delete results. Use the:mssqlimage (the default image has nopdo_sqlsrv);php artisan queryproxy:migrate-appdb --to="sqlsrv://user:password@host:1433/queryproxy?trust_server_certificate=true"moves an installation there. - CI tests the upgrade from the previous release. The new
upgradejob in the Integration workflow creates data on the 0.3.3 image, starts the image built from the branch on the same data volume, and checks that the data is still there, thataudit:verifypasses across the upgrade, and that/upand the login page answer. - Single sign-on through OpenID Connect, with team roles from IdP groups.
Set
QUERYPROXY_OIDC_ISSUERandQUERYPROXY_OIDC_CLIENT_ID(andQUERYPROXY_OIDC_CLIENT_SECRET, orQUERYPROXY_OIDC_CLIENT_SECRET_FILE) and the login page offers "Log in with SSO". It works with any provider that publishes discovery (Keycloak, Entra ID, Okta, Google, Authentik): authorization code flow with PKCE,stateandnonce, and the ID token signature is checked against the provider's keys. Register{APP_URL}/auth/oidc/callbackas the redirect URI. The first sign-in creates the user, and an existing local account with the same e-mail address is linked, but only when the provider says the address is verified (email_verified); otherwise the sign-in is refused and the refusal is audited (auth.sso_refused). Entra ID does not sendemail_verified; there the optionalxms_edovclaim (add it to the app registration's token configuration) counts instead, but only whenemail_verifiedis absent, and an explicitemail_verified: falsealways wins. A user is identified by issuer and subject together.QUERYPROXY_OIDC_ROLE_MAP(group:team:role, comma-separated) turns the groups inQUERYPROXY_OIDC_GROUP_CLAIM(defaultgroups) into team roles at every sign-in: memberships in the teams the map names are added, changed and removed to match the IdP, and each change is audited (user.role_synced). Memberships in other teams are not touched. While a role map is set, a token without the group claim (including a "groups overage" reference) is refused and nothing is changed; an empty group list is applied as such. Suspended and deleted users cannot sign in through SSO either.QUERYPROXY_OIDC_SCOPESchanges the requested scopes (some providers needgroups). New audit events:auth.sso_login,auth.sso_refused,user.sso_provisioned,user.sso_linked,user.role_synced. Role changes at the IdP apply at the next sign-in. QUERYPROXY_LOCAL_LOGINlimits password login:all(default),admins_onlyornone. Withadmins_onlyonly system admins sign in with a password (break-glass access); withnonepassword login and password reset are off, so make sure SSO works first. An unknown value is treated asadmins_only.- CI signs in to a real Keycloak. The new
oidcjob in the CI workflow imports a test realm and checks the sign-in and the group-to-role sync end to end. - A Helm chart runs QueryProxy on Kubernetes, with an HA reference setup.
deploy/helm/queryproxystarts the one image as separateweb,workerandschedulerDeployments (roles switched withQUERYPROXY_RUN_WORKERandQUERYPROXY_RUN_SCHEDULER); the scheduler is always one replica with theRecreatestrategy. Migrations run as apre-install,pre-upgradehook Job.values-ha.yamlsets up an external PostgreSQL, S3 result storage, several web and worker pods, an Ingress, a PodDisruptionBudget and a NetworkPolicy. The worker can scale on thequeryproxy_queue_depthmetric (HorizontalPodAutoscaler, off by default; a prometheus-adapter example is invalues.yaml).APP_KEYand the database password come from a Secret (your own, for example from the External Secrets Operator, or one the chart creates and keeps), optionally mounted as files (*_FILE). With SQLite the chart renders one all-in-one Deployment (web, worker and scheduler, one replica) and refuses more than one web replica, because a SQLite file cannot be shared between pods; the role split applies with an external database. The chart adds the Service's in-cluster names toTRUSTED_HOSTS, so Prometheus can scrape/metricsthrough the Service. The pods run as a non-root user with all capabilities dropped; the root filesystem is still writable because the image's entrypoint writes to it. The architecture and the options are indeploy/helm/queryproxy/README.md. A newHelmworkflow lints the chart on Helm 3 and 4 and installs it into a kind cluster, where a sample request is approved and run by the worker pod, and an upgrade and a rollback are checked. - The image can store result files on S3 and use Redis. It now includes the
Flysystem S3 adapter (
QUERYPROXY_RESULT_DISK=s3with the usualAWS_*settings, including S3-compatible endpoints) and thepredisRedis client (REDIS_CLIENT=predis, for cache, queue and sessions). Both are optional; the default setup is unchanged. - Time-bound connection grants. A DBA can give a developer access to a
connection for a period ("4 hours, read-only") instead of until someone
remembers to remove it, and a developer can ask for such access with a
reason; a DBA of the team approves or rejects the request. The grant starts
when it is approved. Nobody decides their own request, not even a DBA the
requester became while it was open.
connection_usergainsexpires_at,granted_by,reasonandaccess_level(readorwrite); existing grants stay permanent with full access. Access ends atexpires_at(an expired grant is ignored at once), and the newconnections:expire-grantscommand, scheduled every minute, removes the rows and recordsgrant.expired. Areadgrant cannot file a request the SQL guard classifies as a write or DDL: the studio answers 403. Requests that were already pending or approved when a grant ends are not cancelled. A request must match the grant the developer already holds: a request for another access level than the grant in force, or for any level while a permanent grant of that level exists, is refused when it is filed and when it is approved, so a time-bound approval never replaces or widens standing access. An approval for the same level only extends a time-bound grant; it never shortens it or makes it permanent. To change an existing grant, a DBA edits it directly. DBAs are notified in the app and in the team's Slack/Teams channels (without the free-text reason); the requester is told the decision and the grant that is in force. New audit events:grant.requested,grant.approved,grant.rejectedandgrant.expired(ADR-025). Removing a grant by hand still recordsconnection.grant_revoked, andconnection.grant_addednow carriesaccess_level,expires_atandreasonin its metadata. - Screens for time-bound access. On the dashboard a developer can ask for access to a connection of their team: pick the level (read or write), a period (1 hour, 4 hours, 1 day, 7 days or a custom amount up to 30 days) and give the reason, which is required. A developer who holds a timed grant sees its level and the time left, and can ask to extend it at the same level; the open requests and the decisions of the last seven days, with the DBA's rejection reason, are listed on the same page. The Approvals page has a new "Access requests" tab where a DBA of the team approves a request or rejects it with a reason; nobody can decide their own request, and a request for another level than the grant in force cannot be approved (change the existing grant on the Connections page). On the Connections page the grants panel now sets the level, the period (permanent by default) and an optional reason for each grant, shows each developer's level and remaining time, and replaces or revokes an existing grant. The dashboard now also shows the confirmation after a DBA approves a request from it.
- REST API under
/api/v1(ADR-024): submit a query request, read its status and its masked result (NDJSON, or CSV withAccept: text/csv), approve, reject or cancel it, and list connections. Authentication uses Laravel Sanctum personal access tokens (prefixedqp_) with the scopesrequests:submit,requests:read,results:readandapprovals:write; every token expires (QUERYPROXY_API_TOKEN_MAX_DAYS, default 90) and is revoked when its owner is suspended or deleted. Requests still wait for human approval, an approval that would be an override is refused with403and a link to the web form, and a token can never approve its owner's own request. Column names of a result are result data: an auditor does not get them. A user theQUERYPROXY_REQUIRE_2FApolicy covers cannot create a token until they have enrolled, and an existing token of such a user is answered with401until then; the token is kept and works again afterwards. Each token is limited toQUERYPROXY_API_RATE_LIMITcalls per minute (default 60), and failed authentications are limited per address to the same number. Token creation and revocation are audited astoken.createdandtoken.revoked; API submissions, approvals, rejections, cancellations and result reads carrychannel = apiin the audit log. - OpenAPI 3.1 description of the API in
docs/openapi.yaml, linted by Spectral in CI and checked against the routes by a test. - Users can get request and access-request events by e-mail. On the
Profile page each user chooses which events also arrive by e-mail:
a request waiting for review, a decision on their request, a finished,
failed or stopped request, the pending-request reminder, an access request
waiting for a DBA, and the decision on an access request. Everything is
off by default, so nothing changes until a user turns it on. An e-mail
never carries query results, a reviewer's reason, a decision note or an
error message. The submission and reminder e-mails show the statement with
its values hidden (the same redaction as chat, ADR-017; none with
QUERYPROXY_CHAT_INCLUDE_SQL=none, and never the raw SQL, even withfull). Links open the request in QueryProxy and need a signed-in session; nothing is approved from an e-mail. The e-mail is sent from the queue (thedefaultqueue the worker already listens to); the in-app notification is still written at once. A user who is suspended or deleted, or who turned the event off, before the worker sends the e-mail gets none. A mail failure never blocks the request: it is logged, and on a real queue the job also lands infailed_jobssoqueue:retrycan resend it. WithMAIL_MAILER=logthe Profile page says that nothing is delivered. The migration addsusers.notification_preferences. - Create and revoke API tokens on the Profile page. A new "API tokens"
section lets you name a personal access token, choose its scopes and set
how many days it lasts (required, up to
QUERYPROXY_API_TOKEN_MAX_DAYS, 90 by default), and lists your tokens with their scopes, expiry and last use. Creating a token asks you to confirm first: enter your password (or a code from your authenticator app when two-factor authentication is on), and wrong tries are rate limited. An account without a local password must have signed in through SSO in the last 15 minutes; otherwise the page sends you through SSO again (the provider is asked to sign you in anew) and brings you back. The token value is shown once, right after you create it, with a Copy button; it is never kept in the page state, so a reload or any later action hides it for good, and only its hash is stored. Revoking a token stops it at once. Only the scopes your roles can use are offered:approvals:writeneeds the DBA role in a team,requests:submita DBA or developer role, andresults:readis not offered to auditors. Creating and revoking are audited as before.
Changed (0.4.0)
- A pre-release no longer moves
latest. A pre-release tag such asv0.4.0-rc.1now publishes only its version tag;lateststays on the most recently published stable release. - SSO users are outside
QUERYPROXY_REQUIRE_2FA. Their second factor is the identity provider's, so enforce MFA there. A local account that was linked to the provider and still signs in with its password stays covered.
Security (0.4.0)
- The release workflow refuses tags users could not verify. A tag that
matches the
v*.*.*trigger but not the certificate identity inSECURITY.md(for examplev1.2.3.4orv1.2.3+build) now stops the workflow before anything is built or pushed. UsevX.Y.ZorvX.Y.Z-<pre-release>. - Updated
symfony/mimeto 7.4.22 (andsymfony/polyfill-mbstringto 1.43.3). It fixes CVE-2026-45070 (e-mail header injection through non-token characters in MIME parameter names). QueryProxy does not build e-mail headers or MIME parameter names from user input, so it was not exploitable here; the update clearscomposer audit.
Sürüm 0.3.3
GitHub’da görüntüle (0.3.3) (harici site)
Fixed (0.3.3)
- The Teams webhook field suggests a Workflows URL. Microsoft has
retired Office 365 connectors (see Deprecated in 0.3.0), but the empty
Microsoft Teams field on the ChatOps page still showed a
webhook.office.comexample, which pointed new setups at a retired connector. The placeholder now shows a Power Automate Workflows URL, which receives an Adaptive Card, and the hint under it notes that Office 365 connectors are retired. A legacywebhook.office.comURL you already configured is kept, and QueryProxy still sends it a MessageCard.
Security (0.3.3)
- The release workflow verifies the image signature before releasing.
After signing, the workflow runs
cosign verifyagainst the image in both registries with the certificate identity and issuer documented inSECURITY.md. If the check fails, no GitHub Release is created; the image is already pushed by then, so only the release is withheld.
Sürüm 0.3.2
GitHub’da görüntüle (0.3.2) (harici site)
Fixed (0.3.2)
- Changes that write an audit entry no longer fail on MariaDB 11.8 or
later. MariaDB 11.8 turns on
innodb_snapshot_isolationby default: a transaction that reads a row, and then touches a row another transaction changed since its snapshot, fails with error 1020 instead of waiting. When the application database was MariaDB 11.8 or later, deleting or suspending a user, creating or deleting a team and submitting a query could fail this way, because they read first and then append to the audit chain. These transactions are now tried up to three times, and each attempt starts from fresh data. On other databases the first attempt normally succeeds; a deadlock or lock timeout in these transactions is now retried as well. - Deleting a team keeps its TLS certificate files until the delete commits. The files used to be removed before the delete finished, so a delete that was rolled back would have lost them. They are now removed after the commit; a team delete that is retried or rolled back keeps them.
Changed (0.3.2)
- Integration tests run on MariaDB 11.4 and 11.8. The CI matrix has a separate job for each line, and the audit chain tests run against both as the application database.
Sürüm 0.3.1
GitHub’da görüntüle (0.3.1) (harici site)
No image was published for 0.3.0: the release scan stopped it on a vulnerable package in the base image. 0.3.1 is the same release with that package updated; upgrade from 0.2.7 straight to 0.3.1 and follow the 0.3.0 upgrade notes below.
Security (0.3.1)
- The image installs Alpine security updates on build. The PHP base image
still shipped
brotli-libs1.2.0-r1 (CVE-2026-111497, an out-of-bounds write in the Brotli decoder); the build now runsapk upgrade, so packages fixed in Alpine are picked up before the base image is rebuilt.
Changed (0.3.1)
- Dependency updates:
laravel/framework13.38.0,vite8.3.5,@codemirror/view6.43.15 andgithub/codeql-action/upload-sarif4.39.1.
Sürüm 0.3.0
GitHub’da görüntüle (0.3.0) (harici site)
Step-by-step upgrade and rollback instructions, with the migration list, are on the upgrading page. Six changes below are marked Breaking under Changed: the redacted chat SQL preview, soft-deleted users with required override reasons, required TLS on new connections, the Teams Workflows card format, and two that need action before you upgrade: the database privileges the audit migration needs, and the pinned time zone of MySQL and MariaDB application connections. If the migration user lacks those privileges the upgrade stops and the container does not start.
Security (0.3.0)
- Reads run in a read-only session on the target database. PostgreSQL,
MySQL, MariaDB and SQLite enforce it in the engine. SQL Server has no such
session: use the optional read-only identity (below) with
SELECTprivileges only. The connection form warns about this. - MySQL and MariaDB reads could leave their read-only transaction. MySQL
commits implicitly before DDL, which ended the transaction opened with
SET TRANSACTION READ ONLYand let the statement run outside it with write access. The SQL guard classes DDL as a write, so reaching this needed a statement the guard had misclassified as a read and a DBA who approved it as one. It was found by the integration tests against real databases before this release and was never in a released version: read-only sessions are new in 0.3.0. Reads on MySQL and MariaDB now set the session itself read only, so a statement that ends the transaction is still refused, and if the transaction is gone when a read commits the run fails instead of reporting success. The read path also classifies the statement again and refuses a write sent as a read, recordingrefused_by: guardin the audit log. The SQL guard and the DBA approval are unchanged; this closes a gap in the database-side layer. - SQL literals are redacted in chat, audit and listings. String and
numeric literals become placeholders in Slack and Teams previews, audit
metadata and its export, the dashboard and the request lists. The raw SQL
stays on the request page for the requester, the team's
dbausers and the admin. An Auditor sees redacted SQL only. When a statement cannot be redacted safely the preview is hidden. - New connections require TLS (see Changed). Existing connections keep their behaviour and are flagged until you enforce TLS on them.
- Release images are signed with cosign (keyless) and carry an SBOM and a SLSA provenance attestation; an OpenSSF Scorecard workflow runs weekly. How to verify is in SECURITY.md.
- livewire/livewire 4.4.8 fixes an authorization bypass in
#[Authorize](GHSA-mhf7-xp6r-6qrc). QueryProxy does not use#[Authorize], so it was not affected directly.
Added (0.3.0)
- Audit hash chain. Each audit entry stores the hash of the previous one
and its own hash, so edits, deletions and reordering are detectable.
php artisan audit:verifywalks the chain from the first record and reports the first break;audit:checkpoint(scheduled daily) records the latest chain hash as an audit event. Database triggers rejectUPDATEandDELETEonaudit_logson every supported driver (andTRUNCATEon PostgreSQL; MySQL and MariaDB triggers cannot block it). - Audit export to external systems. Opt-in sinks, set with
QUERYPROXY_SIEM_SINKS: JSON lines on stdout, RFC 5424 syslog over udp, tcp or tls, and an HMAC-signed HTTPS webhook. Delivery is queued after the audit record commits, retried, and dead-lettered tofailed_jobs, so a down sink never blocks the audit write. Events followdocs/audit-event.schema.jsonand carry no per-event chain hashes. - Prometheus metrics on
/metrics: counters for requests, approvals, executions (completed, failed, cancelled, deferred for a busy connection), guard rejections by rule and masked columns. Off by default (404); it turns on whenQUERYPROXY_METRICS_TOKENorQUERYPROXY_METRICS_ALLOWED_IPSis set. A scrape passes with the Bearer token or from an allowed address, so an address on the allowlist needs no token; anything else gets401. Labels never carry SQL, user names or hosts. A Grafana dashboard ships asdocs/grafana-queryproxy.json. php artisan queryproxy:doctorchecks an installation without changing it: configuration andAPP_KEY, the database, queue worker and scheduler heartbeat, storage, TLS enforcement on connections and their PEM files, masking coverage, the audit chain (--full-auditverifies all of it), SIEM sinks and metrics.--jsonprints a machine-readable report and--strictalso fails on warnings.- Secrets from files.
APP_KEY,APP_PREVIOUS_KEYS,DB_PASSWORD,MAIL_PASSWORD,REDIS_PASSWORD,SLACK_BOT_USER_OAUTH_TOKEN,QUERYPROXY_ADMIN_PASSWORD,QUERYPROXY_METRICS_TOKENandQUERYPROXY_SIEM_WEBHOOK_SECRETcan be given as the same name with a_FILEsuffix, by the application config and by the Docker entrypoint. Setting bothNAMEandNAME_FILEis an error and the container does not start; a plain value ofnull,(null)or(empty)counts as unset. - Stop a running query from the request page. The requester, an approver
or an admin can stop it:
pg_cancel_backendon PostgreSQL,KILL QUERYon MySQL and MariaDB,KILLon SQL Server, sent from a separate connection. On PostgreSQL and SQL Server the signal first checks the session's start time; MySQL and MariaDB connection ids are not reused. Not available on SQLite. A stopped run is rolled back, ends ascancelled, is audited asexecution.cancelled, and notifies the requester when someone else stopped it. On SQL Server the target connection's user needsVIEW SERVER STATEandALTER ANY CONNECTION; without them the request is still markedcancelledand a warning is shown, but the query runs on until its statement timeout.KILLends the whole session there, where the other drivers cancel only the statement. hmacmasking strategy and checksum validators.hmacpseudonymises values with a 256-bit key per team (stored encrypted), so analysts can join and count on masked columns.queryproxy:masking:rotate-keyreplaces the key; old and new pseudonyms then stop matching. Avalidatormatch type adds TCKN, VKN, TR IBAN and Luhn checks, and a Turkey PII rule pack (national id, tax id, IBAN, mobile and E.164 phone numbers, vehicle plates) can be installed per team. The VKN content validator ships disabled because its check digit matches too many unrelated numbers; the column rule still coversvknandtax_nocolumns.- Sensitive-filter badge. A request that filters, joins or sorts on a column covered by a masking rule is flagged in the approval list and on the request page, because the result can leak masked values through the predicate.
- Connection TLS settings and a read-only identity. Each connection has a
TLS mode and an optional CA, client certificate and key (PEM material is
stored encrypted and written to
storage/app/tls/with owner-only permissions). A connection can also hold an optional, encrypted read-only identity that read requests use. - Suspending users. Administrators suspend and resume users on the Users screen.
- Pending-request reminders.
requests:remindreminds the team's DBAs and admins once,QUERYPROXY_PENDING_REMIND_AFTERhours (default4,0turns it off) after a request is filed, and re-posts chat cards with a fresh approval token once the old tokens have expired. Requests never expire. - Connection slots.
QUERYPROXY_CONNECTION_SLOTS(default2) limits how many requests run against one connection at a time. A request that finds every slot busy is re-queued afterQUERYPROXY_CONNECTION_SLOT_RETRY_DELAYseconds (default5), audited asrequest.execution_deferredand shown with a "Waiting for slot" badge. - Integration tests on real databases. A workflow runs the integration and live suites against PostgreSQL, MySQL, MariaDB and SQL Server, with and without TLS.
Changed (0.3.0)
- Breaking —
QUERYPROXY_CHAT_INCLUDE_SQLnow defaults toliterals_redacted. The Slack and Teams preview replaces string and number literals with?. Values:literals_redacted(default),full(the earlier behaviour) andnone;truestill meansfullandfalsemeansnone. The audit screen and the audit CSV no longer carry raw SQL for any role, and the CSV columnsqlis nowsql_redacted. Update scripts that read it by name. - Breaking — users are soft-deleted and overrides need a reason.
query_requests.user_idandreviewed_bybecomeRESTRICT, so request history cannot disappear with a user, and code that hard-deletes users now fails on the foreign key. Deleting a user blocks their sign-in, sessions and chat identities and cancels theirpendingrequests (audited asrequest.cancelled, actorsystem); approved and queued requests are not touched, and the e-mail address stays reserved. A suspended user is blocked the same way but keeps their pending requests. Every admin override approval now needs a written reason on every channel, stored in the audit log: a Slack Approve press that would be an override is not applied and answers with a link to a web form, and the Teams action endpoint answers422to an override withoutreason. Rolling the user migration back is refused while any deleted or suspended account exists. - Breaking — new connections require TLS. PostgreSQL uses
sslmode=require, which encrypts but does not verify the server (useverify-fullagainst a man-in-the-middle); MySQL and MariaDB require TLS without verifying the server certificate unless you tick "Verify the server certificate" and add a CA; SQL Server usesEncrypt=trueandTrustServerCertificate=false(pdo_sqlsrvmust be installed), so a self-signed or privately issued certificate fails until its CA is trusted orTrustServerCertificateis turned on for that connection. SQLite has no TLS. Existing connections keep their old behaviour and are flagged in the connection list: PostgreSQL shows "TLS not enforced" (sslmode=prefer), the others "TLS: driver default". Edit each connection to enforce TLS;queryproxy:doctorlists those that do not. On SQL Server the enforcement is the ODBC driver's documented behaviour, not something QueryProxy implements, and the tests cannot simulate a server that offers no TLS; treat the network path as untrusted until you have checked it. - Breaking for custom flows — Teams Workflows URLs receive an Adaptive
Card. Webhook URLs on
*.powerplatform.comand*.logic.azure.comnow receive an Adaptive Card 1.5 instead of a MessageCard, and*.powerplatform.comjoins the default webhook host allowlist. Thequeryproxyenvelope with the action token stays at the top level, so the approval flow keeps working. A Power Automate flow that reads the MessageCard fields (title,summary,sections[0].facts) now reads them empty: read the card fromattachments[0].content, or forward the body unchanged. Legacy connector URLs (*.webhook.office.com) still receive a MessageCard (see Deprecated). - Reads run in a read-only session with timeouts derived from
QUERYPROXY_EXECUTION_TIMEOUT. The statement timeout is the execution timeout minus 5 seconds (at least 1) and the lock timeout isQUERYPROXY_LOCK_TIMEOUT(default5); writes get the same limits. On MySQL the statement timeout applies toSELECTonly. On PostgreSQL,SELECT … FOR UPDATEandFOR SHAREare rejected inside a read request. Once a statement has started, QueryProxy no longer reconnects to the target after a network drop: a brief drop fails the run instead of re-running it on a new session. - Results are streamed. Rows are read through a server-side cursor on PostgreSQL and an unbuffered result on MySQL and MariaDB, so memory no longer grows with the result size.
- Slots can make requests wait. If you ran more workers against one
connection than
QUERYPROXY_CONNECTION_SLOTSallows, requests now stayqueuedwhere they used to run at once.0switches the limit off. - Breaking, check before upgrading — MySQL and MariaDB: the application
connection time zone is pinned to UTC.
config/database.phpsets'timezone' => '+00:00'on themysqlandmariadbconnections.TIMESTAMPcolumns are stored as UTC and shown in the session time zone, which until now was the database server's. On a server that is not on UTC, every existingTIMESTAMPvalue (created_at,updated_atand the other*_atcolumns) is shown shifted by the server's UTC offset after the upgrade: a server at UTC+3 shows values 3 hours earlier, one at UTC-5 shows them 5 hours later (with a named zone such asEurope/Berlinthe shift follows daylight saving).DATETIMEcolumns and servers already on UTC are not affected. Open chat approval tokens (chat_approval_tokens.expires_at, 24-hour lifetime) expire early by that offset on UTC+ servers, and a token with less time left becomes invalid at once; on UTC- servers they live that much longer. Delete the open tokens before upgrading to avoid either effect; the affected requests then get a fresh chat card from the reminder:DELETE FROM chat_approval_tokens WHERE used_at IS NULL;. Check the server withSELECT @@global.time_zone, @@session.time_zone, @@system_time_zone, TIMEDIFF(NOW(), UTC_TIMESTAMP());first. The audit hash chain depends on the pin:audit_logs.created_atis hashed as UTC wall time and must read back unchanged. - Breaking, before upgrading — the audit migration needs trigger
privileges. The audit chain migration creates triggers. If it fails, the
container does not start, because the entrypoint runs
migrateon every start. The migration user needs theTRIGGERprivilege on MySQL and MariaDB (with binary logging on, alsoSUPERorlog_bin_trust_function_creators=1, otherwise it fails with error 1419) and ownership of the table on PostgreSQL. Existing audit records are chained from the first one during the migration, which can be run again after a partial failure. After the migration,audit_logsrows can no longer be updated or deleted. - The first reminder run can post many cards at once. Pending requests
older than 24 hours with no live approval token, in teams with an enabled
chat integration, get a renewed chat card on the first
requests:remindrun after the upgrade, so a channel with a backlog sees a one-time burst. - The scheduler gains three jobs: a daily
audit:checkpoint,requests:remindevery 15 minutes and a one-minute heartbeat written to the cache (read byqueryproxy:doctor). Keep the scheduler running. - Masking. The
hashstrategy is deprecated (see Deprecated). The built-in card-number regex is replaced by a Luhn validator rule in new default sets; existing teams keep the old rule and "Add default rules" adds the Luhn rule next to it. The TCKN validator no longer treats a digit run glued to a letter or an underscore as a candidate; a column namedtcknis still caught by its column rule. - As before, the Docker entrypoint sets the worker timeout to
QUERYPROXY_EXECUTION_TIMEOUTplus 10 seconds and ignoresQUERYPROXY_WORKER_TIMEOUT;queryproxy:doctorreports that value.
Deprecated (0.3.0)
- The
hashmasking strategy stays selectable and is labelled deprecated in the UI; usehmac. - MessageCard payloads to Teams legacy connector URLs. Microsoft's retirement notice (April 14, 2026 update) says the rollout ran May 18 to May 22, 2026, after which Office 365 Connectors no longer function, so a legacy URL may already have stopped working. Move to a Workflows URL.
Fixed (0.3.0)
- A job that fails now cancels its query on the target database instead of
leaving it running, and reads cut off at the row cap on MySQL and MariaDB
send
KILL QUERY.
Sürüm 0.2.7
GitHub’da görüntüle (0.2.7) (harici site)
Security (0.2.7)
EXPLAIN,KILLandWITHbodies are parsed with bounded memory. The inner statement behind these keywords was parsed by a parser that kept every error with a full backtrace, so a 63 KB dotted-name chain behindEXPLAINpeaked at about 300 MB and could exhaust a 256 MBmemory_limit. Inner bodies now keep only the first error and stay well under 64 MB on the same input.UPDATEandDELETEstatements the guard cannot parse are refused. When such a statement could not be parsed, the guard fell back to a text search forWHERE, which also matched theWHEREof a subquery and let some statements without their ownWHEREclause through. They are now refused as too complex to verify.
Fixed (0.2.7)
- Nested
DOblocks no longer repeat the same refusal for one language literal; independent violations keep their own messages. Which statements are accepted or refused is unchanged. - The test suite no longer needs a generated
APP_KEY:phpunit.xmlsets a fixed, non-secret test key.
Changed (0.2.7)
QUERYPROXY_EXECUTION_TIMEOUTis capped at 86400 seconds. A valid value above 86400 is clamped to 86400 instead of saturating in the application or overflowing the image entrypoint's arithmetic. The entrypoint prints a warning at container start when it clamps; the application clamps silently.- Inspecting a large
DObody is faster (a 63 KB body takes about 29% less time), because lexing results are shared instead of repeated.
Sürüm 0.2.6
GitHub’da görüntüle (0.2.6) (harici site)
Fixed (0.2.6)
QUERYPROXY_EXECUTION_TIMEOUTis read by one rule everywhere. The application config, the queue'sretry_afterand the image entrypoint now all accept only a whole number of seconds greater than zero and fall back to 300 for anything else (text, empty,0,000, negative, decimal, a value with a unit,true). Before, the entrypoint took0and000literally and derived a 10-second worker timeout from them, and a value such astruewas read as 1 second by the config.retry_afterfor an invalid value is 330, as for the default. The config falls back silently; the entrypoint still warns at container start.- The
sql_moderefusal pointed at the wrong cause. The message told you to removeNO_BACKSLASH_ESCAPES/ANSI_QUOTESfrom the server's or the user's defaultsql_mode, but QueryProxy overrides the session's mode when it connects, so a default cannot be the source. It now says the flag was set after connecting (for example by a proxy or connection pooler that rewrites session variables) and to look there. DO LANGUAGE E'SQL' …was refused with two messages for the same problem; it now gets one. The refusal itself is unchanged.
Security (0.2.6)
Tightening of the SQL guard. Unless noted, every item below is a refusal at submission, with the same explicit message as the existing rules. The guard remains a layer of defence in depth; the DBA approval is still what authorizes a statement.
- Role and user switches are refused.
SET [SESSION|LOCAL] ROLE,RESET ROLE(also writtenRESET "role"),SET … SESSION AUTHORIZATION,RESET SESSION AUTHORIZATION, MySQL and MariaDBSET ROLE/SET DEFAULT ROLE, andset_config('role', …)/set_config('session_authorization', …).roleandsession_authorizationjoin the built-in list of guarded settings, whichQUERYPROXY_EXTRA_DANGEROUS_VARIABLEScan only extend. ALTER SYSTEMis refused on PostgreSQL, whatever it sets.- Writes to the system catalog are refused. PostgreSQL:
INSERT,UPDATE,DELETEandMERGEintopg_catalog,pg_toastandpg_…tables. MySQL and MariaDB: the same statements andREPLACEagainst themysqlschema. Qualified, quoted and CTE-wrapped spellings are covered. On MySQL and MariaDBUSE mysqlis refused as well (also insidePREPARE,EXECUTE IMMEDIATEandSET STATEMENT … FOR), so that a name without a schema cannot be resolved against the catalog. Reading the catalog (SELECT * FROM pg_proc,SELECT … FROM mysql.user) still passes. READ WRITEis refused inSET TRANSACTION,SET SESSION CHARACTERISTICS,BEGINandSTART TRANSACTION, so a submission cannot lift a read-only transaction.READ ONLYstill passes.- Writes to the MySQL / MariaDB read-only switch are refused in every
scope and spelling (
transaction_read_only,tx_read_only,SESSION,GLOBAL,@@…,SET STATEMENT … FOR). - A MySQL / MariaDB write whose default database is
mysqlis refused at execution. A table name without a schema would otherwise resolve inside the system schema. The executor readsDATABASE()on the connection before the first statement; a single-statement read and a connection with no default database are not affected. ALTER DATABASE|ROLE|USER … RESETof a guarded setting (orALL) is refused on PostgreSQL, and the action of aCREATE [OR REPLACE] RULE(DO [ALSO|INSTEAD] …) is now inspected by the guard's own rules instead of being skipped.- SQL Server impersonation is refused:
EXECUTE AS,EXEC AS,SETUSERandREVERT, including insideEXEC('…')andsp_executesql, andWITH EXECUTE ASin object definitions. A column namedexecute_asand the text inside a string literal are not affected. - A very long dotted-name chain could stall a worker. The guard re-walked the rest of the chain at every part, so the cost grew with the square of the chain length and a statement near the editor's size limit could keep a PHP process busy for a long time. It now scans in linear time. No refusal was added or relaxed by this change.
- The new rules apply inside
DOand routine bodies,PREPAREandEXECUTE IMMEDIATEtext, CTE bodies,EXPLAIN ANALYZE, MariaDBANALYZEandSET STATEMENT … FOR.
Sürüm 0.2.5
GitHub’da görüntüle (0.2.5) (harici site)
Added (0.2.5)
php artisan queryproxy:mask-error-messages [--dry-run]masks the error messages already stored on query requests and the copy of them inside in-app "request failed" notifications, using each connection's current rules (a request or connection that no longer exists gets the fixed message). Safe to run repeatedly; the immutable audit log is left untouched and the run is recorded as oneerror_messages.maskedentry (scanned, changed, replaced by the fixed message, skipped; and scanned, changed, replaced by the fixed message and skipped for notifications) even when it stops halfway. A record whose connection rules cannot be read is skipped, left unchanged and reported, and the command exits non-zero.- DDL flag on requests. A request that contains DDL now carries a
DDLbadge in the approvals queue and on the request page, and the Slack and Teams "new request" messages say so, so the approving DBA sees it before deciding. The flag was already computed and stored; it was never shown.
Fixed (0.2.5)
- A request failed by the job's
failed()handler left no trace. It now writes therequest.execution_failedaudit entry (withsource: job_failed) and notifies the requester, as a failure inside the executor already did. A request that has already completed is still never flipped. - The Teams HMAC docblock described the key as
base64_decoded_secret; it now states what the code does: the secret base64-decoded in strict mode, or the raw secret when it is not valid base64. - Auditors could not reach the Requests list from the menu. The sidebar hid the link for the auditor role although the page, the route and the policy already allowed it. Auditors now see the team's requests from the menu; result data stays closed to them.
- The Slack and Teams "account not linked" replies, the chat-ops setup steps and the add-member error pointed to "Admin → Users"; they now name the actual screen, Teams & Users → Manage users.
Security (0.2.5)
- Error messages of failed queries are masked like result data. Driver
errors echo the offending values (
Duplicate entry 'bob@x.io',Key (phone)=(...), the SQL with its bindings), and the message was stored and shown unmasked in the request, the audit log and the in-app notification. It now goes through the connection's masking rules first: content rules mask matches in place, and when the message names a column covered by a column rule every literal value in it is masked. The raw message stays in the application log only. This closes the gap in ADR-014 for new failures. It does not rewrite history: audit entries written by earlier versions (request.execution_failed,execution.late_completion) keep the raw text, because the audit log is immutable. Runphp artisan queryproxy:mask-error-messagesto mask the messages stored on query requests and in the in-app "request failed" notifications by earlier versions. - The job
failed()safety net now redacts and masks too. It stored the raw exception message; it now uses the same redact-and-mask path as the executor.
Sürüm 0.2.4
GitHub’da görüntüle (0.2.4) (harici site)
Fixed (0.2.4)
- The live PostgreSQL test that lifts a read-only transaction failed when
pdo_pgsqlwas built against libpq older than 17: the driver frees prepared statements withDEALLOCATE, which takes a snapshot and makes PostgreSQL refuse the switch back to read-write. The test's setup statements now run unprepared. The guard itself is unchanged.
Security (0.2.4)
league/commonmarkupdated to 2.10.3. 2.10.0 is affected by GHSA-3q6v-r5mr-hxv8 (high) and GHSA-97jj-33gv-5xf9 (medium). The release image scan failed on them, so the container images for 0.2.1, 0.2.2 and 0.2.3 were never published; 0.2.4 is the first image after 0.2.0 and carries all of their changes.
Sürüm 0.2.3
GitHub’da görüntüle (0.2.3) (harici site)
Changed (0.2.3)
- CI and the live test setup.
ci.ymlruns with a read-onlycontentstoken and checks out without persisting credentials, aslive-pgsql.ymlalready did. PostgreSQL for thelive-pgsqlgroup (the workflow's service anddocker-compose.test.yml) is initialised with--auth-host=scram-sha-256, because the image trusts loopback otherwise and thedblink_exectest never sent the password it is meant to prove it passes. Thedblink_exectest now passeshost,portandpasswordin an escaped connection string (QUERYPROXY_LIVE_PGSQL_SERVER_HOST/_SERVER_PORToverride the address the server sees itself at). The compose file listens on55432inside the container as well, so the documented command no longer needsQUERYPROXY_LIVE_PGSQL_SERVER_PORT, and keeps its data directory on tmpfs so every container starts from a freshinitdb. If you run the compose example locally, recreate the container once (docker compose -f docker-compose.test.yml down, thenup -d --wait).
Fixed (0.2.3)
- A write refused by a read-only session was silently run again. Laravel
counts PostgreSQL SQLSTATE
25006and MySQL/MariaDB's--read-onlyerror as a lost connection, so outside a transaction it reconnected and re-ran the write on a fresh, writable session. The lost-connection detector now reports read-only errors as not lost and defers everything else to the framework's detector. Trade-off: after a MySQL/Aurora failover that leaves a connection on a read-only node, or on a PostgreSQL hot standby, the request fails with the database's error instead of being retried on a new connection; a queue worker that lands there no longer stops itself on25006either, so restart workers after such a failover. - Large SQL took seconds and hundreds of megabytes to inspect. The guard
lexed the same SQL several times per inspection, kept lexer errors with their
backtraces and re-scanned its lexical ranges linearly. Each SQL text is now
lexed once per inspection, dialect ranges are kept sorted and binary-searched,
and the parser no longer leaves a cycle of garbage behind each request. On
the worst 64 KiB SQLite input that was tried (
[a],repeated) inspection went from 15.9 s and 589M of peak memory to 0.69 s and 21.5M, measured on a development machine under load; the verdicts do not change. New tests pin the lex count, the memory peak and a time bound relative to a bare lexer pass.
Security (0.2.3)
- PostgreSQL
DObodies were read with the MySQL lexer. The guard scanned aDObody for blocked functions with MySQL's string rules, so a body such asPERFORM 'a\', pg_read_file(…) --hid the call inside what the lexer took for a string, while PostgreSQL ran it. Block and routine bodies (DO,CREATE FUNCTION,CREATE PROCEDURE) are now read with PostgreSQL's lexical rules (comments,'…',E'…',U&'…', quoted identifiers, nested dollar-quoted strings) and go through the same blocked-function and guarded-setting rules as top-level SQL. Behaviour change: the following are now rejected.- Dynamic SQL (
EXECUTE) inside aDObody or a function or procedure body, trigger functions included: the statement it runs is built at run time and cannot be inspected. TriggerEXECUTE FUNCTION|PROCEDUREclauses are not affected. - Any language other than
plpgsqlandsqlforDO,CREATE FUNCTIONandCREATE PROCEDURE. A quoted language name keeps its case, soLANGUAGE "PLPGSQL"is refused.CREATE LANGUAGEandALTER LANGUAGE … RENAMEare refused too. BEGIN ATOMICbodies, andCREATE FUNCTION/CREATE PROCEDUREwithout a body.query_to_xml*,ts_stat,ts_rewrite,crosstab*andconnectbyjoin the blocked functions: they run SQL passed to them as a string.- A
SETclause on a routine (CREATE/ALTER FUNCTION|PROCEDURE … SET), a database (ALTER DATABASE … SET) or a role (ALTER ROLE|USER … SET) now goes through the guarded-setting rule; one whose setting cannot be resolved is rejected.
- Dynamic SQL (
sql_modecould change how the guard's lexer and the server read quotes.NO_BACKSLASH_ESCAPESandANSI_QUOTESchange what a backslash and a double quote mean, so the guard could approve SQL the server then reads differently.sql_modeis now a guarded variable in every scope and on every driver:SET sql_mode,SET SESSION sql_mode,SET @@sql_mode, the@@SESSION./@@GLOBAL.forms, a version comment around them andset_config('sql_mode', …)are refused. Reading it (SELECT @@sql_mode) is unaffected. Onmysqlandmariadbthe executor also reads@@SESSION.sql_modebefore running the SQL and fails the request, without running anything, when it containsNO_BACKSLASH_ESCAPESorANSI_QUOTES. Today QueryProxy sets a strictsql_modeon every connection it opens (Laravel'sstrictoption), which replaces the server's default, so the check only fires if that setting changes or something in between alters the mode; a connection that does end up with either flag is refused until the flag is removed. The/*M!behaviour on themysqldriver is unchanged and now pinned by a regression test.- Three
SETspellings were not judged.SET GLOBAL general_log := 1(a space before:=) read as a label and passed the dangerous-variable list;SET @@general_log = 1(no scope) was not judged at all; and in MariaDBSET STATEMENT … FOR <statement>the statement afterFORskipped every rule, soSET STATEMENT max_statement_time=1 FOR DELETE FROM tran without aWHEREcheck. All three are now judged. The statement afterFORgoes through the same rules as a top-level one (type, denylist,WHERE,LIMIT, nesting depth);SET STATEMENTwithout a statement afterFORis rejected. Behaviour change:SET @@<name>is read as a session variable on every driver, so on PostgreSQL (and when the target database is unknown)SET @@general_log = 1is now refused.
Notes (0.2.3)
- PostgreSQL
DOblocks remain exempt from theWHERErule: they are classified as a write and always go through human approval. Dynamic SQL inside them is now refused, but a plainly writtenDELETEwithoutWHEREin aDObody still passes the guard. - Not covered yet: DML against system catalogs (
UPDATE pg_language,UPDATE pg_proc SET prosrc …; these need a superuser) andALTER SYSTEM SET. Multibyte character sets that change backslash handling (SET NAMES big5,sjis,gbk) are not checked either.
Sürüm 0.2.2
GitHub’da görüntüle (0.2.2) (harici site)
Added (0.2.2)
- Live PostgreSQL guard tests. A
live-pgsqlPest group intests/Liveruns the guard against a real PostgreSQL server and checks both the verdict and the effect on the server (settings,dblink_exec,pg_terminate_backend,pg_cancel_backend,EXPLAIN ANALYZE DELETE,DOblocks and more). It is skipped unlessQUERYPROXY_LIVE_PGSQL_HOSTis set.docker-compose.test.ymlstarts a throwaway PostgreSQL 17 for local runs, and the newlive-pgsqlGitHub Actions workflow runs the group in CI.
Fixed (0.2.2)
- The queue timing warning missed
queue:listenandqueue:work --once. The warning thatretry_afteris at or below the execution timeout was logged only when a daemonqueue:workstarted. It is now also logged whenqueue:listenorqueue:work --oncestarts, once per process; the short-livedqueue:work --oncechildren thatqueue:listenspawns do not repeat it. - The Compose worker example hard-coded
--timeout=310.docker-compose.ymlnow passes the timeout the image entrypoint derives (QUERYPROXY_WORKER_TIMEOUT,QUERYPROXY_EXECUTION_TIMEOUT+ 10), so changingQUERYPROXY_EXECUTION_TIMEOUTin the example also moves the worker timeout. The example needs the image's default entrypoint.
Security (0.2.2)
- PostgreSQL settings that load code or lift protections were not guarded.
The dangerous-variable list now also names
session_preload_libraries,local_preload_libraries,dynamic_library_path,default_transaction_read_only,transaction_read_only,session_replication_role,allow_system_table_mods,lo_compat_privileges,zero_damaged_pagesandignore_checksum_failure. Behaviour change: on PostgreSQL (and when the target database is unknown) these are refused in every scope:SET <name>,SET LOCAL <name>,SET SESSION <name>, theTOspelling andset_config('<name>', …). Before this release no PostgreSQL name was on the list. On MySQL, MariaDB, SQLite and SQL Server theSETrule is unchanged (only the persistent scopesGLOBAL,PERSISTandPERSIST_ONLYare judged), whileset_config('<name>', …)with one of the new names is refused on every driver. Harmless settings such aswork_memorsearch_pathstill pass.QUERYPROXY_EXTRA_DANGEROUS_VARIABLESstill extends the list. - MariaDB
ANALYZE <statement>ran its statement unchecked. LikeEXPLAIN ANALYZE, MariaDB'sANALYZE [FORMAT = x] <statement>executes the statement. It is now judged as the statement inside it (type, denylist,WHERErule,LIMIT, nesting depth). OnlySELECT,WITH,INSERT,REPLACE,UPDATE,DELETEor a parenthesized query may follow; anything else (DROP,TRUNCATE,SET, a nestedANALYZE, …), and a parenthesized(ANALYZE <statement>), is rejected. The mysql driver and an unknown driver are judged the same way.ANALYZE TABLE …(maintenance) is unchanged. - Multibyte comments could hide the real statement from
EXPLAIN ANALYZEandANALYZE. The guard cut the inner statement at a character offset where a byte offset was needed, so a comment with non-ASCII text in front of it shifted the cut and let aDELETEwithoutWHEREthrough. Offsets are now converted to bytes. - MariaDB executable comments were not inspected.
/*M!100000 … */was treated as a plain comment, so its body ran on MariaDB without being checked. On themariadbdriver its body is now read as SQL, whatever the version condition. On themysqldriver/*M!is still a plain comment, so connect a MariaDB server with themariadbdriver. Nesting deeper than 3 levels is rejected, and when the target database is unknown a/*M!comment is rejected outright. - Version-conditional comments (
/*!NNNNN … */) could hide a clause from the guard. A server skips the body when its version is lower than the one written, so aWHEREorLIMITinside it may or may not exist. On themysqlandmariadbdrivers the statement is now checked twice, once as if the bodies run and once as if the server skips them, and is rejected when either reading is refused or when the two readings differ in statement count, transaction state, type or prepared text. Behaviour change: these are now rejected onmysqlandmariadb, which 0.2.1 accepted: different versions in one request (SELECT /*!40101 1 */, /*!50503 2 */is one), a version of other than 5 digits (5 or 6 on MariaDB), a versioned comment nested in or holding another comment, and one with*/inside a string or line comment in its body. A request with a single version and a plain body is unaffected. - SQL that was not valid UTF-8 slipped past the lexer. A stray byte inside
a comment (
DROP DATABASE prod /* é\xFF */) made the guard read an empty statement. SQL that is not valid UTF-8 is now rejected (SQL must be valid UTF-8.), and so is text in which the lexer finds no statement (QueryProxy could not read this SQL: …). UPDATE/DELETEwith an always-trueWHEREpassed.WHERE 1 = 1,WHERE true,WHERE 'a' = 'a'or anORwith such an operand counted as aWHEREclause. Behaviour change: these are now rejected like a missingWHERE, in every statement the guard looks into (CTE bodies,EXPLAIN ANALYZE, MariaDBANALYZE, T-SQL batches, dynamic SQL). Detection is static: literals, comparisons,IS [NOT] NULL|TRUE|FALSE,AND,OR,NOTand parentheses are evaluated, and an operand it cannot evaluate never counts as true. AWHEREclause nested too deeply or too large to evaluate is rejected as well (… too complex to verify).- A byte-order mark or Unicode whitespace could hide keywords. The lexer
glues characters such as U+FEFF, U+00A0, U+2028 or U+3000 to the neighbouring
word while a server may read them as separators, so
\u{FEFF}DROP DATABASE prodshowed noDROP. Behaviour change: these characters are rejected outside string literals, quoted identifiers and comments, on every driver and inside dynamic SQL. - PostgreSQL reads
\and#differently from the lexer. On PostgreSQL (and when the target database is unknown) a backslash is now rejected anywhere except in comments, dollar-quoted bodies,E'…'strings and well-formedU&strings, and#is rejected outside literals, quoted identifiers and comments. A plain string such as'a\'is therefore rejected on PostgreSQL even though MySQL and MariaDB behave as before.
Notes (0.2.2)
- PostgreSQL
DOblocks remain exempt from theWHERErule: they are classified as a write and always go through human approval, where the reviewer sees the whole body. ADELETEwithoutWHEREinside aDOblock therefore passes the guard.
Sürüm 0.2.1
GitHub’da görüntüle (0.2.1) (harici site)
Fixed (0.2.1)
retry_afterfor the database, Beanstalkd and Redis queues no longer falls back to Laravel's 90 seconds, which is below the worker's 310-second timeout and let a slow query be re-reserved while it was still running and then marked failed (notably withQUEUE_CONNECTION=redis). It is now derived fromQUERYPROXY_EXECUTION_TIMEOUT(timeout + 30 seconds, 330 by default) instead of a fixed number. An explicit*_QUEUE_RETRY_AFTERstill wins; a value at or below the timeout logs a warning when aqueue:workworker starts. The Docker entrypoint derives the worker--timeout(timeout + 10) andstopwaitsecs(timeout + 20) the same way.- A query that finished late could turn a Failed request into Completed. The
executor now moves a request to Completed only if it is still Running. A late
result is discarded, the result file is removed, and an
execution.late_completionaudit entry is written; for a write it records that the write was committed.
Security (0.2.1)
EXPLAIN ANALYZE <DML>was classified as a read and ran the DML.EXPLAIN ANALYZEexecutes the statement it explains, but the guard treated everyEXPLAINas a read, soEXPLAIN ANALYZE DELETE FROM tskipped theWHERErule, the write classification and the approval flow. AnEXPLAINthat analyzes is now judged as the statement inside it (type, denylist andWHERErule);EXPLAINwithoutANALYZEstays a read. The option list is checked against PostgreSQL's known options, and an option block the guard cannot read (quoted or unknown options, an unclosed list, an expression value) is rejected.SELECT … INTO <table>created a table but was classified as a read. It is now a write and DDL, and noLIMITis added.SELECT … INTO @varstays a read;INTO OUTFILE/INTO DUMPFILEwere already denied.- Functions with side effects passed as reads. Calling
nextval,setval, the advisory-lock family,get_lock,release_lock,pg_sleep*,sleeporbenchmarknow makes a statement a write.pg_terminate_backend,pg_cancel_backend,pg_reload_conf,pg_rotate_logfile,pg_promote,pg_switch_wal,dblink*and the server-side file functions (pg_read_file,pg_read_binary_file,pg_ls_dir,lo_import,lo_export,load_file) are denied.set_configis a write, and is denied when its first argument is not a literal. Names are matched after quoting andU&escapes are resolved.dblink*is a prefix match, so a user-defined function that starts withdblinkis denied too. The newQUERYPROXY_EXTRA_BLOCKED_FUNCTIONS,QUERYPROXY_EXTRA_STATE_CHANGING_FUNCTIONSandQUERYPROXY_EXTRA_RESOURCE_CONSUMING_FUNCTIONSsettings extend these lists, andQUERYPROXY_EXTRA_BLOCKED_PROCEDURES(SQL Server, below) andQUERYPROXY_EXTRA_SQLITE_PRAGMAS(SQLite, below) do the same for theirs; they can only add to the built-in entries. - SQL Server and SQLite constructs that reach outside the database were not
covered. On SQL Server the guard now denies
xp_*,sp_configure,sp_oa*,sp_addlinkedserver,sp_addlinkedsrvlogin,sp_serveroption,sp_addextendedproc,sp_execute_external_script,OPENROWSET,OPENDATASOURCE,OPENQUERYandBULK INSERT, and also deniesDENYstatements. A batch without semicolons is split at T-SQL statement boundaries and every part is checked, soSELECT 1 DELETE FROM tis rejected. On SQLite it deniesATTACH,DETACH,VACUUM … INTOandload_extension(, and only allows a built-in set of read-onlyPRAGMAs (table_info,table_xinfo,index_list,index_info,index_xinfo,foreign_key_list,database_list,table_list,collation_list,function_list,compile_options), whichQUERYPROXY_EXTRA_SQLITE_PRAGMAScan extend (bare form only); anyPRAGMAthat sets a value, and any not on the list, is rejected. The connection form warns whenQUERYPROXY_SQLITE_ALLOWED_DIRis empty. - Dynamic SQL was not inspected.
EXEC('…'),sp_executesql,EXECUTE IMMEDIATE '…', MySQLPREPARE … FROM '…'and PostgreSQLPREPARE … AS <statement>are now checked together with the SQL they carry. When the SQL is a literal it is judged by the same rules as a top-level statement; when it is a variable, an expression, a concatenation or anEXEC(…) AT <server>, the request is rejected. This can reject legitimate use: code that builds SQL at runtime and passes it through these forms no longer runs through QueryProxy. Behaviour change: on SQL Server these 14 system procedures are rejected whatever their arguments are:sp_prepare,sp_prepexec,sp_prepexecrpc,sp_execute,sp_cursoropen,sp_cursorprepare,sp_cursorprepexec,sp_msforeachtable,sp_msforeachdb,sp_msforeach_worker,sp_sqlexec,sp_send_dbmail,sp_add_jobstepandsp_update_jobstep.sp_executesqlis not on the list; its SQL text is inspected. Dynamic SQL nested more than 3 levels deep is rejected. UPDATE/DELETEinside a CTE skipped theWHERErule. The bodies ofWITH … AS (…)and the statement that follows theWITHare now checked, including nestedWITHs up to 3 levels; aWITHthe guard cannot read is rejected.INSERTandMERGEbodies are not checked separately; the outer statement is a write.- SQL Server
TOPwas only half enforced. ASELECTwithoutTOPwas not bounded by the guard, andTOPvalues withPERCENT, decimals or expressions were read as plain numbers. An existingTOP n/TOP (n)stays capped at 10000; now a top-levelSELECTwithoutTOPgetsTOP (<default row limit>), andTOP … PERCENT, expressions and decimals are rejected. ForWITH,OFFSET,UNION,EXCEPTandINTERSECTnoTOPis added; the executor's row cap applies. - New teams started with no masking rules. A team created from now on starts with the default masking rules. Existing teams are not changed (see Upgrading).
Upgrading (0.2.1)
- Remove the fixed
retry_afterlines from your.env. The 0.2.0.env.exampleshippedDB_QUEUE_RETRY_AFTER=330as an active setting, and an explicit value always wins over the derived one. If that line is still in your.env(orREDIS_QUEUE_RETRY_AFTER=330,BEANSTALKD_QUEUE_RETRY_AFTER), delete it unless you set it on purpose; otherwise raisingQUERYPROXY_EXECUTION_TIMEOUTleavesretry_afterat 330, a long query is re-reserved while it still runs, and a write can commit while its request is marked Failed. A--timeoutset by hand on a separately run worker (the Docker image derives it itself) should be the execution timeout + 10 seconds. - Existing teams do not get masking rules automatically. A DBA opens the team's Masking page and runs Add default rules. Query Studio and the Masking page now show a banner for a team that has no masking rules, or none enabled.
- Some statements now need approval.
SELECTstatements that callnextval,setval, an advisory-lock function,get_lock,release_lock,pg_sleep*,sleep,benchmarkorset_configare classified as writes, so they go through the approval flow. - Some statements that used to pass are now rejected. In particular:
EXPLAIN ANALYZEon aDELETE/UPDATEwithoutWHERE, dynamic SQL built from a variable or an expression, the 14 SQL Server procedures listed under Security, SQL ServerTOP … PERCENT, anyPRAGMAoutside the allowed set on SQLite, and, on PostgreSQL, anyUESCAPEclause and anyEXPLAINoption block the guard cannot read.SELECT … INTO <table>is now a write. - SQL Server
SELECTwithoutTOPnow returns 1000 rows by default. The previous ceiling was 10000. - The full list is in the SQL guards wiki page.
Sürüm 0.2.0
GitHub’da görüntüle (0.2.0) (harici site)
Changed (0.2.0)
- Breaking — set
TRUSTED_PROXIESwhen running behind a reverse proxy. It is empty by default. Until it names your proxy,X-Forwarded-Protois ignored, so HTTPS is not detected and audit entries record the proxy's address rather than the client's. Never use*. - Breaking — a system administrator must set the ChatOps signing secret. DBAs keep control of the webhook URL and the enabled flag, but can no longer enter or rotate the secret. Existing installations should rotate it once after upgrading: any DBA who configured ChatOps already knows the current value.
- Breaking — Approve / Reject buttons in chat messages posted before this
version stop working, because they carry no action token. Decide those
requests in the web UI. Teams automations must start sending the
tokenfield from the card'squeryproxyenvelope. - A TOTP code is now single-use, so signing in on a second device within the same 30-second window requires waiting for the next code.
- Removing a ChatOps integration is administrator-only, matching the bar for setting its signing secret — a DBA could otherwise delete an integration and then be unable to put it back. DBAs still untick Enabled to switch one off.
TRUSTED_HOSTSaccepts extra hostnames for installations served under more than one name, sinceX-Forwarded-Hostis no longer honoured.- New content masking rules apply to new teams only. Run Masking → Add defaults per team to pick them up; it is idempotent and leaves existing rules untouched.
Security (0.2.0)
- ChatOps approvals could be made in someone else's name. The webhook HMAC proves only that the caller knows the signing secret, but the approver's identity was read straight out of the callback body — and any team DBA could overwrite the signing secret without knowing the old one. Together that let a DBA approve their own request as a colleague (or as an admin), defeating the four-eyes rule and writing that colleague's name into the audit trail. Signing secrets are now administrator-only and rotating one requires the current secret; every Approve / Reject control carries a single-use action token bound to one request; and the resolved approver must belong to the integration's team.
- Every proxy was trusted (
trustProxies(at: '*')). Any client could forgeX-Forwarded-Forand get unlimited login and 2FA attempts (both throttles are keyed per IP) and write a false client IP into the audit log, while a forgedX-Forwarded-Hostrewrote the link in password-reset mail. Trust is now opt-in throughTRUSTED_PROXIES,X-Forwarded-Hostis no longer honoured, andtrustHostspins the request host toAPP_URL. - Content masking rules silently skipped non-string values. PDO returns
native integers for
BIGINT/DECIMAL, so a numeric column that matched no column pattern — a card number or a national id stored asBIGINT— reached the result store unmasked. Content rules now see every scalar. - The SQL guard's denylist had bypasses.
SET @@GLOBAL.…walked straight past theSET GLOBALrule (arbitrary file write through the MySQL general log), andCREATE FUNCTION … SONAME,pg_read_file()and friends were not listed at all. The guard now judges the dangerous part rather than the keyword:CREATE EXTENSIONandDOare refused for untrusted procedural languages,SET GLOBAL/SET PERSISTfor file-path, logging and plugin variables, and aDObody is scanned for the unconditionally blocked statements — soDO $$ BEGIN DROP DATABASE prod; END $$does not get through. That scan is best-effort by nature: SQL a block assembles at run time (EXECUTE format(…)) cannot be read statically, and the DBA approval remains the real boundary. - TOTP codes could be replayed for the ~90 seconds their window stayed open;
an accepted code is now burned via
two_factor_last_used_timestamp. - Chat webhook URLs leaked into the logs. A connection error carried the full
URL — and for Slack the secret is the URL path — into
laravel.log. - CSV exports did not guard the header row against spreadsheet formula injection, although the data rows did; column names come from the target schema or the requester's own aliases.
- Query results could be published without authorization if the operator
pointed
QUERYPROXY_RESULT_DISKat a public disk: result paths are guessable, sostorage:linkexposed every team's rows. Such a disk is now refused. - Default masking rules gained content patterns for vendor API keys, JWTs,
PEM private keys and high-entropy secrets, so a secret hidden behind a column
alias (
SELECT api_key AS k) is still masked. The credit-card content rule was narrowed to real IIN ranges so it no longer fires on epoch timestamps. - Third-party GitHub Actions are pinned to commit SHAs, and the release workflow scans the image before any registry credential is on the runner.
Sürüm 0.1.3
GitHub’da görüntüle (0.1.3) (harici site)
Changed (0.1.3)
- Image bases moved to
php:8.5-fpm-alpineandnode:26-alpine. OPcache is no longer installed explicitly: PHP 8.5 links it statically and enables it by default, anddocker-php-ext-install opcachefails there. - Release workflow actions updated to
docker/login-action@v4,setup-buildx-action@v4,setup-qemu-action@v4andmetadata-action@v6.
Sürüm 0.1.2
GitHub’da görüntüle (0.1.2) (harici site)
Added (0.1.2)
php artisan queryproxy:create-admincreates the first administrator, andQUERYPROXY_ADMIN_EMAIL/QUERYPROXY_ADMIN_PASSWORD/QUERYPROXY_ADMIN_NAMEcreate it on first boot — a fresh instance no longer needs demo seeding to have an account to log in with.- Container
HEALTHCHECKagainst/up, sodocker runreports health without a compose file.
Changed (0.1.2)
- One-container install. The image now runs the queue worker and the
scheduler alongside the web tier, so
docker run -p 7432:7432 -v queryproxy-data:/var/www/html/storage/app queryproxy/queryproxyis a complete instance.QUERYPROXY_RUN_WORKER/QUERYPROXY_RUN_SCHEDULERturn them off for split deployments, andQUERYPROXY_WORKER_PROCESSESruns more than one worker.docker-compose.ymlcollapsed to that single service. - One volume. The SQLite database and the generated
APP_KEYmoved next to the result files under/var/www/html/storage/app. Volumes from earlier versions keep working: an existing/var/www/html/database/database.sqlite(or.app_key) is still used when present. - Releases publish to Docker Hub (
queryproxy/queryproxy) as well as GHCR.
Sürüm 0.1.1
GitHub’da görüntüle (0.1.1) (harici site)
Added (0.1.1)
.dockerignore, so localdocker builduses the same clean context as CI instead of copying hostvendor/,node_modules/and.envinto the image.
Fixed (0.1.1)
- Demo seeding (
QUERYPROXY_SEED_DEMO=true) crashed the production container on first boot: the seeder relied on Faker, which is a dev-only dependency and absent from--no-devinstalls. The seeder no longer uses factories.
Sürüm 0.1.0
GitHub’da görüntüle (0.1.0) (harici site)
The first public release.
Added (0.1.0)
- Teams & RBAC — Admin / DBA / Developer / Auditor roles, hard team isolation, per-developer connection grants.
- Connection vault — PostgreSQL, MySQL, MariaDB, SQL Server and SQLite targets with AES-256-encrypted credentials and a connection tester.
- Query Studio — CodeMirror editor with server-side AST guards:
WHERE-less
UPDATE/DELETErejected,LIMITinjection (default 1000) and clamping (cap 10000), explicit-transaction rule for multi-statement submissions, administrative statements blocked. - Approval workflow — pending queue, mandatory rejection reasons, self-approval prevention with audited admin override, requester cancellation, in-app notifications.
- ChatOps — interactive Slack approvals (HMAC-SHA256
v0signatures, ±5-minute replay window, member-ID identity mapping) and a Teams HMAC-verified action endpoint plus channel announcements. - Async execution — database-backed queue worker, cursor streaming to NDJSON result files with constant memory, atomic multi-statement transactions with rollback on failure.
- Dynamic data masking — column-pattern and content-regex rules with full / partial / hash strategies, applied at write time; default rule set and a test playground.
- Results — paginated viewer, streamed CSV export, configurable retention with automatic pruning.
- Immutable audit log — every security-relevant event, filterable auditor UI, CSV export.
- Password management — profile page (name + password change), enumeration-safe e-mail reset flow, admin password reset.
- Deployment — zero-config
docker compose up(app + worker + scheduler, SQLite default), published container imageghcr.io/queryproxy/queryproxy.
Sık sorulanlar
- QueryProxy’nin güncel sürümü nedir?
- Güncel sürüm 0.4.1, 4 Kasım 2026 tarihinde yayımlandı. Aşağıdaki liste her sürümün notunu ve o sürümün GitHub’daki etiketine giden bağlantıyı içerir.
- Sürüm notları hangi biçimi izler?
- Notlar Keep a Changelog biçimindedir ve sürümler SemVer ile numaralanır. Her sürümün altında değişiklikler Security, Added, Changed, Fixed ve Upgrading gibi başlıklara ayrılır.
- Yükseltme notlarını ve geçişle ilgili adımları nerede bulurum?
- Davranışı değiştiren bir sürüm bunu kendi notunda “Behaviour change” olarak işaretler; yükseltmeyle ilgili adımlar da ilgili sürümün notunda, örneğin “Upgrading” bölümünde yer alır. Üretimde çalıştırma önerileri için Üretim güvenliği sayfasına bakın.
- Bir sürümden diğerine yükseltmek güvenli mi?
- Bu sayfa bir uyumluluk taahhüdü vermez. Mevcut sürümünüzle hedef sürüm arasındaki her sürümün notunu okuyun ve özellikle “Behaviour change” işaretli maddelere bakın.