Skip to content

Commit 8246054

Browse files
committed
fix(envlite): treat fetched salts as literal in Phase 7 render
api.wordpress.org/secret-key/1.1/salt/ returns random bytes that can contain \`\$\` and \`\\\`. Passing those bytes as preg_replace's replacement argument means sequences like \`\$1\`, \`\\1\`, or \`\$&\` get interpreted as backreferences and silently corrupt the salts that land in src/wp-config.php. Switch to preg_replace_callback so the salts block is inserted verbatim regardless of what characters it contains. Pinned by a regression test that feeds backreference-shaped bytes through the render path and asserts they survive verbatim.
1 parent 2e77230 commit 8246054

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

tools/local-env/envlite.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,13 +662,22 @@ function envlite_phase7_render(string $sample, int $port, ?string $saltsBlock):
662662
$cfg = strtr($sample, $dbReplacements);
663663

664664
// 2. Salt block: AUTH_KEY through NONCE_SALT, 8 contiguous define()s.
665+
// The salts API returns random bytes that can include `$` and `\`; using
666+
// them as preg_replace's replacement argument would let sequences like
667+
// `$1` or `\1` be interpreted as backreferences and silently corrupt the
668+
// saved salts. Use a callback so the block is inserted as a literal.
665669
if ($saltsBlock !== null) {
666670
$pattern = '/define\(\s*\'AUTH_KEY\'.*?define\(\s*\'NONCE_SALT\'\s*,\s*\'[^\']*\'\s*\);/s';
667671
$count = preg_match_all($pattern, $cfg, $m);
668672
if ($count !== 1) {
669673
throw new \RuntimeException("phase 7: expected exactly one salt block, found $count");
670674
}
671-
$cfg = preg_replace($pattern, $saltsBlock, $cfg, 1);
675+
$cfg = preg_replace_callback(
676+
$pattern,
677+
static function () use ($saltsBlock) { return $saltsBlock; },
678+
$cfg,
679+
1
680+
);
672681
}
673682

674683
// 3. Inject WP_HOME / WP_SITEURL before the marker.

tools/local-env/tests/test_phase7.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,24 @@ function test_phase7_render_keeps_sample_salts_when_null_provided() {
4242
// 8 sample salt lines remain unchanged
4343
envlite_assert_eq(8, substr_count($out, "'put your unique phrase here'"));
4444
}
45+
46+
function test_phase7_render_treats_salts_as_literal_not_backreferences() {
47+
// The salts API returns random bytes; some include `$` and `\` which
48+
// preg_replace's replacement argument would interpret as backreferences.
49+
// The render path must insert the salts as a literal block.
50+
$sample = file_get_contents(dirname(__DIR__, 3) . '/wp-config-sample.php');
51+
$literalBlock =
52+
"define( 'AUTH_KEY', 'a\$1b\\\\1c\$&d' );\n"
53+
. "define( 'SECURE_AUTH_KEY', 'X' );\n"
54+
. "define( 'LOGGED_IN_KEY', 'X' );\n"
55+
. "define( 'NONCE_KEY', 'X' );\n"
56+
. "define( 'AUTH_SALT', 'X' );\n"
57+
. "define( 'SECURE_AUTH_SALT', 'X' );\n"
58+
. "define( 'LOGGED_IN_SALT', 'X' );\n"
59+
. "define( 'NONCE_SALT', 'X' );";
60+
$out = envlite_phase7_render($sample, 8421, $literalBlock);
61+
envlite_assert(
62+
strpos($out, "define( 'AUTH_KEY', 'a\$1b\\\\1c\$&d' );") !== false,
63+
'metacharacters in salts must survive verbatim'
64+
);
65+
}

0 commit comments

Comments
 (0)