|
| 1 | +<?php |
| 2 | + |
| 3 | +/** |
| 4 | + * Security postflight follow-up: widen the user/accounts avatar carve-out to |
| 5 | + * cover Flex folder-storage user avatars (getgrav/grav#4185). |
| 6 | + * |
| 7 | + * The 2026-06-30_0 update opened user/accounts only for the flatfile avatar |
| 8 | + * layout, user/accounts/avatars/<file> (the account://avatars stream). Flex |
| 9 | + * users on UserFolderStorage store their avatar media under the user object |
| 10 | + * folder instead, user/accounts/<username>/<file>, so those avatars kept |
| 11 | + * returning a 403. |
| 12 | + * |
| 13 | + * Both layouts are exactly two segments under user/accounts (<dir>/<file>), so |
| 14 | + * this widens the root .htaccess RewriteCond from the avatars-only path to any |
| 15 | + * two-segment image path. Account data (.yaml password hashes) is one segment |
| 16 | + * (user/accounts/<username>.yaml) or a non-image file inside the folder, so it |
| 17 | + * stays blocked. SVG stays blocked as a stored-XSS vector. |
| 18 | + * |
| 19 | + * The per-folder user/accounts/.htaccess backup already grants by <FilesMatch> |
| 20 | + * extension regardless of depth, so it needs no change. This is idempotent: |
| 21 | + * once the path is on the [^/]+/[^/]+ form the avatars-only literal is gone. |
| 22 | + */ |
| 23 | + |
| 24 | +return [ |
| 25 | + 'preflight' => null, |
| 26 | + 'postflight' => |
| 27 | + function () { |
| 28 | + $root = GRAV_ROOT . '/.htaccess'; |
| 29 | + if (is_file($root) && is_writable($root)) { |
| 30 | + $contents = file_get_contents($root); |
| 31 | + if ($contents !== false) { |
| 32 | + $patched = str_replace( |
| 33 | + '/user/accounts/avatars/[^/]+\.', |
| 34 | + '/user/accounts/[^/]+/[^/]+\.', |
| 35 | + $contents |
| 36 | + ); |
| 37 | + if ($patched !== $contents) { |
| 38 | + @file_put_contents($root, $patched); |
| 39 | + } |
| 40 | + } |
| 41 | + } |
| 42 | + } |
| 43 | +]; |
0 commit comments