Summary
Theme File Editor is a tool for trusted administrators. Editing a .phtml template means writing PHP that the server executes, so anyone who can save a file through this module can run arbitrary code as the web server user. The module's security model is about limiting who can do that and which files they can touch, not about sandboxing the edits.
Who has access
Two ACL resources are registered at bootstrap, ThemeFileEditor\Controller\Admin\EditorController and ThemeFileEditor\Controller\Admin\ApiController, and the global_admin and site_admin roles are allowed all privileges on both. No other role is granted anything.
Every controller action, page and API alike, calls userIsAllowed() on its controller before touching the filesystem or database. Pages throw Omeka's PermissionDeniedException; API actions return HTTP 403 with a JSON error. The sidebar links carry the same resource and privilege, so users without access do not see them.
Access is not scoped per site: a site_admin of one site can edit every theme, including themes used by sites they do not administer.
Where files can be read from
All browsing, reading and searching is confined to three kinds of directory:
| Source | Directory |
|---|---|
theme |
themes/<theme>/view/ |
module |
modules/<Module>/view/ |
app |
application/view/ |
Theme and module directory names must match ^[A-Za-z0-9_-]+$; anything else is rejected before a path is built. Directory listings skip entries whose name starts with a dot and skip symbolic links. Searching only considers .phtml files.
Reads are further limited to .phtml files: the file tree marks only .phtml files as editable, the editor page and the /api/file endpoint refuse to return other files, and saving anything but a .phtml path is rejected (extension check is case-insensitive). The extension is checked on the requested path before anything is read from disk, and again on the resolved path so that a symbolic link named *.phtml cannot expose another file type. Theme files larger than the configured maximum size are refused.
Path validation
Every relative path supplied by the client goes through the same checks:
- Backslashes are converted to slashes.
- Absolute paths (
/...or a Windows drive letter) are rejected. This check runs before any trimming so a leading slash is never silently stripped. - Trailing slashes are removed.
- Any
.or..path segment is rejected. - The path is joined to the base directory and resolved with
realpath(). If the file does not exist the request fails. - Both the candidate and the base directory are resolved again with
realpath()inside the root check, and the resolved path must start with the resolved base directory followed by a slash. This catches symbolic links that point outside the allowed directory regardless of how the caller built the path.
Violations raise a SecurityException, which the API reports as HTTP 403 and the pages show as an error banner.
Where files can be written
| Operation | What is written | Constraints |
|---|---|---|
| Save | An existing .phtml file in themes/<theme>/view/ |
File must already exist and resolve inside the theme's view/ directory. New files cannot be created by saving. Content is size-limited and, by default, syntax-checked. |
| Rollback | The file recorded in the revision | The recorded path must still exist inside themes/<theme>/view/. |
| Copy to Theme | A new or replaced .phtml file in themes/<theme>/view/ at the source file's relative path |
Source must be a .phtml file inside a module or application view/ directory. Missing parent directories are created with mode 0755. An existing theme file at that path is overwritten. |
| Copy Theme | A new directory themes/<name>/ |
Both names must match ^[A-Za-z0-9_-]+$; the target must not exist. Symbolic links in the source are skipped. The copy also rewrites name in the new theme's theme.ini. |
| Disk backup | <file>.<timestamp>.bak next to the saved file |
Only when requested for a save. |
Module and application view files are never written. The module never changes file ownership or permissions; if a target is not writable the operation fails with a message that includes the path.
Other safeguards
- CSRF token: every POST to the JSON API (
save,rollback,copy-to-theme,copy-theme,settings) must carry a CSRF token, sent as thecsrfform field or theX-CSRF-Tokenheader. The token is rendered into the editor and Revision History pages and is bound to the admin session, with the same 12-hour lifetime as Omeka's own form tokens. A missing or invalid token is aSecurityExceptionand returns HTTP 403. The Settings page form uses Omeka's standard form CSRF element. - Syntax check: with
exec()available, the new content is written to a temporary file and checked withphp -l(using the CLI binary even when PHP runs under FPM). A failure blocks the save unless the user explicitly overrides it. Withoutexec(), a heuristic tag and brace count is used. - Size limit: both the file on disk (for opening) and the submitted content (for saving) are limited by Maximum editable file size (KB).
- Rate limit: the
FileEditorservice counts saves per user in a rolling 60-second window and rejects more than Maximum saves per user per minute with aSecurityException(HTTP 403 from the API). The window start and count are persisted per user in Omeka'suser_settingtable undertheme_file_editor_rate_limit, so the limit holds across requests and PHP-FPM workers. Saves without an authenticated user ID are not rate limited (all endpoints require an authenticated admin, so this only matters for direct service calls). If the user setting cannot be read or written, the failure is logged and the save proceeds. - Audit trail: every save and rollback is written to the PHP error log (theme, path, size, user, revision ID, never the content), fires an event, and is logged by the AuditTrail module when installed. Every revision records the user ID.
- Revision history: because every write is recorded first, any change made through the module can be reverted from the Revision History page.
What the module does not do
- The JSON API has no key-based authentication: it relies on the Omeka admin session cookie, the ACL check and, for POST requests, the CSRF token described above.
- No content filtering is applied to what is saved. A template can contain any PHP.
- Files outside
view/directories, such as theme CSS, JavaScript,theme.iniorconfig/, are not visible and cannot be edited (Copy Theme copies them but does not expose them). - Backups written to disk are not cleaned up, and revisions are only pruned per file when that file is saved.