Problem/Motivation

When you want to allow anonymous users to access a REST resource, you need to:

  1. grant the necessary permissions/node grants to the anonymous user role
  2. not list any authentication providers for the REST resource's configuration

And then it should work.

However:

        // Check that authentication providers are defined.
        if (empty($rest_resource_config->getAuthenticationProviders($method))) {
          $this->logger->error('At least one authentication provider must be defined for resource @id', array(':id' => $rest_resource_config->id()));
          continue;
        }

This means that you must specify an authentication provider, otherwise the route won't be created. Then once you have specified e.g. cookie, or basic_auth (or both, or more — as long there is any authentication provider listed), the route will be created. If you then set up your permissions correctly, you'll still be able to use the resource as the anonymous user … due to quirks in how the routing system works.

Small detail: note how the error logging there is broken: @id in the text, but :id in the parameters. Which means the logging is broken.

Fun fact: this implies that \Drupal\user\Plugin\rest\resource\UserRegistrationResource which was added in #2291055: REST resources for anonymous users: register cannot be used at all in practice! (See #27 for details.) This alone is sufficient to make this major.

I'm not convinced, because … how do anonymous REST tests work around this then, if it is so broken?

Glad you asked!

Above, we explained how setting the cookie provider would allow anonymous users to use it. But, alternatively, you must specify the empty string as an authentication provider, i.e.:

supported_auth: ['']

That gets you past the above if-test (because the array is not empty: empty(['']) === FALSE), and hence the route will be created, and it just happens to be that Symfony also doesn't validate this further… This is how NodeJsonAnonTest etc are able to work: because \Drupal\Tests\rest\Functional\ResourceTestBase::$auth === FALSE by default, and \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::provisionEntityResource() does:

$auth = isset(static::$auth) ? [static::$auth] : [];

This results in $auth = [FALSE], which when it is passed to \Drupal\Tests\rest\Functional\ResourceTestBase::provisionResource() and saved into a RestResourceConfig entity just happens to be stored as 'authentication' => ['']… because PHP bool-to-string casting…

