Problem/Motivation
On a site with a node with ID 1, the following code returns the same:
\Drupal\node\Entity\Node::loadMultiple(['1']);
\Drupal\node\Entity\Node::loadMultiple(['01']);
\Drupal\node\Entity\Node::loadMultiple([' 1']);
\Drupal\node\Entity\Node::loadMultiple([' 01']);
In case of use the Node::load() method instead, it returns the node entity only when 1 or '1' is used as entity ID, getting NULL in the rest of the cases ('01', ' 1', ' 01') as expected.
It actually happens with any content entity type. As far I can see, the root cause is located at ContentEntityStorageBase::cleanIds(), that alters numeric entity IDs processing them by intval().
Entity URLs affected
From Drupal 8.7, due to the changes introduced at #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing, URLs with numeric entity IDs are affected by this. For example, /node/1 and /node/01 returns the same page. As concluded in the related issue #2674618-35: System behave differently on URL "node/00001" and "node/1", /node/1 and /node/01 (001, 0001, and so on) are different URLs and must have different response.
This may affect several aspects. It could be considered a vulnerability from the SEO point of view, since /node/1 and /node/01 would be seen as duplicated content by Google which penalizes it.
It also could have many other side-effects. For instance, custom access callbacks receives arguments as string and could end in errors when an ID with leading zeros are received. It happens with the contact core module. In a fresh D9.2 installation with the standard profile, the URL /user/01 ends in call to a member function on null exception.
Steps to reproduce
In a fresh D9.2 installation with the standard profile, create an article at /node/add/article with arbitrary content. It should be at /node/1. The new node can also be accessed at /node/01 or edited at /node/001/edit
Logged as the user 1, at /user/01 should end in 404 but Drupal tries to render the user canonical page, ending in a crash related with the Contact module from core.
Proposed resolution
Since entity IDs are strings, '1' is not equal to '01' or ' 1' or ' 01'. Any EntityStorageInterface::loadMultiple() implementation must be aware of that.
Remaining tasks
Review, test.
User interface changes
N/A
API changes
None.
Data model changes
None.
Release notes snippet
TBD.
| Comment | File | Size | Author |
|---|---|---|---|
| #26 | Screenshot 2025-12-09 at 11.56.11 AM.png | 48.82 KB | samitk |
| #15 | interdiff_3191566_14-15.txt | 1.37 KB | andregp |
| #15 | 3191566_15.patch | 2.72 KB | andregp |
| #15 | 3191566_15-tests-only.patch | 1.25 KB | andregp |
| #14 | 3191566_14.patch | 2.86 KB | andregp |
Issue fork drupal-3191566
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
manuel.adanInitial fix that illustrates the issue causes and try it against tests.
Comment #3
manuel.adanComment #4
manuel.adanRight patch generation.
Comment #7
larowlanComment #8
kristen polNitpick: 80 character wrapping not quite right.
Comment #9
kristen polForgot to tag for testing.
Comment #10
joshua1234511Reviewed the patch from #4
Manual tested the patch on 9.4 on standard profile.
Steps followed from Issue summary
The new node can also be
- accessed at /node/01 and edited at /node/01/edit
- accessed at /node/001 and edited at /node/001/edit
Rerolled the path with Nitpick from #8
Comment #12
sonnyktConfirming patch #10 works with 9.4.x (standard profile) and an existing 9.3.14 site:
Before applying the patch:
* Both
/node/1/editand/node/01/editreturn the same node edit form for node #1.* Both
/node/1and/node/01return the same page for node #1.* Both
/user/1/editand/user/01/editreturn the same edit form for user #1.After applying the patch:
*
/node/1/editand/user/1/editwork as usual.*
/node/01/editand/user/01/editnow return error 404.*
/node/01now returns error 404.drush phpwith the patch:Comment #13
catchThis could use some test coverage.
Comment #14
andregp commentedSending a test-only patch which is expected to fail and the total patch.
I'm not sending an interdiff this time because I didn't change anything on
SqlContentEntityStorage.phpso the test-only patch is the inderdiff.Edit: I'm not sure if this test is good enough as it doesn't cover cases like ' 01' or ' 1'.
Comment #15
andregp commentedI believe I've came up with a better test.
This time I assert using the loadMultiple() itself and I included the cases with space in the id.
Again sending a test only patch, which should fail and the complete patch.
Comment #18
kristen polThanks! Removing tests tag.
Comment #20
smustgrave commentedVerified this on Drupal 10.1
Went to a URL node/0009 and it loaded the node/9 page
Applied patch
And now I get a page not found
Comment #21
larowlanThis doesn't need to be a functional test, we should create the node via the Node::create API and move it to a Kernel test
Comment #23
ressaThanks for working on fixing this, which I discovered by chance a few months ago. It looks like the theme engine can be bypassed with this method.
Will it be back-ported to Drupal 7, which is also affected?
Comment #24
luke.stewart commentedComment #26
samitk commentedNot reproducible in 11.x branch. Running the following code produces the expected output shown in the attached screenshot.
dsm(\Drupal\node\Entity\Node::loadMultiple([' 1']));
dsm(\Drupal\node\Entity\Node::loadMultiple(['1']));
dsm(\Drupal\node\Entity\Node::loadMultiple(['01']));
dsm(\Drupal\node\Entity\Node::loadMultiple([' 1']));
dsm(\Drupal\node\Entity\Node::loadMultiple([' 01']));
Comment #28
smustgrave commentedMay be good to least have test coverage around this