Add File Manager sub-app (Pro feature) (v2.7.0)
New browse/edit interface for the WordPress install's own files, built using the installed "WP File Manager" plugin (elFinder-based) as a functional reference rather than embedding it. Own REST controller (FileManagerData.php) + Vue page, same pattern as every other sub-app. Security is the whole point here (highest-risk surface in the plugin — arbitrary filesystem read/write): - Root is ABSPATH, manage_options-gated throughout. - Every client path is resolved via realpath() and checked for root containment against the *resolved* path, not string-matched against the raw input — catches symlink tricks and encoded traversal that a naive "../" check would miss. - Uploads/new files/renames reject a blocklist of server-executable extensions (.php, .cgi, .sh, etc.) — the single most important control, since uploading a webshell is the classic exploit path for this class of feature. - Deliberately not built: zip/unzip (zip-slip vulnerability class), chmod/permissions editing, cross-directory move/copy. Gated as a paid feature (riskier than Database Explorer, which is already paid) — a judgment call flagged to the user, not something they specified. Also fixed a real bug found while wiring this up: SubApps.php's page-level lock doesn't stop someone with manage_options from calling the REST endpoints directly, bypassing the license check entirely. File Manager's REST permission callbacks now also check License::is_feature_available(). The same gap exists in Database Explorer, Role Editor, and Activity Log's REST controllers and is flagged as a follow-up task, not fixed here (out of scope for what was asked, affects three unrelated features). Verified live via direct REST calls: path traversal blocked (404), blocked extensions rejected (400), root deletion blocked (400), and a full create/write/read/rename/delete round trip succeeded cleanly. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
204c4365e2
commit
15d6060ae3
+63
-1
@@ -1,6 +1,6 @@
|
||||
# Aurora Admin v2 — Project Status
|
||||
|
||||
_Last updated: 2026-07-05 (latest session — Pages/Media/Users replacement pages, completing the "skinned vs. replaced" conversion started with Posts)_
|
||||
_Last updated: 2026-07-05 (latest session — new File Manager sub-app; found a license-bypass gap affecting several paid REST controllers)_
|
||||
|
||||
## What this project is
|
||||
|
||||
@@ -702,6 +702,68 @@ compatibility, trust), which wasn't in scope here.
|
||||
artifacts (the mu-plugin, the tampered file, the test manifest) were
|
||||
removed from the LocalWP install afterward.
|
||||
|
||||
## File Manager (new sub-app, Pro feature)
|
||||
|
||||
Added this session — the user has the "WP File Manager" plugin (elFinder-
|
||||
based) installed on the test site and asked for equivalent functionality
|
||||
built into Aurora, as a fresh implementation rather than embedding
|
||||
elFinder. `admin/src/FileManager/FileManagerData.php` (REST) +
|
||||
`app/src/pages/file-manager/FileManager.vue` (entry `file-manager.js`),
|
||||
registered as `aurora-admin-file-manager` in `SubApps.php` (parented under
|
||||
`aurora-admin` like Menu Creator/Role Editor, not a native-page
|
||||
replacement — there's no native WP file browser to redirect from).
|
||||
|
||||
- **Root is `ABSPATH`** (the WordPress install directory), same as the
|
||||
reference plugin. Capability is `manage_options` everywhere, no
|
||||
exceptions — by far the highest-risk surface in the plugin (arbitrary
|
||||
filesystem read/write on the site's own PHP).
|
||||
- **Path containment**: every client-supplied path goes through
|
||||
`resolve_path()`, which `realpath()`s the candidate and checks the
|
||||
*resolved* path is still inside the root — never a raw string check
|
||||
against `"../"`, since that alone doesn't catch symlinks or encoded
|
||||
traversal. New files/folders (which don't exist yet, so `realpath()`
|
||||
would return false) resolve their *parent* directory instead, then just
|
||||
sanitize the new name component (`wp_basename()`, reject empty/`.`/`..`/
|
||||
control characters).
|
||||
- Verified live via direct REST calls: `?path=../../../etc` → 404
|
||||
(correctly rejected); creating/renaming to a blocked extension → 400;
|
||||
deleting the root path itself → 400; a full create-folder → create-
|
||||
file → write → read → rename → recursive-delete round trip all
|
||||
succeeded and left no trace afterward.
|
||||
- **Blocked extensions on upload/create/rename**
|
||||
(`FileManagerData::BLOCKED_EXTENSIONS`): `.php` and friends, `.cgi`,
|
||||
`.sh`, `.exe`, etc. — the single most important control, since
|
||||
uploading a webshell through a "file manager" is the classic way this
|
||||
class of feature gets exploited. A separate `BINARY_EXTENSIONS` list
|
||||
(images/archives/media/fonts) plus a 2MB size cap keeps the text editor
|
||||
from trying to open something it shouldn't.
|
||||
- **Deliberately not built** (scope decisions): zip/unzip (extraction has
|
||||
its own well-known vulnerability class — zip-slip, where a crafted
|
||||
archive's entries escape the extraction directory), chmod/permissions
|
||||
editing (too easy to accidentally lock yourself out or open a real
|
||||
hole), cross-directory move/copy (rename within the same directory is
|
||||
supported; a full move/copy tree is meaningfully more surface for a v1).
|
||||
- **Gated as a paid feature** (`License::PAID_FEATURES["file-manager"]`)
|
||||
— this was a judgment call, not something the user specified; reasoned
|
||||
as "even riskier than Database Explorer, which is already paid," and
|
||||
flagged to the user as a default they could override. They didn't
|
||||
object.
|
||||
- **Real bug found and fixed while wiring this up, not specific to File
|
||||
Manager**: `SubApps.php`'s page-level lock (`render()` swaps in the
|
||||
upgrade prompt) only gates the *page* — it doesn't stop someone with
|
||||
`manage_options` from calling the REST endpoints directly and getting
|
||||
full functionality regardless of license status. File Manager's REST
|
||||
permission callbacks were fixed to also check
|
||||
`License::is_feature_available('file-manager')`. **Database Explorer,
|
||||
Role Editor, and Activity Log's REST controllers have the exact same
|
||||
gap and are NOT yet fixed** — flagged as a follow-up task
|
||||
(`task_cb1ac5ca` in this session) rather than fixed inline, since it's
|
||||
a pre-existing issue affecting three other features, out of scope for
|
||||
what was asked. If picking this back up: the fix is exactly what
|
||||
`FileManagerData.php` now does — add the license check to each
|
||||
`permission_callback`, matching the corresponding feature id in
|
||||
`SubApps.php`'s `PAGES` const.
|
||||
|
||||
## Key bugs fixed (root causes, in case they resurface)
|
||||
|
||||
- **Admin Notices invisible on the Dashboard**: `admin_notices` prints
|
||||
|
||||
Reference in New Issue
Block a user