A caller has one job at the moment of the return: read the value and act on it. Everything the function learned in the process of producing that value, every branch it took, every precondition that failed, every lower-level library it delegated to, is compressed through the declared return type on the way out. When the type is narrower than the behavior, the compression loses signal. The signal the compression loses is the signal the caller needed to make the right decision.
The failure is structural, not careless. Four exhibits, four languages, four interpreters, and the same shape in every one.
musl's __crypt_sha256 and __crypt_sha512 wrappers have return type char *. They need to express at least three outcomes: the SHA computation produced a hash, the input key exceeded 256 bytes, the library's internal self-test failed. The type cannot carry three values without additional grammar, so three outcomes collapse into the string "*". OpenSSH reads the result through /etc/shadow's convention where "*" means "no password hash lives here; a password login must never succeed." The caller reads one two-byte string; the function wrote three different failures into it. musl's own DES implementation, written a decade earlier, carries a one-character defense against exactly this collision (return (setting[0]=='*') ? "x" : "*";). The SHA paths added later dropped it. The 2016 timing-side-channel patch introduced the unauthenticated reachability; the 2026 bypass used it (CVE-2026-123158).
Brotli's DecodeBlockTypeAndLength has return type BROTLI_BOOL. The function expresses three outcomes: success, need-more-input (retry the frame after more bytes arrive), format-error (reject the stream permanently). Two map to BROTLI_FALSE. The hot-path macro at the call sites throws the return value on the floor and discriminates based on how many input bytes the decoder consumed on the failing call; the format-error branch consumed none, so the macro treats the rejection as a retry and loops. The patch widens the signature to BrotliDecoderErrorCode, mints a new negative value (-17 BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH), and converts the three call sites to a macro that actually reads the return. The commit title is "Detect and report invalid stream earlier." Nothing in that title tells a dependency-bump bot the change is a fix for a security bug, and eight months pass between the upstream patch and the downstream advisory (CVE-2026-111497).
Barracuda's Index.pm password-change handler returns a Perl scalar named $userrole. The success path returns $userrole. The ERROR_PASSWD_CHANGE branch also returns $userrole. There is no third shape for "the password change I attempted on your behalf failed; do not consult my return value for authorization." The dispatcher reads the value as authorization in both cases. The patch does not widen the return type; the patch routes the failure branch through a different exit function (_bailout_with_json_login_response) whose side effect is terminating response processing before the dispatcher runs. The sentinel is unchanged; the exit is routed around it. The variant is instructive: when the surrounding code cannot be refactored to widen the type, the equivalent fix is to make sure the colliding branch never reaches the reader (CVE-2026-116606).
Hippoo's get_user_permissions is a PHP method returning mixed, intended as a role array or null. Three branches return null: no current user, administrator, authenticated user with no configured role policy. Two of those returns carry the inline comment // Full access. The downstream gate has_role_access reads $perms === null as return true; // admin or unrestricted, and the unauthenticated visitor inherits administrator trust through the shared sentinel. The fix separates the sentinel: null for administrator, false for "no user." No authentication check is added anywhere (CVE-2026-49060).
The pattern lives in the gap between the function's internal behavior-space and its declared return type. The compression is where the sentinel acquires its multiple meanings; the caller is where the ambiguity pays out. When one of the colliding meanings is "I succeeded," another is "I failed," and the caller's branch is a security decision, the collapse is a bypass. When the colliding meanings are "retry" and "reject," and the caller loops on retry, the collapse is a denial-of-service. When the colliding meanings are "administrator" and "anonymous," and the caller reads either as allow, the collapse is unauthenticated privilege escalation.
What makes the fix a security-shaped diff is not any added permission check. The patches in all four exhibits add no caller-side check. They widen the return type (Brotli), redirect the exit path to avoid the collision (Barracuda), raise the caller's minimum-length precondition to exclude one of the colliding values (OpenSSH), or split the sentinel across two of the colliding states (Hippoo). None of these look like security to a dependency bumper reading commit titles. The signature change, the extra return line, the new enum case: those are the receipts. A reviewer who learns to read return-type widenings as security fixes has a cheap cross-project signal that neither the CVE record nor the commit message will give them.
The class repeats because every language ships a cheap narrow return type as its default: bool, int zero-means-success, None-or-value, a scalar, a char * whose convention is set by the surrounding system. The ergonomic default of each language is exactly the shape that cannot carry more than two outcomes. The function's internal behavior-space grows past the type as the code matures; the type does not grow with it. The comments at each return statement grow instead. The compiler, the linter, and the caller read the value through the type; only a human reader reaches the comments. Each comment at a return line that disambiguates meaning is a candidate exhibit of this class waiting for a reachable caller.