[PHP-DEV] [rfc] str_mask function

Why is this needed in php itself? Masking seems very use-case specific and would be better done inside php library with extensible modules for different types of data to mask.

Ilia Alshanetsky
Technologist, CTO, Entrepreneur
E: ilia@ilia.ws
T: @iliaa
B: http://ilia.ws

On Fri, Sep 18, 2026, 2:29 p.m. سپهر محمودی <sepehrphpr@gmail.com> wrote:

Hi everyone,

I’m officially starting my work on the ext/intl and standard string functions, and I’m very excited to share my first major proposal for PHP 8.7!

I have successfully set up my local development environment and compiler on my machine, and everything is up and running smoothly.

As part of this, I would like to propose a new native function called str_mask().

Proposal Overview

The str_mask() function is designed to securely mask portions of a string using a specified mask character. This is extremely useful for handling sensitive user data like credit card numbers, phone numbers, and tokens.

Signature:
str_mask(string $string, string $mask_char = ‘*’, int $offset = 0, ?int $length = null): string

Examples

  1. Masking a credit card (positive offset & length):
    $credit_card = ‘1234567890123456’;
    $masked_card = str_mask($credit_card, ‘*’, 4, 8);
    // Output: 1234********3456

  2. Masking a phone number (negative offset to count from the end):
    $phone_number = ‘+989123456789’;
    $masked_phone = str_mask($phone_number, ‘X’, -4);
    // Output: +9891234XXXX

You can find all the details, implementation plans, and RFC discussions here:
https://wiki.php.net/rfc/str_mask

Looking forward to hearing your feedback and thoughts!

Best regards,
Sepehr Mahmoudi

در تاریخ دوشنبه ۲۱ سپتامبر ۲۰۲۶، ۰۴:۰۴ Morgan <weedpacket@varteg.nz> نوشت:

On 2026-09-21 03:48, سپهر محمودی wrote:

Just to clarify the intent: str_mask() is designed not as domain/
business logic (like slugification), but as a low-level string
manipulation primitive—very similar to existing core primitives like
str_pad(), substr_replace(), and str_repeat().

The main reasons for considering it at the core level rather than
userland/PECL are:

  1. First-class integration with #[\SensitiveParameter] to ensure safe
    handling of sensitive/PII data in stack traces.
  2. Providing a strict, fail-closed contract natively without the
    allocation and performance overhead of composing multiple userland calls.

See, this #[\SensitiveParameter] thing is what makes it look to me like
business logic rather than low-level; it assumes that what is being
masked is - well - sensitive, according to some criterion. There might
be other reasons why someone will want to mask part of a string that
isn’t sensitive (anyone for a game of Hangman?).

It’s reasonable to apply #[\SensitiveParameter] to the $password field
of openssl_password_hash() or PDO::connect() because you are by
definition passing a sensitive value (a password) in that parameter. But
for low-level string manipulation? Strings are arbitrary sequences of
bytes and “sensitivity” is not an inherent property of such things.

Morgan


Hi Morgan,

That is a very valid and fair point.

The intention was to provide a helpful security default, but you are right: str_mask() is fundamentally a low-level string manipulation primitive, and masking is also used in non-sensitive contexts (like word games, anonymized identifiers, or formatting). Treating it as a generic string primitive without baking in domain-specific assumptions makes total sense.

I will remove the #[\SensitiveParameter] attribute from the RFC to keep the function purely general-purpose, consistent with other ext/standard string functions.

Thanks for the insightful feedback!

Best regards,
Sepehr

Hello dear Sepher.

So I have a question:

The main reasons for considering it at the core level rather than userland/PECL are:

  1. First-class integration with #[\SensitiveParameter] to ensure safe handling of sensitive/PII data in stack traces

Wasn’t the objective of the RFC to have first class integration with #[\SensitiveParameter]?

Also as you stated in that sentence, you want to injure handling of sensitive pii data in stack trades. However that’s kind of uses very uses as a very specific use case. But was he RFC first design for that kind of use case?

