Skip to main content

Web Applications

Every WordPress, Drupal and other application detected in the accounts' home directories, in one list, with the version each runs and whether a newer release exists. It's Web Applications in the System menu, and webapp:list on the command line. The screen answers one question during an incident: "which installations on this server are below version X?"

The account holder's own view of the same inventory, with the buttons to update, install and sign in, is the account docs' Applications and updates. The admin app's view is read-only: to act on an installation, sign in as its account (Act in an account).

What the server does on its own​

WhenWhatBy hand
Nightly, 03:41Scans every account's home directory for installations, and reads each WordPress site's plugin and theme inventorywebapp:scan --all --queue
Every 6 hoursRefreshes the upstream release feedswebapp:releases --refresh
Nightly, 04:23Applies updates to the installations whose owners turned on automatic updateswebapp:auto-upgrade
Nightly, 05:17Polls the advisory feeds, audits every installation that can be audited, records what changed, and notifieswebapp:advisory-sweep
Nightly, 06:07Updates the plugins and themes that open advisories are against, on installations that opted inwebapp:auto-remediate

The order carries the logic: the scan records what is installed, the feeds say what upstream ships, the advisory sweep decides what is vulnerable, and only then does remediation act. Every one of them has a command, so what the scheduler does at night can be run and watched by hand.

The sweeps are dry runs by default

webapp:auto-upgrade, webapp:advisory-sweep, webapp:auto-remediate, webapp:upgrade and webapp:remediate print what they would do and change nothing until you pass --no-dry-run. The dry run uses the same selection code as the real run, so what it prints is what would happen.

What is detected​

The scan recognizes WordPress, Drupal, Joomla, Laravel, Symfony, Magento, Moodle, PrestaShop, MediaWiki, phpBB, Nextcloud, Ghost, Matomo, Node.js applications and PHP applications managed by Composer, each by its marker file. An installation no website serves (a backup copy, an old extract, an unpacked tarball) is listed too, marked not served: it's real code, and reachable if a document root ever points at it.

The scan is nightly, so an installation an account holder uploaded today appears tomorrow, unless you scan the account now:

elpanctl webapp:scan <account>

States​

StateMeaning
DetectedPresent and readable.
In installationThe panel is installing it and no scan has seen the files yet.
BrokenThe marker file is there, but the version could not be read: a truncated upload, a permission problem. Kept, because "something is here we cannot read" is worth showing.
MissingA scan did not find it. The page says how many consecutive scans missed it; after three, the row is deleted. One bad scan can't drop the inventory.

The list​

The filters narrow the list before anything is read:

  • Application: one type, from the types this server can resolve.
  • Version below: a dotted release number, e.g. 6.9.6. Together with Application, this is the answer to an advisory: every WordPress below the fixed version.
  • Behind the latest release: everything below the newest release the feed knows for the selected application. It needs an application selected and a recent feed.
  • State, Served, Advisories (With open advisories) and a free-text Search over title, path, host and version.

A filter that can't be applied is said in a warning, never dropped silently.

ColumnWhat it holds
AccountThe account, linked.
ApplicationThe site's title where the scan could read one, and the type.
VersionWhat the files report.
UpdateUp to date, Update available, New major version or Unknown.
AdvisoriesThe count the nightly sweep stored, and the worst severity.
StateThe states above.
ServedThe hostname that serves it, or not served.
PathUnder the account's home directory.
Last SeenWhen a scan last observed it.
Unknown is not up to date

The update status needs a release feed, and the screen names the types that have one; today those are WordPress and Drupal. Every other application is listed with its version and an unknown status. A feed that hasn't refreshed for a week also goes unknown, on purpose: the panel doesn't quote stale numbers as fact.

The Advisories column is a different question from the update status: a site at the newest release can still carry an advisory. There is no "clean only" filter, because a zero count also covers "never audited" and "the last audit failed".

An installation's page​

Open a row for everything the last scan read, in one shape whatever the type: the installed version, the last action and the last upgrade with their outcome, the update status, whether the owner turned on Automatic Updates, each release on offer (a Security release is marked), the newest release and the PHP it needs, when the feed was last refreshed, the path, the website that serves it, and the database, app server and mount it uses.

Two readings cost more than a row. The file permission audit, what the web server may write to against what the application asks for, is one privileged call and sits on the page. The dependency audit (composer audit against the installation's lockfile, plus the open advisories against its core) runs a process and a network call, so it's a page of its own behind Dependencies, present only where there is something to audit.

Nothing on these pages changes a site. The opt-in to automatic updates, the permission fix, a plugin update: each is the account holder's decision, made in their panel. Mass upgrades are yours and have their own page: Upgrade Runs.

Advisories​

The nightly sweep matches three sources: composer audit for the dependencies of a resolved Composer project, OSV for an application's own core, and Wordfence Intelligence for WordPress core and every plugin and theme on every site, each matched on its own version.

A real run sends two mails: a digest to the server's root mailbox, grouped by advisory (System mail), and a notice to each affected account's system mailbox about its own sites. A finding is mailed once, not once per poll.

The first run on a populated server

Every existing finding is "new" the first time. Record the baseline without mailing anyone, and let the following night report what changed:

elpanctl webapp:advisory-sweep --no-dry-run --no-notify

A failed reading resolves nothing: an installation that could not be audited keeps every finding it had, and a stale feed contributes nothing rather than what it last said.

elpanctl webapp:advisory-sources # each feed, when it was last polled, how old its payload is
elpanctl webapp:advisory-sources --component=wp:plugin/akismet
elpanctl webapp:audit --account=<account> # a live reading, nothing stored
elpanctl webapp:audit --type=drupal --findings-only

webapp:audit costs a Composer process and a network round trip per installation, so narrow it with --account or --type. It exits 1 when anything was found.

File permissions​

The web server runs a site as the account's -www user and can write nowhere unless the application declares an exception: WordPress names wp-content/uploads, Laravel names storage/.

elpanctl webapp:permissions --type=wordpress --divergent-only
elpanctl webapp:permissions --account=<account> --apply

--apply applies the whole declaration, so a directory the account holder opened by hand goes back to what the application asks for. Every successful upgrade re-applies the declaration too. An absent directory is normal and is skipped, not created.

When the status is wrong everywhere​

Unknown on every row: the release feed hasn't refreshed. webapp:releases shows each feed's freshness; --refresh fetches what is due, --force fetches even a feed refreshed minutes ago. On a new server, server:init fills the catalog and says which feeds it could not fetch and why; the six-hourly refresh tries again regardless.