fix: Terminal plugin (removed API) + Macro dialog crash; document both #3

Merged
hermes merged 3 commits from fix/plugin-and-macro-dialog into master 2026-09-29 16:22:39 +10:00
Owner

Summary

Fixes the two faults reported from the running editor, and documents both properly.

1. The Terminal plugin didn't work — a removed API

plugins/Terminal/terminal.php calls Common::execute(...), which no longer exists in Atheos:

plugin calls : Common::execute(...)      ->  MISSING
available    : Common::raw_execute(...)  ->  ["code" => int, "output" => array]

It's a port of Fluidbyte/Codiad-Terminal, so it targets the old API and assumes the old return
shape. Fixed to raw_execute + implode of the output array, and verified against the real
method
: pwd/echo return correctly, and after chdir it reports the blog path — so the terminal
opens in the right directory.

Two further upstream defects are documented but deliberately not fixed, so nobody trusts them:
the block-list is dead code (explode(",", BLOCKED === true) passes a boolean, so ssh/telnet are
never blocked), and strtolower($command === "exit") means exit doesn't clear the auth flag.

2. Settings → Macro returned HTTP 500

components/macro/dialog.php:35 does implode(", ", $macro["fTypes"]) — requires an array — but
class.macro.php::save() writes the string "N/A" for folder macros. So any folder-type
macro makes that dialog fatal. Our writer macros are all folder-scoped, which exposed it.

Fixed by correcting the data ("N/A" → []) rather than patching Atheos's code, so it survives
an Atheos update. Verified: implode() now succeeds for all 8 macros.

Review response

✅ Adopted — and one correction to my own previous commit

Round 1 correctly flagged that I had published a live credential: the Terminal plugin's in-app
gate constant was quoted verbatim in the docs. That was my error. The value has been rotated on
the guest and removed from the docs and the patch file (now a placeholder).

❌ Declined, with measurement — disable_functions cannot be a default

Review asked for php.ini to disable exec/shell_exec/passthru/system by default. Measured:
this would break the editor's core function.