Because it is kind of hard to understand it, the reason of the RFC, if in the same mails you sent also you state that you are rectifying the RFC. It sounds like the RFC first is designed for a very specific kind of use, which is very niche, and apart from that it is a very dynamically designed RFC.

It just I’m the kind of lost of why is the using in the first place. If you can do it in user land perfectly. And in the case of using c, you have extensions that are not part of the main API but solves a very important problem and issue. Why is the need in the main PHP API? Because it sounds like you’re trying just to push something into the main API just for the sake of pushing it, and from each Mail the necessity of using it or it’s consideration at least gradually decreases

Kind regards,

David Maye

El 21/09/2026 11:56 سپهر محمودی sepehrphpr@gmail.com escribió:

در تاریخ دوشنبه ۲۱ سپتامبر ۲۰۲۶، ۰۴:۰۴ Morgan <weedpacket@varteg.nz> نوشت:

On 2026-09-21 03:48, سپهر محمودی wrote:

Just to clarify the intent: str_mask() is designed not as domain/
business logic (like slugification), but as a low-level string
manipulation primitive—very similar to existing core primitives like
str_pad(), substr_replace(), and str_repeat().

The main reasons for considering it at the core level rather than
userland/PECL are:

  1. First-class integration with #[\SensitiveParameter] to ensure safe
    handling of sensitive/PII data in stack traces.
  2. Providing a strict, fail-closed contract natively without the
    allocation and performance overhead of composing multiple userland calls.

See, this #[\SensitiveParameter] thing is what makes it look to me like
business logic rather than low-level; it assumes that what is being
masked is - well - sensitive, according to some criterion. There might
be other reasons why someone will want to mask part of a string that
isn’t sensitive (anyone for a game of Hangman?).

It’s reasonable to apply #[\SensitiveParameter] to the $password field
of openssl_password_hash() or PDO::connect() because you are by
definition passing a sensitive value (a password) in that parameter. But
for low-level string manipulation? Strings are arbitrary sequences of
bytes and “sensitivity” is not an inherent property of such things.

Morgan


Hi Morgan,

That is a very valid and fair point.

The intention was to provide a helpful security default, but you are right: str_mask() is fundamentally a low-level string manipulation primitive, and masking is also used in non-sensitive contexts (like word games, anonymized identifiers, or formatting). Treating it as a generic string primitive without baking in domain-specific assumptions makes total sense.

I will remove the #[\SensitiveParameter] attribute from the RFC to keep the function purely general-purpose, consistent with other ext/standard string functions.

Thanks for the insightful feedback!

Best regards,
Sepehr

در تاریخ دوشنبه ۲۱ سپتامبر ۲۰۲۶، ۰۴:۳۱ Ilia <ilia@ilia.ws> نوشت:

Why is this needed in php itself? Masking seems very use-case specific and would be better done inside php library with extensible modules for different types of data to mask.

Ilia Alshanetsky
Technologist, CTO, Entrepreneur
E: ilia@ilia.ws
T: @iliaa
B: http://ilia.ws

On Fri, Sep 18, 2026, 2:29 p.m. سپهر محمودی <sepehrphpr@gmail.com> wrote:

Hi everyone,

I’m officially starting my work on the ext/intl and standard string functions, and I’m very excited to share my first major proposal for PHP 8.7!

I have successfully set up my local development environment and compiler on my machine, and everything is up and running smoothly.

As part of this, I would like to propose a new native function called str_mask().

Proposal Overview

The str_mask() function is designed to securely mask portions of a string using a specified mask character. This is extremely useful for handling sensitive user data like credit card numbers, phone numbers, and tokens.

Signature:
str_mask(string $string, string $mask_char = ‘*’, int $offset = 0, ?int $length = null): string

Examples

  1. Masking a credit card (positive offset & length):
    $credit_card = ‘1234567890123456’;
    $masked_card = str_mask($credit_card, ‘*’, 4, 8);
    // Output: 1234********3456

  2. Masking a phone number (negative offset to count from the end):
    $phone_number = ‘+989123456789’;
    $masked_phone = str_mask($phone_number, ‘X’, -4);
    // Output: +9891234XXXX

You can find all the details, implementation plans, and RFC discussions here:
https://wiki.php.net/rfc/str_mask

