This is an automated email from the ASF dual-hosted git repository. jdaugherty pushed a commit to branch 8.0.x in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit f1fbe869561aa539269a3c0bc67d1368b5fd533c Merge: 3c5b1b9045 cb3a5753c7 Author: James Daugherty <[email protected]> AuthorDate: Tue Jul 28 23:51:14 2026 -0400 Merge pull request #15971 from apache/feat/gorm-query-safety-ast-check Compile-time check for GORM query strings flattened from GString (alternative to #15968) .../common/compiler/GroovyTransformOrder.groovy | 8 +- .../GlobalGormQuerySafetyASTTransformation.java | 76 +++ .../transform/GormQuerySafetyTransformer.java | 690 +++++++++++++++++++++ ...org.codehaus.groovy.transform.ASTTransformation | 1 + ...obalGormQuerySafetyASTTransformationSpec.groovy | 78 +++ .../GormQuerySafetyTransformerSpec.groovy | 543 ++++++++++++++++ .../en/guide/security/securingAgainstAttacks.adoc | 56 ++ .../src/en/guide/upgrading/upgrading80x.adoc | 21 + 8 files changed, 1472 insertions(+), 1 deletion(-) diff --cc grails-doc/src/en/guide/upgrading/upgrading80x.adoc index 2bb8748368,32210c30cf..40f77ca298 --- a/grails-doc/src/en/guide/upgrading/upgrading80x.adoc +++ b/grails-doc/src/en/guide/upgrading/upgrading80x.adoc @@@ -1528,235 -1513,25 +1528,256 @@@ Both hooks may be used on the same plug **Bean-definition overriding edge case.** Grails defaults `spring.main.allow-bean-definition-overriding` to `true`, and under that default nothing changes: an application bean (from the application class, `resources.groovy` or `resources.xml`) that uses the same name as a plugin bean still replaces the plugin's bean. However, because plugin beans now register earlier and application beans register in a separate, later step, an application that explicitly sets `spring.main.allow-bean-def [...] + + ==== 34. Compile-Time GORM Query Safety Check + + Grails 8 adds a compile-time check that can **break existing builds**: passing a GORM query method (`find`, `findAll`, `executeQuery`, `executeUpdate`, and the Neo4j Cypher equivalents) a `String`-typed variable that was built from an interpolated `GString` is now a compile error, not just a discouraged pattern. + + [source,groovy] + ---- + def vulnerable() { + String query = "from Book as b where b.title = ${params.title}" // fails to compile + def books = Book.executeQuery(query) + } + ---- + + This is flagged because passing the `GString` *directly* to `executeQuery` is safe — GORM binds the interpolated value as a query parameter — but assigning it to a `String`-typed local first causes Groovy to coerce it to a plain `String` at that point, silently discarding the safe binding; the interpolated value is then embedded as raw, unescaped query text. See <<sqlInjection,SQL injection>> for the full explanation and examples. + + If your application has this pattern, fix it by keeping the query a `GString` all the way to the call, or by using named/positional parameters instead. For a call site that has been reviewed and is genuinely safe, add `@SuppressWarnings("GormUnsafeQueryString")` to the enclosing method rather than restructuring the code. + + This check runs automatically wherever `grails-datamapping-core` is on the compile classpath, with no opt-in configuration required. If it produces a false positive with no workaround, the whole check can be disabled by setting the `protectSqlInjectionAttacks` system property to `false` (it defaults to `true`) — this is a last-resort, build-wide kill switch, not a substitute for the per-call-site `@SuppressWarnings` above. + + The same check also emits (non-build-breaking) **compile-time warnings**, not errors, for two related but lower-confidence patterns: a `String`-typed field assigned an interpolated `GString` and later read through `this.field` in a query call, and query text built with `+` concatenation from a non-constant value with no `GString` involved. Neither of these fails the build, but both are worth reviewing — see <<sqlInjection,SQL injection>> for examples of both. + **Most applications and plugins need no action.** Behavior only changes where a plugin bean and a conditional Boot bean competed for the same name or type — the plugin bean now wins, which is almost always the intended outcome. + +==== 34. Extension API Compatibility Notes + +Most Grails applications do not call the Grails extension APIs listed below directly. +They can affect applications, plugins, or build logic that import these public classes or override these public extension points directly. +Unsupported internal implementation details are not covered here. + +[cols="1,2,2", options="header"] +|=== +| Module +| Changed or removed API +| Migration note + +| `grails-codecs` +| `org.grails.plugins.codecs.CodecsGrailsPlugin` no longer overrides `doWithSpring()` +| Do not depend on that plugin override to contribute codec bean definitions. +Custom plugins should register their own beans from their own `doWithSpring()` implementation. + +| `grails-controllers` +| `ControllersAutoConfiguration.dispatcherServletRegistration(...)` now returns Spring Boot 4's `org.springframework.boot.webmvc.autoconfigure.DispatcherServletRegistrationBean` +| Update direct imports, overrides, and binary integrations that referenced the Spring Boot 3 return type. + +| `grails-converters` +| `grails.web.JSONBuilder` +| Use Groovy's `groovy.json.JsonBuilder` or `groovy.json.StreamingJsonBuilder` for JSON builder code. + +| `grails-core` +| `GrailsApplicationPostProcessor`, `ProfilingGrailsApplicationPostProcessor`, and `DefaultGrailsPluginManager` plugin-discovery wiring changed +| Update custom bootstrapping code or subclasses that construct `GrailsApplicationPostProcessor` or `DefaultGrailsPluginManager` directly to pass the plugin-discovery dependency. +`ProfilingGrailsApplicationPostProcessor` keeps its `GrailsApplicationLifeCycle`, `ApplicationContext`, and `Class...` constructor shape, but the application context must expose the `grailsPluginDiscovery` bean. +This was introduced during the Grails 7.1 plugin-loading changes and remains required in Grails 8. + +| `grails-core` +| `grails.plugins.PluginFilter`, `IncludingPluginFilter`, `ExcludingPluginFilter`, `NoOpPluginFilter`, and related filter types +| Import replacement types from the `org.apache.grails.core.plugins.filters` package, such as `PluginFilter`, `IncludingPluginFilter`, `ExcludingPluginFilter`, and `NoOpPluginFilter`. + +| `grails-core` +| Legacy mixin transform APIs such as `grails.util.Mixin`, `MixinTargetAware`, and `MixinTransformation` +| Replace legacy mixin usage with Groovy traits, extension methods, or explicit composition. + +| `grails-core` +| `grails.validation.ConstraintsEvaluator` +| Use `org.grails.datastore.gorm.validation.constraints.eval.ConstraintsEvaluator`. + +| `grails-core` +| `BinaryGrailsPluginDescriptor`, `CorePluginFinder`, and `BinaryGrailsPlugin.getBinaryDescriptor()` +| Use the current plugin-discovery APIs and `BinaryGrailsPlugin.getPluginDescriptor()`. + +| `grails-domain-class` +| `ConstraintEvaluatorAdapter` and `GrailsDomainClassAutoConfiguration.constraintsEvaluator(...)` +| Code that imported the auto-configuration method should use the current `validateableConstraintsEvaluator(...)` bean path and the datastore constraint evaluator APIs. + +| `grails-fields` +| `BeanPropertyAccessor.getBeanClass()`, its setter, `PropertyPathAccessor.getBeanClass()`, `FormFieldsTagLib.input(...)`, and direct `render*` helper methods +| Use the `beanType` property exposed by the accessors. +Custom field renderers should go through the documented `f:field`, `f:widget`, and `f:displayWidget` tags and templates instead of calling private render helpers directly. + +| `grails-gradle-plugins` +| `ApplicationContextCommandTask`, `ApplicationContextScriptTask`, `ProfileCompilerTask`, and `GrailsRunTask` are abstract; `GrailsGradlePlugin.createBuildPropertiesTask(...)` no longer returns a task value; several `IntegrationTestGradlePlugin` constants were removed +| Register these task types through Gradle task registration and update custom plugins that subclass `GrailsGradlePlugin` or read integration-test constants to use Gradle source sets, configurations, and the current test-phase APIs. + +| `grails-shell-cli` +| `AetherGrapeEngine` and `AetherGrapeEngineFactory` +| Use `MavenResolverGrapeEngine` and `MavenResolverGrapeEngineFactory` if you extend the legacy shell dependency resolver. + +| `grails-shell-cli` +| JLine 2 `complete(...)` signatures +| Implement the JLine 3 `org.jline.reader.Completer` contract, `complete(LineReader, ParsedLine, List<Candidate>)`. + +| `grails-spring` +| `GrailsApplicationContext.onRefresh()` +| Custom context subclasses should use supported Spring lifecycle extension points instead of overriding this removed hook. +Spring theme APIs removed from the same area are covered in section 10. +|=== + +==== 35. CLI Commands Move to Companion `-cli` Artifacts + +Grails commands (`ApplicationCommand` implementations) no longer ship inside runtime plugin +artifacts. Each command-bearing module publishes a companion artifact under its own coordinate with +a `-cli` suffix, and commands are registered in a dedicated `META-INF/grails-cli.factories` file +instead of `META-INF/grails.factories`. + +**Package rename.** The command contract moved from `grails.dev.commands.*` (in `grails-core`) to +`org.apache.grails.core.cli.*` (in the new `org.apache.grails:grails-core-cli` artifact). Update +imports in your `grails-app/commands` classes: + +[source,groovy] +---- +// Grails 7 +import grails.dev.commands.ApplicationCommand +import grails.dev.commands.ExecutionContext + +// Grails 8 +import org.apache.grails.core.cli.ApplicationCommand +import org.apache.grails.core.cli.ExecutionContext +---- + +**New `grailsCli` configuration.** The Grails Gradle plugin registers a `grailsCli` configuration +that is compile-visible for `grails-app/commands` sources and on the command-runner classpath, but +never on `runtimeClasspath` — so commands and their CLI-only dependencies are excluded from +`bootRun`, `bootJar`, and `bootWar`. + +**CLI dependencies are discovered automatically.** The plugin adds +`org.apache.grails:grails-core-cli` (the command contract) and `org.apache.grails:grails-console` +(the command runner) to `grailsCli`, and walks the application's resolved dependency graph — +including transitive plugins — for jars carrying the `Grails-Cli-Artifact` manifest attribute, +adding each advertised companion automatically. The per-command Gradle tasks (e.g. `dbmUpdate`) +are registered from those companions as well, so no `buildscript` classpath entry is required. In +most applications, upgrading therefore requires **no build changes** to keep plugin commands +working: + +[source,groovy] +.build.gradle +---- +dependencies { + // the dbm-* commands are discovered automatically: the plugin jar advertises + // org.apache.grails:grails-data-hibernate7-dbmigration-cli, which is added to grailsCli + implementation 'org.apache.grails:grails-data-hibernate7-dbmigration' +} +---- + +The `console` and `shell` tasks also draw from the provisioned cli tier, so the +`console "org.apache.grails:grails-console"` dependency that earlier Grails versions generated in +`build.gradle` is no longer needed and can be removed (the `console` configuration itself remains +for additional console-only dependencies). + +Disable the discovery with `grails { cliAutoProvision = false }` and declare the cli tier +explicitly when full control over the command classpath is preferred: + +[source,groovy] +.build.gradle +---- +grails { + cliAutoProvision = false +} + +dependencies { + implementation 'org.apache.grails:grails-data-hibernate7-dbmigration' + grailsCli 'org.apache.grails:grails-core-cli' + grailsCli 'org.apache.grails:grails-console' + grailsCli 'org.apache.grails:grails-data-hibernate7-dbmigration-cli' +} +---- + +The renamed framework command packages are: + +|=== +|Grails 7 | Grails 8 (`-cli` artifact) + +|`grails.dev.commands.*` (grails-core) +|`org.apache.grails.core.cli.*` (grails-core-cli) + +|`org.grails.web.mapping.reporting.UrlMappingsReportCommand` +|`org.apache.grails.web.mapping.cli.UrlMappingsReportCommand` (grails-web-url-mappings-cli) + +|`scaffolding.*` commands +|`org.apache.grails.scaffolding.cli.*` (grails-scaffolding-cli) + +|`grails.plugin.hibernate.commands.SchemaExportCommand` +|`org.apache.grails.data.hibernate5.cli` / `...hibernate7.cli` (grails-data-hibernate5-cli / -7-cli) + +|`org.grails.plugins.databasemigration.command.*` +|`org.apache.grails.data.hibernate5.dbmigration.cli` / `...hibernate7.dbmigration.cli` + +|`grails.plugin.springsecurity` s2 commands +|`org.apache.grails.security.cli.*` (grails-spring-security-cli) +|=== + +**Third-party plugins must be rebuilt against Grails 8 for their commands to be available.** +Apply the new `org.apache.grails.gradle.grails-plugin-cli` Gradle plugin, move command sources from +`grails-app/commands` to `src/cli/groovy`, and update the contract imports — recompiling then +regenerates the `grails-cli.factories` registration automatically and the companion `-cli` +artifact is published alongside the plugin (see +xref:commandLine#creatingCustomCommands[Creating Custom Commands] for the complete workflow). +Hand-authored command registrations must move from +`src/main/resources/META-INF/grails.factories` to `.../grails-cli.factories`. A Grails 7 command +plugin dropped onto a Grails 8 application unchanged will not have its commands discovered. + +**What this change means for upgrading an application.** Plugin re-releases are *not* a +prerequisite for upgrading the application itself — the impact of the CLI split is bounded as +follows: + +* *Plugins that ship no commands* are entirely unaffected by this change. +* *Command-bearing plugins that have not yet been rebuilt for Grails 8* keep their runtime + functionality — controllers, services, taglibs, and other artefacts continue to work exactly as + before, because the runtime plugin jar never depended on the command contract. Only the plugin's + *commands* are unavailable: their registrations in the legacy `grails.factories` location are + ignored (the clean break above), so they disappear from the command list rather than fail. The + commands return once the plugin publishes a Grails 8 release with a companion `-cli` artifact. +* *The application's own commands* in `grails-app/commands` need only the import rename shown + above; the build wiring is automatic. + +NOTE: The statements above are about the CLI split specifically. Independently of it, Grails 8 +removes APIs that were deprecated in Grails 7 — a Grails 7 plugin that avoided those deprecations +generally continues to work on Grails 8 unchanged, while one that relied on them (or on other +Grails 8 changes such as Spring Boot 4) needs an update for that reason. + +==== 36. jQuery Webjar Upgraded to 4.0.0 + +The jQuery webjar managed by the `grails-bom` (`org.webjars.npm:jquery`) moves from 3.7.1 to 4.0.0. +jQuery 4 is a major release: it drops Internet Explorer and other legacy browsers and removes long-deprecated utilities such as `jQuery.trim`, `jQuery.type`, `jQuery.isArray`, `jQuery.isFunction`, `jQuery.proxy`, and `jQuery.isWindow`. +See the https://jquery.com/upgrade-guide/4.0/[jQuery 4.0 Upgrade Guide] for the complete list of changes. + +Because the version is managed by the BOM, any application that references the jQuery webjar without an explicit version picks up 4.0.0 automatically once it adopts the Grails 8 BOM. +This includes the generated create-app welcome page, whose assets have been verified against 4.0.0. + +If your application's own JavaScript relies on the removed APIs, migrate those call sites. +If you are not ready to migrate, pin the previous version in your gradle build: + +[source,properties] +.gradle.properties +---- +jquery.version=3.7.1 +---- + +==== 37. spring-security-ui Rewritten on Bootstrap 5 + +The `spring-security-ui` plugin's screens are rewritten on Bootstrap 5 with no bundled styling of their own. +The plugin no longer ships or depends on jQuery UI, jquery-form, jGrowl, DataTables, bgiframe or the bundled jdMenu/positionBy plugins, and every plugin stylesheet and image has been removed — dialogs are Bootstrap modals, tabs are nav-tabs, notifications render through `g:flashMessages`, and the autocomplete search uses a native `datalist`. +jQuery (4.x) and Bootstrap are expected from the host application, as provided by the create-app layout. + +Plugin pages now render through the host application's own layout — configured with `grails.plugin.springsecurity.ui.gsp.parentLayout` (default `main`) — inheriting its theme, including Bootstrap dark mode, locale selector and branding. +The security menu and login state are contributed to the host navbar through `nav` and `navActions` content blocks, which the create-app layout renders with `<g:pageProperty name="page.nav"/>` and `<g:pageProperty name="page.navActions"/>`; a custom layout that wants the menu in its navbar renders the same page properties. + +If you upgrade an application that customized the plugin: + +* Views previously copied into the application with `s2ui-override` reference removed stylesheets and rely on the old table-based markup of the `s2ui` field-row tags, which now emit Bootstrap form groups; regenerate or update those copies. +* CSS written against the old `s2ui_*` and `jd_menu` classes no longer has anything to match; restyle against Bootstrap classes. +* Applications that used the plugin's transitive webjars (jQuery UI, jGrowl, DataTables, jquery-form) must now declare those dependencies themselves. +* The `grails.plugin.springsecurity.ui.Constants` class and the `spring-security-ui-register.js` and `spring-security-ui-*` stylesheet assets have been removed. +* `LogoutController` now declares a computed `allowedMethods` restricting its action to POST while `logout.postOnly` is active (the default), making the restriction visible to tooling such as the create-app welcome page.
