Files
aurora-wordpress-admin/app/vite.config.js
T
nixpanelandClaude Sonnet 5 15d6060ae3 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]>
2026-07-05 06:59:24 -05:00

48 lines
2.3 KiB
JavaScript

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { fileURLToPath, URL } from 'node:url';
// Deliberately simple: one entry in this first slice (dashboard). Add more
// entries here as additional pages get rebuilt — same flat pattern each
// time, no shared scoping/theme-remap layer to keep in sync across files.
export default defineConfig({
root: fileURLToPath(new URL('./src', import.meta.url)),
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
build: {
outDir: fileURLToPath(new URL('./dist', import.meta.url)),
emptyOutDir: true,
sourcemap: true,
manifest: true,
rollupOptions: {
input: {
shell: fileURLToPath(new URL('./src/entries/shell.js', import.meta.url)),
dashboard: fileURLToPath(new URL('./src/entries/dashboard.js', import.meta.url)),
settings: fileURLToPath(new URL('./src/entries/settings.js', import.meta.url)),
'menu-creator': fileURLToPath(new URL('./src/entries/menu-creator.js', import.meta.url)),
'admin-notices': fileURLToPath(new URL('./src/entries/admin-notices.js', import.meta.url)),
'role-editor': fileURLToPath(new URL('./src/entries/role-editor.js', import.meta.url)),
'activity-log': fileURLToPath(new URL('./src/entries/activity-log.js', import.meta.url)),
'database-explorer': fileURLToPath(new URL('./src/entries/database-explorer.js', import.meta.url)),
plugins: fileURLToPath(new URL('./src/entries/plugins.js', import.meta.url)),
comments: fileURLToPath(new URL('./src/entries/comments.js', import.meta.url)),
posts: fileURLToPath(new URL('./src/entries/posts.js', import.meta.url)),
pages: fileURLToPath(new URL('./src/entries/pages.js', import.meta.url)),
media: fileURLToPath(new URL('./src/entries/media.js', import.meta.url)),
users: fileURLToPath(new URL('./src/entries/users.js', import.meta.url)),
'file-manager': fileURLToPath(new URL('./src/entries/file-manager.js', import.meta.url)),
'locked-feature': fileURLToPath(new URL('./src/entries/locked-feature.js', import.meta.url)),
},
output: {
entryFileNames: '[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash][extname]',
},
},
},
});