Re-pin the default-active qqq-snapshot profile from qqq-bom-pom
4.1.0-RC.1 to 4.1.0-RC.2 (now on Maven Central) and update the docs.
No SNAPSHOT dependencies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without a least-privilege read-only role, read mode runs as the app
role and relies on the classifier (H2 / generic) or a READ ONLY
transaction that still allows side-effecting functions (Postgres).
Also records the security fixes in the CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SqlWorkbenchCredentials.getJdbcUrl is @JsonIgnore (a URL can carry a
password); it is only read in code when deriving the connection.
- CHANGELOG names the artifact com.kingsrook.qbits:qbit-sql-workbench.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text cells starting with =, +, -, @, tab or CR are prefixed with a
quote so spreadsheets open them as text. Numbers are left alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A failure to open the target connection (driver messages can expose
hosts, users, URLs) now answers "Could not connect to backend X; see
the server log." with no sqlState, and logs the cause server-side.
Schema introspection does the same.
- An unexpected exception inside a started run finishes its audit row
as FAILED (generic message, detail logged) before it propagates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MySQL runs the contents of /*! ... */ (and MariaDB /*M! ... */) and reads
/*+ ... */ hints, but the scanner skipped them as comments, so
"SELECT * FROM t /*! INTO OUTFILE '/tmp/x' */" classified as READ.
On the MySQL dialect the scanner now emits EXECUTABLE_COMMENT tokens for
these, and the classifier marks any statement containing one FORBIDDEN.
MySQL "--" also only starts a comment when whitespace follows ("1--1"
is arithmetic), so text after it is no longer hidden from the classifier.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-sending an in-flight executionUuid overwrote its registration, failed
the audit insert (UNIQUE), and the finally-unregister then removed the
original - escaping maxConcurrentExecutionsPerUser and cancellation.
- RunningStatements.register refuses an id already registered
(putIfAbsent); unregister(uuid, registration) removes only its own.
- The engine refuses a client id that is running or already in the
audit, before registering or recording anything
(DuplicateExecutionException; the route provider answers 409).
- ExecutionAuditor.record() checks the insert result and fails loudly.
- Execution ids are lower-cased at the API edge (execute, rows, export,
cancel, snapshots) and in engine.cancel.
- Tests for each, including 409 over HTTP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five tables used the same HAS_ACCESS_PERMISSION base (sqlWorkbench)
that every workbench user holds, so any user could read and write other
users' personal notebooks, cells, versions, pinned result snapshots and
the full execution audit through the generic /qqq/v1/table API.
- Tables now use adminPermissionBaseName (default sqlWorkbenchAdmin,
validated to differ from the access base name).
- Cell, version and snapshot tables drop insert/update/delete
capabilities, like the audit table.
- NotebookService and the history / snapshot audit lookups check the
caller in code, then read and write as the system user (SystemUser,
shared with ExecutionAuditor).
- Tests: a second ordinary user (and the owner) get permission denied
from the table query executor on all five tables; the workbench API
keeps working for ordinary users; admins can read everything.
- README / DESIGN document the admin permission.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /execute takes an optional executionUuid (validated as a UUID) so
the UI can cancel a running statement before the response arrives.
Generated server-side when absent; the audit's unique key still
guarantees no reuse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>