Looking forward to hearing your feedback and thoughts!

Best regards,
Sepehr Mahmoudi


Hi Ilia,

Thanks for sharing your perspective.

I completely agree that high-level, schema-aware masking (such as formatting credit cards, emails, or complex data structures) belongs in userland libraries and extensible packages.

However, str_mask() is intentionally not designed to be a high-level formatter or business-logic helper. Instead, it is a low-level, positional string primitive—analogous to str_pad() or substr_replace()—that simply replaces a byte range with a repeating character without intermediate string allocations.

Userland libraries currently implement this through substr_replace($str, str_repeat(...)) or regex loops, incurring repeated string allocations and edge-case validation overhead. Having a single native primitive provides the fast, fail-safe foundation that those higher-level libraries can build upon.

Best regards,
Sepehr

Sorry from the last mail I meant Ensure, not injure. Grammatical character is on :sweat_smile:. Sorry everyone

El 21/09/2026 12:15 David Maye david.maye@seigisoft.com escribió:

Hello dear Sepher.

So I have a question:

The main reasons for considering it at the core level rather than userland/PECL are:

  1. First-class integration with #[\SensitiveParameter] to ensure safe handling of sensitive/PII data in stack traces

Wasn’t the objective of the RFC to have first class integration with #[\SensitiveParameter]?

Also as you stated in that sentence, you want to injure handling of sensitive pii data in stack trades. However that’s kind of uses very uses as a very specific use case. But was he RFC first design for that kind of use case?

Because it is kind of hard to understand it, the reason of the RFC, if in the same mails you sent also you state that you are rectifying the RFC. It sounds like the RFC first is designed for a very specific kind of use, which is very niche, and apart from that it is a very dynamically designed RFC.

It just I’m the kind of lost of why is the using in the first place. If you can do it in user land perfectly. And in the case of using c, you have extensions that are not part of the main API but solves a very important problem and issue. Why is the need in the main PHP API? Because it sounds like you’re trying just to push something into the main API just for the sake of pushing it, and from each Mail the necessity of using it or it’s consideration at least gradually decreases

Kind regards,

David Maye

El 21/09/2026 11:56 سپهر محمودی sepehrphpr@gmail.com escribió:

در تاریخ دوشنبه ۲۱ سپتامبر ۲۰۲۶، ۰۴:۰۴ Morgan <weedpacket@varteg.nz> نوشت:

On 2026-09-21 03:48, سپهر محمودی wrote:

Just to clarify the intent: str_mask() is designed not as domain/
business logic (like slugification), but as a low-level string
manipulation primitive—very similar to existing core primitives like
str_pad(), substr_replace(), and str_repeat().

The main reasons for considering it at the core level rather than
userland/PECL are:

  1. First-class integration with #[\SensitiveParameter] to ensure safe
    handling of sensitive/PII data in stack traces.
  2. Providing a strict, fail-closed contract natively without the
    allocation and performance overhead of composing multiple userland calls.

See, this #[\SensitiveParameter] thing is what makes it look to me like
business logic rather than low-level; it assumes that what is being
masked is - well - sensitive, according to some criterion. There might
be other reasons why someone will want to mask part of a string that
isn’t sensitive (anyone for a game of Hangman?).

It’s reasonable to apply #[\SensitiveParameter] to the $password field
of openssl_password_hash() or PDO::connect() because you are by
definition passing a sensitive value (a password) in that parameter. But
for low-level string manipulation? Strings are arbitrary sequences of
bytes and “sensitivity” is not an inherent property of such things.

Morgan


Hi Morgan,

That is a very valid and fair point.

The intention was to provide a helpful security default, but you are right: str_mask() is fundamentally a low-level string manipulation primitive, and masking is also used in non-sensitive contexts (like word games, anonymized identifiers, or formatting). Treating it as a generic string primitive without baking in domain-specific assumptions makes total sense.

I will remove the #[\SensitiveParameter] attribute from the RFC to keep the function purely general-purpose, consistent with other ext/standard string functions.

Thanks for the insightful feedback!

Best regards,
Sepehr