(First described in #16.)

Proposed resolution

There are multiple ways to solve this:

1. The superficial/minimal solution
Stop ignoring REST resource routes whose REST resource config entity did not specify any authentication providers (supported_auth: []). Therefore, also stop logging an error message. The end!
Rationale: it's valid to not list any authentication providers, because the cookie authentication provider is global, and is therefore always allowed, on any and every route, and there is no way to make it not global:
  user.authentication.cookie:
    class: Drupal\user\Authentication\Provider\Cookie
    arguments: ['@session_configuration', '@database']
    tags:
      - { name: authentication_provider, provider_id: 'cookie', priority: 0, global: TRUE }

IOW: this solution is accepting that it's impossible to disallow cookie-based authentication and therefore also anonymous access.

Consequence: every REST resource plugin should be doing some level of access checking, relying only on a user being authenticated or not is insufficient. This is already enforced by default, because \Drupal\rest\Plugin\ResourceBase::permissions() automatically generates a permissions for every method for every REST resource.
2. The explicit solution
This is what you probably prefer if you think solution 1 is too vague/implicit/misleading, and therefore dangerous.

Therefore this solution does not accept the status quo: it does not accept that a global authentication provider must be global; we must change that to mean global by default. What this means is that we add a mechanism to allow routes to effectively opt out from global route providers, and only allow the explicitly allowed authentication providers.

Now, changing the routing system is a tall order, and will require extreme scrutiny to not cause BC breaks. A practical alternative could be:

  1. allow 'anon' to be specified in the REST Resource config entity's authentication section
  2. this would not be an actual authentication provider, but would be an explicit signal to allow anonymous access
  3. in \Drupal\rest\Routing\ResourceRoutes::getRoutesForResourceConfig(), set _user_is_logged_in: 'TRUE' unless this 'anon' authentication provider is explicitly mentioned
  4. the end result would be that anonymous access is forbidden unless a REST resource's configuration explicitly allowed it — hence removing the requirement that if you don't want anonymous users to access REST resources, that they have some permission
  5. and if 'anon' is the only item listed in supported_auth, then we'd set _user_is_logged_in: 'FALSE', and we would not set _auth, so that'd we'd effectively fall back to the default authentication providers (i.e. cookie)

To retain BC (becaus Drupal has always allowed anonymous access to any REST resource due to all of the above), we could simply add anon to the supported_auth of every REST Resource config entity. That'd guarantee zero functional changes (because zero changes to actual route definitions), but would at least allow things to become clearer in the future.

Rationale: Explicitness prevents unpleasant security surprises.
Consequence: Upgrade path necessary. But we've done that in the past, in #2721595: Simplify REST configuration, so we've got a clear, battle-hardened example to look at.

Remaining tasks

  1. Decide which solution we want to go with.
  2. Test coverage
  3. Update https://www.drupal.org/docs/8/api/authentication-api

User interface changes

None.

API changes

None.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Issue summary: View changes
Status: Active » Needs review
Issue tags: +Needs tests
FileSize
1.18 KB
Wim Leers’s picture

Wim Leers’s picture

Issue tags: +DrupalWTF
dawehner’s picture

Even if not, don't we even in this case fallback to the default authentication, which is cookie by default? So that would be a totally valid configuration?

Grayside’s picture

Configuring an auth provider for anonymous is an explicit requirement that could be handled via a sensible default.

Right now, the problem is that Drupal will yell if your anonymous resource is insufficiently configured. At least it "yells". Removing this, and setting up authenticated resources will have an unmet, implicit dependency that will be confusing to troubleshoot. I don't think this should go forward as-is without a planned follow-up to make authenticated access to REST resources less touchy by default.

Wim Leers’s picture

Even if not, don't we even in this case fallback to the default authentication, which is cookie by default?

Correct. Note that for that problem/aspect I have #2817745: Add test coverage to prove that REST resource's "auth" configuration is also not allowing global authentication providers like "cookie" when not listed.

So that would be a totally valid configuration?

If by that you mean that supported_auth: [] is totally valid, then yes. If not, can you clarify?


Configuring an auth provider for anonymous is an explicit requirement that could be handled via a sensible default.

What are you suggesting exactly?

Right now, the problem is that Drupal will yell if your anonymous resource is insufficiently configured. At least it "yells".

In what way does it "yell"? Logging to watchdog is very very easily missed.

Removing this, and setting up authenticated resources will have an unmet, implicit dependency that will be confusing to troubleshoot. I don't think this should go forward as-is without a planned follow-up to make authenticated access to REST resources less touchy by default.

I would argue it is confusing today.


This issue is just meant to make things logical for "authless" resources. For making it more clear for authenticated resources, there is #2817745: Add test coverage to prove that REST resource's "auth" configuration is also not allowing global authentication providers like "cookie" when not listed.

Grayside’s picture

The other issue addresses my concerns with this, which I slightly conflated with how confusing this is to set up with the REST UI module enforcing the current expectations.

Wim Leers’s picture

which I slightly conflated with how confusing this is to set up with the REST UI module enforcing the current expectations.

Yep, that's exactly part of the problem. REST UI exposes the very same problems in the UI: the same problems that you have if you manually set up this YAML file.

dawehner’s picture

Could we make an explicit check of supported_auth : [] vs supported_auth: ~ aka. a non existing value? You could read [] as explicit decision to not provide authentication.

Wim Leers’s picture

That sounds sensible!

dawehner’s picture

Maybe we even have tests for that alright, even I sadly doubt it ...

dawehner’s picture

Damnit it'll be tricky to update \Drupal\rest\Tests\RESTTestBase::enableService properly.

Wim Leers’s picture

+++ b/core/modules/rest/src/Routing/ResourceRoutes.php
@@ -94,7 +94,10 @@ protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_
+        if ($authentication_providers !== [] && empty($authentication_providers)) {

Wouldn't empty($a) && $a !== NULL be clearer?

dawehner’s picture

Wouldn't empty($a) && $a !== NULL be clearer?

Oh totally!

Wim Leers’s picture

Assigned: Unassigned » Wim Leers

So, I worked on #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method, and that does also have explicit anonymous test coverage. Why does that work?

Two reasons:

  1. Because "anonymous" is not an authentication provider you even can list, so it's always allowed. It just means you need sufficient permissions. (This issue would not change that.)
  2. Because it has similar incorrect logic that \Drupal\rest\Tests\RESTTestBase::enableService() has. It's slightly different though: \Drupal\rest\Tests\RESTTestBase::enableService sets it to ['cookie'], if you don't specify allowed auth. \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::provisionEntityResource() just sets [FALSE] if you don't specify allowed auth. And yes, everything accepts this: the RestResourceConfig entity (because it doesn't do validation) and the routing system (because it maps this to the empty string). It only works thanks to the entire system being incredibly non-strict.

By fixing EntityResourceTestBase::provisionEntityResource(), we automatically gain test coverage.


Thinking about this more, and playing with this, I actually had interpreted ~ (i.e. NULL) as the way to specify the intention to only allow anon access. Because ~ is currently not yet allowed by the REST Resource config schema.

But thinking about this again, I think it makes sense to just use the empty array for that. Because you can never choose to not allow anonymous access. Anonymous access to a resource is always allowed. That's how the routing system works.

Wim Leers’s picture

This is the direction I went with first: ~ (NULL) means anonymous only.

Wim Leers’s picture

And this is if I follow what @dawehner initially proposed. Note that the value can never be NULL. So this:

+++ b/core/modules/rest/src/Routing/ResourceRoutes.php
@@ -94,7 +94,10 @@ protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_
+        // We want to allow people to specify no authentication providers by
+        // using @code [] @endcode explicitly.
+        if ($authentication_providers !== [] && empty($authentication_providers)) {

does not make sense. Unless we of course again update the config schema to allow NULL, and then NULL becomes the way to truly specify "no authentication providers", but then that also doesn't actually make sense, because A) we don't allow that in the code anyway, so why allow it in the config schema?, B) not allowing any authentication providers would still allow anonymous access, and that's exactly what the empty array already signifies.

In other words: that logged error ceases to make sense, and that's all!

Wim Leers’s picture

Of course, if some of you think oh, but isn't it dangerous/misleading to allow anonymous access to a REST resource?, then I don't disagree!

But the thing is that we've always allowed anonymous access to any REST resource!

I personally think what would be clearest is if we would break BC (it'd be a soft BC break, because it's unlikely many sites are relying on anonymous access to REST resources), and do this:

  1. allow 'anon' to be specified in the REST Resource config entity's authentication section
  2. this would not be an actual authentication provider, but would be an explicit signal to allow anonymous access
  3. in \Drupal\rest\Routing\ResourceRoutes::getRoutesForResourceConfig(), set _user_is_logged_in: 'TRUE' unless this 'anon' authentication provider is explicitly mentioned
  4. the end result would be that anonymous access is forbidden unless a REST resource's configuration explicitly allowed it — hence removing the requirement that if you don't want anonymous users to access REST resources, that they have some permission (which btw is the case by default, because \Drupal\rest\Plugin\ResourceBase::permissions() generates a permission for every method for every resource, unless you opt out from that)
gabesullice’s picture

@Wim Leers I like what you have to say in #19. I much prefer explicitly "allowing" anon to access the resource over presuming that anonymous is allowed simply because no authentication provider is configured (if we were to remove the error and log message).

My only concern is with CSRF tokens (I do not have deep enough knowledge of this to know if it's a legitimate concern). By not having cookie based auth, even for anonymous users, then we lose the anonymous session cookie, which I believe plays some role in creating CRSF tokens. Are we potentially enabling unexpected behavior with that? Will requests that should seem to work start getting blocked or will IPs start being put in the flood table? As I said, this may be a totally unfounded concern, but I want to raise it just in case it is not.

Wim Leers’s picture

By not having cookie based auth, even for anonymous users, then we lose the anonymous session cookie, which I believe plays some role in creating CRSF tokens.

Glad you raised it, I've gotten confused by that myself too :)

CSRF tokens are only necessary for cookie auth. Basic auth, OAuth 2 etc are not vulnerable to CSRFs (because you can embed a URL that would be triggered by an unwitting user, but it would never include Basic Auth or OAuth 2 request headers — the same is not true for cookies).

Furthermore, allowing anonymous implies allowing anybody to access a resource, so CSRF is irrelevant: anybody can do these things. (So you'd probably only allow GET for anonymous users.)

The last submitted patch, 17: 2817737-17-test-only.patch, failed testing.

The last submitted patch, 18: 2817737-18-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 18: 2817737-18.patch, failed testing.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Wim Leers’s picture

Wim Leers’s picture

Priority: Normal » Major
Related issues: +#2291055: REST resources for anonymous users: register

Actually, this means that \Drupal\user\Plugin\rest\resource\UserRegistrationResource which was added in #2291055: REST resources for anonymous users: register cannot be used at all in practice.

If you set this up and comply with REST UI's demand to specify an authentication method and choose e.g. basic_auth, registration fails because:

{
  "message": "Only anonymous users can register a user."
}

And if you manually modify the user_resource REST Resource Config entity, so that it has:

authentication: []

(i.e. no authentication mechanisms) then the corresponding REST route is never created.

The only way to have it work is to associate the cookie authentication mechanism… and then not send a session cookie. Because \Drupal\user\Authentication\Provider\Cookie is a global authentication provider, this is accepted. This is a huge WTF.

EDIT: and yes, we have test coverage: \Drupal\user\Tests\RestRegisterUserTest. But it only works due to chance: it doesn't explicitly enable the cookie authentication provider, that just happens to be the default. in RESTTestBase::enableService(). IOW: this test coverage only works by accident!

Wim Leers’s picture

Issue summary: View changes

Improved issue summary: explained how the current REST test coverage for the anonymous use (NodeJsonAnonTest, CommentHalJsonAnonTest, and many dozens others) manage to work around this.

Wim Leers’s picture

Issue summary: View changes
Status: Needs work » Active
Issue tags: +Security improvements

Revamped IS.

There are now two proposed resolutions in the IS. Solution 1 == patch in #18. Solution 2 does not yet have a patch, but was proposed in #19.

This is blocked on a decision which solution is preferred. Hopefully we can get a framework manager to express a preference for either one, otherwise we may be wasting a lot of effort.

Wim Leers’s picture

Issue summary: View changes
dawehner’s picture

Status: Active » Needs review
FileSize
1.34 KB

I totally agree that this should be explicit rather than implicit.

Of course, if some of you think oh, but isn't it dangerous/misleading to allow anonymous access to a REST resource?, then I don't disagree!

This statement is confusing. How is this dangerous, its really common to render stuff for the anonymous user.

allow 'anon' to be specified in the REST Resource config entity's authentication section
this would not be an actual authentication provider, but would be an explicit signal to allow anonymous access
in \Drupal\rest\Routing\ResourceRoutes::getRoutesForResourceConfig(), set _user_is_logged_in: 'TRUE' unless this 'anon' authentication provider is explicitly mentioned
the end result would be that anonymous access is forbidden unless a REST resource's configuration explicitly allowed it — hence removing the requirement that if you don't want anonymous users to access REST resources, that they have some permission (which btw is the case by default, because \Drupal\rest\Plugin\ResourceBase::permissions() generates a permission for every method for every resource, unless you opt out from that)

I don't see a problem, it could be literal a anon provider, which returns an anonymous user as result of the authentication.

Status: Needs review » Needs work

The last submitted patch, 31: 2817737-31.patch, failed testing. View results

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

malcolm_p’s picture

Not sure if this issue is blocked in 8.4 but I wanted to weigh in in favor of option #1.

In the case of rest views, this can be particularly confusing because the requirement for authentication will supercede access controls based on permissions. I think that requiring authentication as a means of access rather than relying on the standard access control system via permisisons and roles. If the user can't be authenticated because no authentication providers are defined they should be considered anonymous.

Wim Leers’s picture

Assigned: Wim Leers » dawehner

I somehow never saw #31! :(

This statement is confusing. How is this dangerous, its really common to render stuff for the anonymous user.

Sorry, I meant "allowing anon access by default".

I don't see a problem, it could be literal a anon provider, which returns an anonymous user as result of the authentication.

+1!

I like where this patch is going, @dawehner++

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1.49 KB

Rebased, let's see how many failures it has today. Even though the routing system now has fewer bugs, the number of failures is likely much higher, simply because there's far better REST test coverage now.

Status: Needs review » Needs work

The last submitted patch, 36: 2817737-36.patch, failed testing. View results

dawehner’s picture

Assigned: dawehner » Unassigned
Status: Needs work » Needs review
FileSize
1.34 KB
594 bytes

This might fix a good bunch of errors ...

Status: Needs review » Needs work

The last submitted patch, 38: 2817737-38.patch, failed testing. View results

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1.72 KB
3.18 KB

HAH! Nice! 👌

All basic auth tests are failing like this:

Failed asserting that 403 is identical to 401.

Now they are 403s saying that the used authentication method is not allowed. But it used to be a 401, thanks to \Drupal\Core\EventSubscriber\AuthenticationSubscriber::onExceptionSendChallenge() converting the 403 exception (AccessDeniedHttpException) into a 401.

Now it's impossible in practice to ever reach that point. Because in \Drupal\Core\EventSubscriber\AuthenticationSubscriber::onKernelRequestFilterProvider(), $this->authenticationProvider->applies($request) returns true for Anon (which makes sense), which results in that 403 exception.
But then because that exception is thrown, we reach \Drupal\Core\EventSubscriber\AuthenticationSubscriber::onExceptionSendChallenge(), we check !$this->authenticationProvider->applies($request) (the inverse!), which can then of course never be true *also*.

The problem is that onExceptionSendChallenge() never took into account that some authentication provider would always apply. It assumed it would only ever be called when access was denied because the user wasn't logged in. This is wrong.

What we should do, is update \Drupal\Core\EventSubscriber\AuthenticationSubscriber::onExceptionSendChallenge() to iterate over only the authentication providers that allowed on the requested route, instead of iterating over all authentication providers.

I don't yet see how to achieve that without making BC breaks. For now, here's a work-around that allows us to focus on fixing the other problems first. (I spent an hour trying.)

Status: Needs review » Needs work

The last submitted patch, 40: 2817737-40.patch, failed testing. View results

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Wim Leers’s picture

Somebody asked again about this WTF in #2949327-6: Help with REST authentication.3.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Solthun’s picture

Ran into this issue when I found out a headless site in prod wasn't creating content since before summer.
First, thanks for the great work!
I wanted a rounded solution so went for the latest patch and I wanted to add some details here.
The app used the route '/entity/node ....' as a rest endpoint by posting for submissions to it and it worked back in the day.
Tried to fix the content creation by applying the patch and changing rest.resource.entity.node.yml by adding this to the end:
authentication:
- anon
Until now it did not work since the import itself failed without an authentication method set.
Big surprise, didn't work this time either.
After some debug, I can tell you this:
The 'new' rest endpoint '/node' works great with this patch, but the canonical path '/entity/{entity_type}' still fails.

Did not have the time to dig deeper and find the root cause, but still might help someone struggling with this.

PQ’s picture

Status: Needs work » Needs review
FileSize
2.72 KB

Rerolled against 8.6.x (also applies to 8.5.x and 8.7.x)

Someone more clued up on this then me might check if the tests it adds in BasicAuthResourceTestTrait->assertResponseWhenMissingAuthentication() are now redundant with the interim changes to that file.

Status: Needs review » Needs work

The last submitted patch, 46: 2817737-46.patch, failed testing. View results

oknate’s picture

+1 on this.

I don't set a lot of these up, but when I do, I usually forget to set the permissions. I was thinking maybe there could be a checkbox (Allow for anonymous and authenticated users) on the form. But then I found this issue, and it makes more sense if you can just not select auth, and then there's no access check.

I went ahead a created a new issue for setting permissions:

#3062876: Allow setting resource permissions from config form

It would at least remove some of the pain of finding your resource is inaccessible to anonymous users on the QA site. And it wouldn't require an upgrade path.

Wim Leers’s picture

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev
dafeder’s picture

Wondering has anyone revisited this issue recently? I have some custom routes I'd like to re-implement as a rest plugin but I need the GETs to allow anonymous access. Have people discovered other workarounds in D9?

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

kksandr’s picture

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.