Skip to content

[Issue] Validate numeric region ownership in AbstractAddress::getRegionId() #41184

Description

@m2-assistant

This issue is automatically created based on existing pull request: #41164: Validate numeric region ownership in AbstractAddress::getRegionId()


Background

The underlying getRegionId() behavior predates the recent Magento security updates.

We started seeing the resulting inconsistent address state block checkout after applying the July 2026 Magento security hardening associated with APSB26-73.

One relevant change in that update moves the validateQuoteAddress plugin from the REST-specific DI scope:

vendor/magento/module-quote/etc/webapi_rest/di.xml

to the global quote DI scope:

vendor/magento/module-quote/etc/di.xml

Previously:

<type name="Magento\Quote\Model\Quote">
    <plugin name="validateQuoteAddress" type="Magento\Quote\Plugin\QuoteAddress" />
</type>

was registered only for REST requests.

After the security update, the plugin is registered globally for:

Magento\Quote\Model\Quote

so quote address validation is applied to additional PHP and checkout flows that call setShippingAddress() or setBillingAddress(), not only REST-specific flows.

This security change does not create the invalid region_id, and Magento\Quote\Plugin\QuoteAddress itself is not responsible for validating whether a region belongs to a country.

The inconsistent state can already exist before validation:

country_id = NL
region_id  = null
region     = "80"

and AbstractAddress::getRegionId() can then materialize the country-mismatched ID:

country_id = NL
region_id  = 80
region     = "80"

Persisted data observed

We also found already persisted addresses where region_id belonged to a different country than the address itself.

The following query can be used to identify these records in quote addresses:

SELECT
    qa.address_id,
    qa.quote_id,
    qa.address_type,
    qa.country_id,
    qa.region_id,
    qa.region,
    dcr.country_id AS region_country_id,
    dcr.code,
    dcr.default_name,
    qa.updated_at
FROM quote_address AS qa
INNER JOIN directory_country_region AS dcr
    ON dcr.region_id = qa.region_id
WHERE qa.region_id IS NOT NULL
  AND qa.country_id IS NOT NULL
  AND qa.country_id <> dcr.country_id
ORDER BY qa.updated_at DESC;

In our database this returned, among others, combinations such as:

address country   region_id   actual region country
AT                80          DE
NL                80          DE
LU                80          DE

Region ID 80 is:

DE / BAW / Baden-Württemberg

The same inconsistency was also present in saved customer addresses:

SELECT
    cae.entity_id,
    cae.country_id,
    cae.region_id,
    cae.region,
    dcr.country_id AS region_country_id,
    dcr.code,
    dcr.default_name,
    cae.updated_at
FROM customer_address_entity AS cae
INNER JOIN directory_country_region AS dcr
    ON dcr.region_id = cae.region_id
WHERE cae.region_id IS NOT NULL
  AND cae.country_id IS NOT NULL
  AND cae.country_id <> dcr.country_id
ORDER BY cae.updated_at DESC;

Historical mismatched customer address records were present as far back as 2024 and 2025.

This provides database-level evidence that invalid country/region combinations existed before the July 2026 security hardening.

The mismatch is ultimately rejected later by Magento address validation during quote/order validation.

In our case this became visible as:

Invalid value "80" for field regionId.

The relevant sequence is therefore:

numeric region value
→ AbstractAddress::getRegionId() can materialize a country-mismatched region_id
→ inconsistent address state can be persisted
→ hardened checkout/quote flow reaches address validation
→ country/region mismatch is rejected
→ checkout fails

Therefore the security hardening should be considered the point at which the existing data-integrity issue became visible in our checkout, not the origin of the invalid state.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue: ready for confirmationReported on 2.4.xIndicates original Magento version for the Issue report.Triage: Dev.ExperienceIssue related to Developer Experience and needs help with Triage to Confirm or Reject it

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions