Skip to content

Conversation

@henriquemoody
Copy link
Member

The CallableStringifier is undeniably useful, but its convenience comes at a security risk. By allowing strings and arrays to be interpreted as callables by default, we risk exposing sensitive data—like passwords or hostnames—hidden within those structures. Furthermore, it is often frustrating to have arbitrary strings automatically turned into callable representations.

To fix this, I am moving to a "secure-by-default" stance. The stringifier now defaults to closure-only mode. Closures are significantly less likely to expose sensitive data in their contracts, making this a much safer baseline.

I have made an exception for Fibers. Since a Fiber explicitly contains a callable, we know the usage is intentional, and being explicit about the underlying callable is important for debugging clarity.

If someone still needs the original behavior, they can have it, but they must be intentional. They can restore the previous functionality by manually defining the $closureOnly parameter as false in the constructor.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restricts the CallableStringifier to only stringify closures by default, addressing security concerns about potential data leakage from stringifying arbitrary callables like strings and arrays. The change implements a "secure-by-default" approach with a closureOnly parameter that defaults to true.

Changes:

  • Modified CallableStringifier to add closureOnly parameter (default: true) and check for closures first
  • Updated closure signature format from function (...) to Closure { [static] fn (...) }
  • Updated tests to use named data providers and test both closure-only and permissive modes

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Stringifiers/CallableStringifier.php Added closureOnly parameter with secure default, restructured logic to handle closures first, updated signature format
src/Stringifiers/CompositeStringifier.php Created separate CallableStringifier instances - one closure-only for general use, one permissive for FiberObjectStringifier
tests/unit/Stringifiers/CallableStringifierTest.php Split tests into closure-specific and non-closure callable tests, added test coverage for new default behavior
tests/integration/stringify-callable.phpt Removed non-closure callable examples, updated expected output format
tests/src/Double/FakeStringifier.php Changed return type from ?string to string
tests/fixtures/WithMethods.php Changed return type from ?static to static
phpcs.xml.dist Added exclusions for static closure rule in test files
README.md Removed non-closure callable examples from documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@henriquemoody henriquemoody force-pushed the callable branch 2 times, most recently from ce07549 to 49d2071 Compare January 19, 2026 00:55
The `CallableStringifier` is undeniably useful, but its convenience comes at a
security risk. By allowing strings and arrays to be interpreted as callables
by default, we risk exposing sensitive data—like passwords or hostnames—hidden
within those structures. Furthermore, it is often frustrating to have arbitrary
strings automatically turned into callable representations.

To fix this, I am moving to a "secure-by-default" stance. The stringifier now
defaults to closure-only mode. Closures are significantly less likely to expose
sensitive data in their contracts, making this a much safer baseline.

I have made an exception for Fibers. Since a Fiber explicitly contains a
callable, we know the usage is intentional, and being explicit about the
underlying callable is important for debugging clarity.

If someone still needs the original behavior, they can have it, but they must be
intentional. They can restore the previous functionality by manually defining
the `$closureOnly` parameter as `false` in the constructor.
@henriquemoody henriquemoody merged commit 676f823 into Respect:main Jan 19, 2026
5 checks passed
@henriquemoody henriquemoody deleted the callable branch January 19, 2026 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant