Back

Masking sensitive values

🧭

Is this package for you?

17 reasons, each a before and an after. Select the problems you have and see how many match.

Take the scorecard
🔒

Some values belong in code but not on screen

A payment webhook URL or a provisioned sender number is version-controlled configuration — but you still would not want it scrolling past in a CI log or sitting in a screenshot. Mark the property #[Sensitive] and the console prints ******** instead.

Nothing else changes. toArray() and your application still receive the real value.

Three rules, in order

1

Empty stays empty

A value that was never set prints as nothing. Masking it would imply a secret exists where none does.

2

Marked wins

A property carrying #[Sensitive] is masked whatever it holds — string, int, enum. The developer said so.

3

Then the name guess

Unmarked string properties whose name contains key, secret, password or token are masked as a fallback.

What actually happens

Six properties on one class, run through env-settings:show. Every row below is real command output.

Property Type Printed Why
#[Sensitive] $marked_int int ******** Marked — the attribute ignores type entirely.
$api_key string ******** Name contains "key". The fallback working as intended.
$max_tokens int 8000 Name contains "token", but it is an int — so it is shown.
$monkey_api_url string ******** "monkey" contains "key" — hidden for no reason.
#[Sensitive] $marked_empty string (blank) Empty even though marked. Rule 1 comes first.
$passphrase string swordfish Not one of the four fragments — printed in full.

⚠️ It over-matches

monkey_api_url contains key, so a perfectly public URL is hidden — and you are left wondering what the value was.

💥 It under-matches

passphrase, credentials and bearer are printed in full. This is the failure that matters.

So mark the property

The name list is kept only so settings written before the attribute existed stay hidden. It is a guess, not a rule. One attribute removes the guesswork:

#[Sensitive] public string $passphrase,

In this demo

PaymentSettings::$webhook_url
| mode *           | sandbox  | live     |
| currency         | USD      | USD      |
| retry_attempts * | 1        | 5        |
| webhook_url *    | ******** | ******** |

A payment webhook URL usually embeds a token.

NotificationSettings::$sms_from
| sms_provider *          | log      | vonage   |
| sms_from *              | ******** | ******** |
| default_channel *       | log      | sms      |
| rate_limit_per_minute * | 100      | 200      |

A provisioned sender number is tied to the account.

🔀

Still flagged as differing

Both columns read ********, yet the row keeps its *. The diff compares the real values and masks only when printing, so you still learn that staging and production disagree — without learning what either one is.

📦

Display only

Masking lives in the commands, not the model. toArray(), serialisation and every line of your application still get the real value. Nothing breaks by marking a property.

Generate one already marked

env-settings:make can apply the attribute for you. Each name passed to --sensitive must also appear in --properties.

php artisan env-settings:make WebhookSettings \
  --properties="endpoint:string,signing_secret:string" \
  --sensitive="signing_secret"
Read the masking docs Enum-valued settings The completeness gate All four commands #[Environment] In tests Overrides Is this for you? Back to the demo