components/codegit/*      -> Common::safe_execute   31 call sites (every git operation)
components/macro/*        -> Common::raw_execute     1 call site
common.php raw_execute    -> function_exists("system") / function_exists("exec")

Every commit, push, fetch and status from the editor runs through exec/system. Disabling them
turns this into a text editor that cannot commit. It stays a deliberate exposure lever (S-7),
applied only if the container is ever exposed — which is why S-7 already says test, do not
blind-apply
.

❌ Declined — deploy key read-only

Writes are the entire point of the writer-branch workflow (macro 2 pushes). Not applicable.

⚠️ Corrected — a false positive

Review reported a syntax error in patches/Terminal-terminal.php.patched. Verified with php83 -l
on the guest: No syntax errors detected. The placeholder is a plain string literal inside a
double-quoted PHP string. Recorded as a non-issue rather than changed to satisfy a false positive.

📝 Recorded as an operator decision — removing the Terminal plugin

This is the reviewers' strongest point and it is correct: a browser shell is the largest attack
surface in the container, and macros already provide command execution. It is documented as the
recommended action, with the mechanism (rename to Terminal.disabled — readDirectory() skips
*.disabled).

Left installed deliberately: it is the operator's plugin, and removing a tool without being asked
is not the agent's call. Flagged for them.

Test plan

  • php83 -l clean on the patched plugin
  • raw_execute verified for both call patterns the patch introduces (incl. chdir)
  • implode() succeeds for all 8 macros after the data fix
  • Confirmed the implode fatal in the log is timestamped 16:11:38, before the 16:18 fix
  • Plugin renders: rightbar button + JS present in plugins.min.js
  • No credential, key, or secret in the repo (grep for keys/tokens/passwords)
## Summary Fixes the two faults reported from the running editor, and documents both properly. ### 1. The Terminal plugin didn't work — a removed API `plugins/Terminal/terminal.php` calls `Common::execute(...)`, which **no longer exists** in Atheos: ``` plugin calls : Common::execute(...) -> MISSING available : Common::raw_execute(...) -> ["code" => int, "output" => array] ``` It's a port of `Fluidbyte/Codiad-Terminal`, so it targets the old API and assumes the old return shape. Fixed to `raw_execute` + `implode` of the output array, and **verified against the real method**: `pwd`/`echo` return correctly, and after `chdir` it reports the blog path — so the terminal opens in the right directory. Two further upstream defects are documented but deliberately **not** fixed, so nobody trusts them: the block-list is dead code (`explode(",", BLOCKED === true)` passes a boolean, so `ssh`/`telnet` are never blocked), and `strtolower($command === "exit")` means `exit` doesn't clear the auth flag. ### 2. Settings → Macro returned HTTP 500 `components/macro/dialog.php:35` does `implode(", ", $macro["fTypes"])` — requires an **array** — but `class.macro.php::save()` writes the **string** `"N/A"` for folder macros. So **any** folder-type macro makes that dialog fatal. Our writer macros are all folder-scoped, which exposed it. Fixed by correcting the **data** (`"N/A"` → `[]`) rather than patching Atheos's code, so it survives an Atheos update. Verified: `implode()` now succeeds for all 8 macros. ## Review response ### ✅ Adopted — and one correction to my own previous commit Round 1 correctly flagged that I had **published a live credential**: the Terminal plugin's in-app gate constant was quoted verbatim in the docs. That was my error. The value has been **rotated** on the guest and **removed** from the docs and the patch file (now a placeholder). ### ❌ Declined, with measurement — `disable_functions` cannot be a default Review asked for `php.ini` to disable `exec`/`shell_exec`/`passthru`/`system` by default. **Measured: this would break the editor's core function.** ``` components/codegit/* -> Common::safe_execute 31 call sites (every git operation) components/macro/* -> Common::raw_execute 1 call site common.php raw_execute -> function_exists("system") / function_exists("exec") ``` Every commit, push, fetch and status from the editor runs through `exec`/`system`. Disabling them turns this into a text editor that cannot commit. It stays a **deliberate exposure lever** (S-7), applied only if the container is ever exposed — which is why S-7 already says *test, do not blind-apply*. ### ❌ Declined — deploy key read-only Writes are the entire point of the writer-branch workflow (macro 2 pushes). Not applicable. ### ⚠️ Corrected — a false positive Review reported a syntax error in `patches/Terminal-terminal.php.patched`. Verified with `php83 -l` on the guest: **No syntax errors detected**. The placeholder is a plain string literal inside a double-quoted PHP string. Recorded as a non-issue rather than changed to satisfy a false positive. ### 📝 Recorded as an operator decision — removing the Terminal plugin This is the reviewers' strongest point and it is **correct**: a browser shell is the largest attack surface in the container, and macros already provide command execution. It is documented as the recommended action, with the mechanism (`rename to Terminal.disabled` — `readDirectory()` skips `*.disabled`). **Left installed deliberately**: it is the operator's plugin, and removing a tool without being asked is not the agent's call. Flagged for them. ## Test plan - [x] `php83 -l` clean on the patched plugin - [x] `raw_execute` verified for both call patterns the patch introduces (incl. `chdir`) - [x] `implode()` succeeds for all 8 macros after the data fix - [x] Confirmed the implode fatal in the log is timestamped 16:11:38, before the 16:18 fix - [x] Plugin renders: rightbar button + JS present in `plugins.min.js` - [x] No credential, key, or secret in the repo (`grep` for keys/tokens/passwords)
hermes added 3 commits 2026-09-29 16:22:38 +10:00
Diagnosed two independent faults reported from the running editor.

1. Terminal plugin (market) did not work as shipped.
   Root cause: it is a port of Fluidbyte/Codiad-Terminal and calls Common::execute(),
   which Atheos REMOVED (renamed to raw_execute with a different return shape).
     plugin calls : Common::execute(...)      -> MISSING
     available    : Common::raw_execute(...)  -> ["code"=>int, "output"=>array]
   Fixed to raw_execute + implode of the output array. Verified against the real
   method: pwd/echo return correctly, and after chdir it reports the blog repo path,
   so the terminal opens in the right directory.

   Two further upstream defects found and deliberately NOT fixed, but documented so
   nobody trusts them: the block-list is dead code (`explode(",", BLOCKED === true)`
   passes a boolean, so ssh/telnet are never blocked), and `strtolower($command ===
   "exit")` means typing exit does not clear the auth flag.

   Security note recorded: this plugin is a browser shell as the php-fpm user, gated
   by a hardcoded in-app constant (PASSWORD="terminal"). It is not a secret. Strongest
   argument yet for keeping the container LAN-only.

2. Settings -> Macro dialog returned HTTP 500.
   Root cause: Atheos's own components/macro/dialog.php:35 does implode(", ",
   $macro["fTypes"]) which requires an ARRAY, but class.macro.php::save() writes the
   STRING "N/A" for folder macros. So ANY folder-type macro makes that dialog fatal:
     PHP Fatal error: Uncaught TypeError: implode(): Argument #2 ($array) must be of
     type ?array, string given in .../components/macro/dialog.php:35
   Our writer macros are all folder-scoped, so adding them exposed it.

   Fixed by correcting the DATA (fTypes ""N/A"" -> "[]") rather than patching Atheos's
   code, so the fix survives an Atheos update. Verified implode() now succeeds for all
   8 macros. The trade-off and the proper upstream fix are both documented.

Also records the diagnostic order for any future plugin problem, and the rule of
thumb: a Codiad-era plugin's Common:: calls are probably stale.

Adds:
  docs/known-issues.md                  both faults, root causes, fixes, verification
  patches/Terminal-terminal.php.patched the patched plugin file
  files/macros.db.php                   regenerated with the corrected fTypes
Responds to the independent security review, which correctly flagged that the previous
commit published a live credential.

The Terminal plugin's in-app password gate is a hardcoded constant. The previous commit
quoted the value verbatim in docs/known-issues.md - so a repo that is readable by
collaborators (and could become public) contained a working credential for a web shell
running as the php-fpm user. That was my error.

Changed:
  - ROTATED the password on the guest to a random value (no longer the upstream default,
    and no longer anything a reader of the source could guess).
  - REMOVED the value from docs/known-issues.md. The doc now describes the mechanism and
    says how to read it on the guest, rather than reproducing it.
  - REDACTED it from patches/Terminal-terminal.php.patched (shows a placeholder; the real
    value lives only on the guest).
  - Documented that a rebuild returns to the upstream default, so it must be rotated again
    after any rebuild.

Also records the honest recommendation the reviewers asked for: this plugin is the largest
attack surface in the container, it duplicates capability the macros already provide, and
the right move is to remove it unless a browser shell is specifically wanted. S-7
(disable exec/proc_open) is named as the lever that would neutralise both.

Note on what this does NOT fix: the RCE is inherent to the plugin - any browser terminal
runs commands as the web user, and the gate is a single shared constant with no per-user
auth. Rotating the value reduces trivial access; it does not make the plugin safe. That is
stated plainly rather than papered over.
Second review round asked for three things. Two adopted, one measured and declined.

ADOPTED
  - Removing the Terminal plugin is recommended in writing as the right move (it is the
    largest attack surface, and macros already provide command execution). Left installed
    because it is the operator's plugin - removing a tool unilaterally is not the agent's
    call. Documented how: rename to Terminal.disabled (readDirectory() skips *.disabled).
  - Automatic password rotation and the deploy-key-read-only suggestion are recorded as
    operator decisions with their trade-offs.

DECLINED, WITH EVIDENCE
  - "php.ini should disable exec/shell_exec/passthru/system by default." Measured, this
    would break the editor's core function, so it cannot be a default:
        components/codegit/*   -> Common::safe_execute   31 call sites (every git op)
        components/macro/*     -> Common::raw_execute     1 call site
        common.php raw_execute -> function_exists("system") / function_exists("exec")
    Every commit, push and fetch from the editor runs through exec/system. It stays a
    deliberate exposure lever (S-7), not a default.

CORRECTED
  - Review claimed the redacted patch file had a syntax error. Verified on the guest with
    php83 -l: "No syntax errors detected". The placeholder is a plain string literal inside
    a double-quoted PHP string. That finding was wrong, and is recorded as such rather than
    "fixed" to satisfy a false positive.

Also fixed the section ordering in the doc.
hermes merged commit 0bdd15b31b into master 2026-09-29 16:22:39 +10:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: hermes/atheos-blog-lxc#3