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]>
- New Integrity class checksums the files that implement/enforce the
free/paid split (SubApps, Analytics, AnalyticsData, WhiteLabel, Shell,
License itself, and the two bootstrap files) against a manifest
generated at zip-build time. No manifest present (a dev checkout) is
never treated as tampered.
- License::is_feature_available() checks Integrity::is_tampered() before
checking the actual license, so a distributed copy with the check
stripped/commented out re-locks every paid feature even with an
otherwise-valid license, rather than the plugin trusting whatever code
happens to still be there.
- bin/build-integrity-manifest.php generates the manifest against a
staged zip-build copy (gitignored build artifact, same pattern as
app/dist) — now a required step in the zip-build workflow.
- Verified live: untampered+unlicensed stays locked, untampered+licensed
unlocks, one tampered file re-locks regardless of license status
(tested via a temporary mu-plugin faking an active license, kept
outside any protected file so it didn't itself trip the check).
This raises the bar against the realistic low-effort case (finding and
deleting/inverting the one license-check line) but can't stop someone who
rewrites a file's logic to never call the gate, or who edits this
integrity check itself — an inherent limit of protection living in plain,
editable PHP.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- License.php now calls the aurora-licensing plugin's REST API
(/wp-json/aurora-licensing/v1/validate|activate|deactivate) instead of
treating any non-empty license_key as active. Result is cached in the
aurora_admin_license_status option for 12 hours; is_active() and
is_feature_available() read the cache instead of hitting the network on
every admin page load.
- Hooks update_option_aurora_admin_settings to activate/deactivate
immediately when the license_key setting actually changes, rather than
waiting out the cache. Instantiated in AuroraAdmin.php so that hook
registers.
- Settings page now surfaces the real status (active/invalid/unreachable,
with the server's own message) as a status line under the License key
field, instead of a field that silently does nothing.
- Verified live against the real server: a fake test key correctly came
back locked with the server's own "License key not found." message.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Analytics, White Label, remote site switcher are paid)
- New License class is the single gate every paid feature checks —
today it's a placeholder (any non-empty license_key counts as active)
since there's no license server yet, but every gated feature already
funnels through it so swapping in real remote validation later won't
touch anything else.
- Role Editor, Activity Log, and Database Explorer stay registered/
capability-checked as before, but render an UpgradeNotice screen
instead of the real feature when unlicensed (and skip loading the
real JS bundle/REST data). Menu Creator, Admin Notices, Comments,
and Plugins stay free.
- Analytics' tracking hooks and REST endpoint, and White Label's
favicon/rename/text-replace hooks, simply don't register without a
license. Settings fields for both stay visible/editable either way.
- Remote site switcher gated alongside its existing capability check.
- Sidebar shows a small lock badge on locked Aurora nav items; the
Dashboard's Analytics tab shows the same upgrade prompt when locked.
- Fixed an unrelated bug surfaced while testing Role Editor: RolesData's
REST callback called get_editable_roles() without loading
wp-admin/includes/user.php first, which REST requests don't get for
free (only real wp-admin page loads do) — 500'd every time.
Co-Authored-By: Claude Sonnet 5 <[email protected]>