Reference Omeka S module

Security Model

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 starts in one of 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. Searching only considers .phtml files.

Symbolic links inside these directories are followed for reading only, and only when their target resolves inside the theme's or module's own directory (the parent of view/, or application/ for the application) or inside the Omeka installation. This lets the editor show the templates of themes that link to a shared copy, such as several themes developed against one shared engine. Links pointing anywhere else are not listed, not searched and cannot be opened. Search keeps track of the directories it has entered, so a link loop or two links to the same directory are searched once.

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:

  1. Backslashes are converted to slashes.
  2. Absolute paths (/... or a Windows drive letter) are rejected. This check runs before any trimming so a leading slash is never silently stripped.
  3. Trailing slashes are removed.
  4. Any . or .. path segment is rejected.
  5. The path is joined to the base directory and resolved with realpath(). If the file does not exist the request fails.
  6. 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. For reads only, a path that leaves the base directory is still accepted when it resolves inside the theme's or module's own directory or the Omeka installation (see above); writes never accept it.

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, so a linked template that points elsewhere is read-only. 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 and resolve inside themes/<theme>/view/, as for a save.
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. The theme name must match ^[A-Za-z0-9_-]+$. An existing theme file at that path is overwritten, but only when it resolves inside the theme's view/ directory: a link to a shared template is refused. Missing parent directories are created with mode 0755, and the nearest existing directory on the path must resolve inside the theme's view/ directory, so nothing is created through a linked directory.
Copy Theme A new directory themes/<name>/, written as a temporary themes/.<name>.copying-<random>/ and renamed when complete (removed if the copy fails) Both names must match ^[A-Za-z0-9_-]+$; the target must not exist. A symbolic link in the source is replaced by a copy of its target when that resolves inside the source theme or the Omeka installation; other links, broken links and links to a directory containing them are left out and reported. The copy is limited to 20,000 files and 256 MB, measured before anything is written, and to 32 levels of directories. 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 the csrf form field or the X-CSRF-Token header. 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 a SecurityException and 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 with php -l (using the CLI binary even when PHP runs under FPM). A failure blocks the save unless the user explicitly overrides it. Without exec(), 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 FileEditor service counts saves per user in a rolling 60-second window and rejects more than Maximum saves per user per minute with a SecurityException (HTTP 403 from the API). The window start and count are persisted per user in Omeka's user_setting table under theme_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.ini or config/, 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.
Log in for Support