Commit 3036712be90 for php.net

commit 3036712be90ba605a4a39e44f5d7b6619cd7981f
Author: Jordi Kroon <jkroon@onyourmarks.agency>
Date:   Mon Sep 21 07:19:04 2026 -0400

    tree: Add #[\SensitiveParameter] to more functions taking secrets (#23782)

    * ext/sodium: add SensitiveParameter to missing functions

    * ext/phar: add SensitiveParameter to missing functions

    * ext/snmp: add SensitiveParameter to missing functions

    * ext/sqlite3: add SensitiveParameter to missing functions

    * ext/hash: add SensitiveParameter to missing functions

diff --git a/ext/hash/hash.stub.php b/ext/hash/hash.stub.php
index d86fa8d2c7e..b89f290b566 100644
--- a/ext/hash/hash.stub.php
+++ b/ext/hash/hash.stub.php
@@ -83,7 +83,7 @@ function mhash_get_hash_name(int $algo): string|false {}
  * @refcount 1
  */
 #[\Deprecated(since: '8.1')]
-function mhash_keygen_s2k(int $algo, string $password, string $salt, int $length): string|false {}
+function mhash_keygen_s2k(int $algo, #[\SensitiveParameter] string $password, string $salt, int $length): string|false {}

 #[\Deprecated(since: '8.1')]
 function mhash_count(): int {}
@@ -92,7 +92,7 @@ function mhash_count(): int {}
  * @refcount 1
  */
 #[\Deprecated(since: '8.1')]
-function mhash(int $algo, string $data, ?string $key = null): string|false {}
+function mhash(int $algo, string $data, #[\SensitiveParameter] ?string $key = null): string|false {}
 #endif

 final class HashContext
diff --git a/ext/hash/hash_arginfo.h b/ext/hash/hash_arginfo.h
index bc213d02bfb..98cd952cd12 100644
Binary files a/ext/hash/hash_arginfo.h and b/ext/hash/hash_arginfo.h differ
diff --git a/ext/hash/tests/mhash_sensitive_parameter.phpt b/ext/hash/tests/mhash_sensitive_parameter.phpt
new file mode 100644
index 00000000000..c35a82718fe
--- /dev/null
+++ b/ext/hash/tests/mhash_sensitive_parameter.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Test that the key/password parameters of mhash() and mhash_keygen_s2k() are marked sensitive.
+--SKIPIF--
+<?php if (!function_exists('mhash')) { die('skip mhash compatibility layer not available'); } ?>
+--FILE--
+<?php
+declare(strict_types=1);
+
+try {
+    var_dump(mhash(0, null, 'secret-key'));
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+try {
+    var_dump(mhash_keygen_s2k(0, 'secret-password', 'salt', 0));
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+?>
+--EXPECTF--
+Deprecated: Function mhash() is deprecated since 8.1 in %s on line %d
+TypeError: mhash(): Argument #2 ($data) must be of type string, null given in %s:%d
+Stack trace:
+#0 %s(%d): mhash(0, NULL, Object(SensitiveParameterValue))
+#1 {main}
+
+Deprecated: Function mhash_keygen_s2k() is deprecated since 8.1 in %s on line %d
+ValueError: mhash_keygen_s2k(): Argument #4 ($length) must be a greater than 0 in %s:%d
+Stack trace:
+#0 %s(%d): mhash_keygen_s2k(0, Object(SensitiveParameterValue), 'salt', 0)
+#1 {main}
diff --git a/ext/phar/phar_object.stub.php b/ext/phar/phar_object.stub.php
index ae5bd69c32f..dde2719fb1b 100644
--- a/ext/phar/phar_object.stub.php
+++ b/ext/phar/phar_object.stub.php
@@ -196,7 +196,7 @@ public function setDefaultStub(?string $index = null, ?string $webIndex = null):
     public function setMetadata(mixed $metadata): void {}

     /** @tentative-return-type */
-    public function setSignatureAlgorithm(int $algo, ?string $privateKey = null): void {}
+    public function setSignatureAlgorithm(int $algo, #[\SensitiveParameter] ?string $privateKey = null): void {}

     /**
      * @param resource|string $stub
@@ -470,7 +470,7 @@ public function setMetadata(mixed $metadata): void {}
      * @tentative-return-type
      * @implementation-alias Phar::setSignatureAlgorithm
      */
-    public function setSignatureAlgorithm(int $algo, ?string $privateKey = null): void {}
+    public function setSignatureAlgorithm(int $algo, #[\SensitiveParameter] ?string $privateKey = null): void {}

     /**
      * @param resource|string $stub
diff --git a/ext/phar/phar_object_arginfo.h b/ext/phar/phar_object_arginfo.h
index bca9f0112ea..f2ebf5ba6ea 100644
Binary files a/ext/phar/phar_object_arginfo.h and b/ext/phar/phar_object_arginfo.h differ
diff --git a/ext/phar/tests/setsignaturealgo_sensitive_parameter.phpt b/ext/phar/tests/setsignaturealgo_sensitive_parameter.phpt
new file mode 100644
index 00000000000..71c58c08c79
--- /dev/null
+++ b/ext/phar/tests/setsignaturealgo_sensitive_parameter.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Test that the private key parameter of Phar::setSignatureAlgorithm() is marked sensitive.
+--EXTENSIONS--
+phar
+--INI--
+phar.require_hash=0
+phar.readonly=0
+--FILE--
+<?php
+$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.tar';
+$p = new PharData($fname);
+$p['file1.txt'] = 'hi';
+
+try {
+    $p->setSignatureAlgorithm(-1, 'secret-private-key');
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+?>
+--CLEAN--
+<?php
+unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.tar');
+?>
+--EXPECTF--
+UnexpectedValueException: Unknown signature algorithm specified in %s:%d
+Stack trace:
+#0 %s(%d): PharData->setSignatureAlgorithm(-1, Object(SensitiveParameterValue))
+#1 {main}
diff --git a/ext/snmp/snmp.stub.php b/ext/snmp/snmp.stub.php
index f68da7e75f1..48199f2a662 100644
--- a/ext/snmp/snmp.stub.php
+++ b/ext/snmp/snmp.stub.php
@@ -115,18 +115,18 @@
      */
     const SNMP_COUNTER64 = UNKNOWN;

-    function snmpget(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}
+    function snmpget(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}

-    function snmpgetnext(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}
+    function snmpgetnext(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}

-    function snmpwalk(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}
+    function snmpwalk(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}

-    function snmprealwalk(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}
+    function snmprealwalk(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}

     /** @alias snmprealwalk */
-    function snmpwalkoid(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}
+    function snmpwalkoid(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}

-    function snmpset(string $hostname, string $community, array|string $object_id, array|string $type, array|string $value, int $timeout = -1, int $retries = -1): bool {}
+    function snmpset(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, array|string $type, array|string $value, int $timeout = -1, int $retries = -1): bool {}

     function snmp_get_quick_print(): bool {}

@@ -145,44 +145,44 @@ function snmp_set_string_output_format(Snmp\StringOutput $format): void {}
     /** @alias snmp_set_oid_output_format */
     function snmp_set_oid_numeric_print(Snmp\OidOutput|int $format): true {}

-    function snmp2_get(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}
+    function snmp2_get(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}

-    function snmp2_getnext(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}
+    function snmp2_getnext(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}

-    function snmp2_walk(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}
+    function snmp2_walk(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}

-    function snmp2_real_walk(string $hostname, string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}
+    function snmp2_real_walk(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}

-    function snmp2_set(string $hostname, string $community, array|string $object_id, array|string $type, array|string $value, int $timeout = -1, int $retries = -1): bool {}
+    function snmp2_set(string $hostname, #[\SensitiveParameter] string $community, array|string $object_id, array|string $type, array|string $value, int $timeout = -1, int $retries = -1): bool {}

     function snmp3_get(
         string $hostname, string $security_name, string $security_level,
-        string $auth_protocol, string $auth_passphrase,
-        string $privacy_protocol, string $privacy_passphrase,
+        string $auth_protocol, #[\SensitiveParameter] string $auth_passphrase,
+        string $privacy_protocol, #[\SensitiveParameter] string $privacy_passphrase,
         array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}

     function snmp3_getnext(
         string $hostname, string $security_name, string $security_level,
-        string $auth_protocol, string $auth_passphrase,
-        string $privacy_protocol, string $privacy_passphrase,
+        string $auth_protocol, #[\SensitiveParameter] string $auth_passphrase,
+        string $privacy_protocol, #[\SensitiveParameter] string $privacy_passphrase,
         array|string $object_id, int $timeout = -1, int $retries = -1): mixed {}

     function snmp3_walk(
         string $hostname, string $security_name, string $security_level,
-        string $auth_protocol, string $auth_passphrase,
-        string $privacy_protocol, string $privacy_passphrase,
+        string $auth_protocol, #[\SensitiveParameter] string $auth_passphrase,
+        string $privacy_protocol, #[\SensitiveParameter] string $privacy_passphrase,
         array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}

     function snmp3_real_walk(
         string $hostname, string $security_name, string $security_level,
-        string $auth_protocol, string $auth_passphrase,
-        string $privacy_protocol, string $privacy_passphrase,
+        string $auth_protocol, #[\SensitiveParameter] string $auth_passphrase,
+        string $privacy_protocol, #[\SensitiveParameter] string $privacy_passphrase,
         array|string $object_id, int $timeout = -1, int $retries = -1): array|false {}

     function snmp3_set(
         string $hostname, string $security_name, string $security_level,
-        string $auth_protocol, string $auth_passphrase,
-        string $privacy_protocol, string $privacy_passphrase,
+        string $auth_protocol, #[\SensitiveParameter] string $auth_passphrase,
+        string $privacy_protocol, #[\SensitiveParameter] string $privacy_passphrase,
         array|string $object_id, array|string $type, array|string $value,
         int $timeout = -1, int $retries = -1): bool {}

@@ -239,15 +239,15 @@ class SNMP
         public int $oid_output_format;
         public int $exceptions_enabled;

-        public function __construct(int $version, string $hostname, string $community, int $timeout = -1, int $retries = -1) {}
+        public function __construct(int $version, string $hostname, #[\SensitiveParameter] string $community, int $timeout = -1, int $retries = -1) {}

         /** @tentative-return-type */
         public function close(): bool {}

         /** @tentative-return-type */
         public function setSecurity(
-            string $securityLevel, string $authProtocol = "", string $authPassphrase = "",
-            string $privacyProtocol = "", string $privacyPassphrase = "",
+            string $securityLevel, string $authProtocol = "", #[\SensitiveParameter] string $authPassphrase = "",
+            string $privacyProtocol = "", #[\SensitiveParameter] string $privacyPassphrase = "",
             string $contextName = "", string $contextEngineId = ""): bool {}

 	    /** @tentative-return-type */
diff --git a/ext/snmp/snmp_arginfo.h b/ext/snmp/snmp_arginfo.h
index 1e134c62764..5e8af709928 100644
Binary files a/ext/snmp/snmp_arginfo.h and b/ext/snmp/snmp_arginfo.h differ
diff --git a/ext/snmp/snmp_decl.h b/ext/snmp/snmp_decl.h
index 78fad4608a2..f0b7f49d5a3 100644
Binary files a/ext/snmp/snmp_decl.h and b/ext/snmp/snmp_decl.h differ
diff --git a/ext/snmp/tests/sensitive_parameter.phpt b/ext/snmp/tests/sensitive_parameter.phpt
new file mode 100644
index 00000000000..a17457743d0
--- /dev/null
+++ b/ext/snmp/tests/sensitive_parameter.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test that community strings and SNMPv3 passphrases are marked sensitive.
+--EXTENSIONS--
+snmp
+--FILE--
+<?php
+try {
+    var_dump(snmpget('127.0.0.1', 'public', new stdClass));
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+try {
+    var_dump(snmp3_get('127.0.0.1', 'user', 'authPriv', 'MD5', 'auth-pass', 'DES', 'priv-pass', new stdClass));
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+try {
+    new SNMP(SNMP::VERSION_1, '127.0.0.1', 'public', 'not-an-int');
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+?>
+--EXPECTF--
+TypeError: snmpget(): Argument #3 ($object_id) must be of type array|string, stdClass given in %s:%d
+Stack trace:
+#0 %s(%d): snmpget('127.0.0.1', Object(SensitiveParameterValue), Object(stdClass))
+#1 {main}
+TypeError: snmp3_get(): Argument #8 ($object_id) must be of type array|string, stdClass given in %s:%d
+Stack trace:
+#0 %s(%d): snmp3_get('127.0.0.1', 'user', 'authPriv', 'MD5', Object(SensitiveParameterValue), 'DES', Object(SensitiveParameterValue), Object(stdClass))
+#1 {main}
+TypeError: SNMP::__construct(): Argument #4 ($timeout) must be of type int, string given in %s:%d
+Stack trace:
+#0 %s(%d): SNMP->__construct(0, '127.0.0.1', Object(SensitiveParameterValue), 'not-an-int')
+#1 {main}
diff --git a/ext/sodium/libsodium.stub.php b/ext/sodium/libsodium.stub.php
index 80d8de2c0b2..f1fefefe65e 100644
--- a/ext/sodium/libsodium.stub.php
+++ b/ext/sodium/libsodium.stub.php
@@ -696,12 +696,12 @@ function sodium_crypto_pwhash_scryptsalsa208sha256_str(#[\SensitiveParameter] st
 function sodium_crypto_pwhash_scryptsalsa208sha256_str_verify(string $hash, #[\SensitiveParameter] string $password): bool {}
 #endif

-function sodium_crypto_scalarmult(string $n, string $p): string {}
+function sodium_crypto_scalarmult(#[\SensitiveParameter] string $n, string $p): string {}

 #ifdef crypto_core_ristretto255_HASHBYTES
-function sodium_crypto_scalarmult_ristretto255(string $n, string $p): string {}
+function sodium_crypto_scalarmult_ristretto255(#[\SensitiveParameter] string $n, string $p): string {}

-function sodium_crypto_scalarmult_ristretto255_base(string $n): string {}
+function sodium_crypto_scalarmult_ristretto255_base(#[\SensitiveParameter] string $n): string {}
 #endif

 function sodium_crypto_secretbox(#[\SensitiveParameter] string $message, string $nonce, #[\SensitiveParameter] string $key): string {}
diff --git a/ext/sodium/libsodium_arginfo.h b/ext/sodium/libsodium_arginfo.h
index fbeb30fdd63..d13759fd75f 100644
Binary files a/ext/sodium/libsodium_arginfo.h and b/ext/sodium/libsodium_arginfo.h differ
diff --git a/ext/sodium/tests/sensitive_parameter.phpt b/ext/sodium/tests/sensitive_parameter.phpt
new file mode 100644
index 00000000000..95b1af2c153
--- /dev/null
+++ b/ext/sodium/tests/sensitive_parameter.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Test that the secret scalar parameter of sodium_crypto_scalarmult*() is marked sensitive.
+--EXTENSIONS--
+sodium
+--FILE--
+<?php
+foreach ([
+    'sodium_crypto_scalarmult',
+    'sodium_crypto_scalarmult_base',
+    'sodium_crypto_scalarmult_ristretto255',
+    'sodium_crypto_scalarmult_ristretto255_base',
+] as $function) {
+    $parameter = (new ReflectionFunction($function))->getParameters()[0];
+    echo $function, '($', $parameter->getName(), '): ';
+    var_dump(array_map(fn (ReflectionAttribute $a) => $a->getName(), $parameter->getAttributes()));
+}
+?>
+--EXPECT--
+sodium_crypto_scalarmult($n): array(1) {
+  [0]=>
+  string(18) "SensitiveParameter"
+}
+sodium_crypto_scalarmult_base($secret_key): array(1) {
+  [0]=>
+  string(18) "SensitiveParameter"
+}
+sodium_crypto_scalarmult_ristretto255($n): array(1) {
+  [0]=>
+  string(18) "SensitiveParameter"
+}
+sodium_crypto_scalarmult_ristretto255_base($n): array(1) {
+  [0]=>
+  string(18) "SensitiveParameter"
+}
diff --git a/ext/sqlite3/sqlite3.stub.php b/ext/sqlite3/sqlite3.stub.php
index 1a51f9dc3d8..c30b6e08c0d 100644
--- a/ext/sqlite3/sqlite3.stub.php
+++ b/ext/sqlite3/sqlite3.stub.php
@@ -162,13 +162,13 @@ class SQLite3
 #endif

     /** @implementation-alias SQLite3::open */
-    public function __construct(string $filename, int $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, string $encryptionKey = "") {}
+    public function __construct(string $filename, int $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, #[\SensitiveParameter] string $encryptionKey = "") {}

     /**
      * @tentative-return-type
      * @todo SQLite3::open should really be static
      */
-    public function open(string $filename, int $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, string $encryptionKey = ""): void {}
+    public function open(string $filename, int $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, #[\SensitiveParameter] string $encryptionKey = ""): void {}

     /** @tentative-return-type */
     public function close(): bool {}
diff --git a/ext/sqlite3/sqlite3_arginfo.h b/ext/sqlite3/sqlite3_arginfo.h
index 3917ef63e28..f3f8160e5e2 100644
Binary files a/ext/sqlite3/sqlite3_arginfo.h and b/ext/sqlite3/sqlite3_arginfo.h differ
diff --git a/ext/sqlite3/tests/sensitive_parameter.phpt b/ext/sqlite3/tests/sensitive_parameter.phpt
new file mode 100644
index 00000000000..24807013f63
--- /dev/null
+++ b/ext/sqlite3/tests/sensitive_parameter.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Test that the encryption key parameter of SQLite3::__construct() and SQLite3::open() is marked sensitive.
+--EXTENSIONS--
+sqlite3
+--FILE--
+<?php
+try {
+    new SQLite3(':memory:', 'not-an-int', 'secret-key');
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+try {
+    $db = (new ReflectionClass(SQLite3::class))->newInstanceWithoutConstructor();
+    $db->open(':memory:', 'not-an-int', 'secret-key');
+} catch (\Throwable $e) {
+    echo $e, PHP_EOL;
+}
+?>
+--EXPECTF--
+TypeError: SQLite3::__construct(): Argument #2 ($flags) must be of type int, string given in %s:%d
+Stack trace:
+#0 %s(%d): SQLite3->__construct(':memory:', 'not-an-int', Object(SensitiveParameterValue))
+#1 {main}
+TypeError: SQLite3::open(): Argument #2 ($flags) must be of type int, string given in %s:%d
+Stack trace:
+#0 %s(%d): SQLite3->open(':memory:', 'not-an-int', Object(SensitiveParameterValue))
+#1 {main}