fix: rDelete fataled on musl (GLOB_BRACE) - broke ALL deletion and plugin removal
Reported as "deleting terminal just cycles in the ui". Root cause is much larger than the
plugin: it was a platform bug that broke deletion everywhere.
CAUSE
traits/helpers.php::rDelete() did:
glob($target . "{*,.[!.]*,..?*}", GLOB_BRACE|GLOB_MARK);
GLOB_BRACE is a GLIBC extension. Alpine is musl, where the constant is undefined,
and referencing an undefined constant is a FATAL in PHP 8:
PHP Fatal error: Uncaught Error: Undefined constant "GLOB_BRACE"
in traits/helpers.php:138
#0 components/market/class.market.php(178): Common::rDelete()
#1 components/market/controller.php(46): Market->remove()
The fatal kills the request before Common::send(200), so the front end never gets a
settle response and the UI spins forever. A hang, not an error - which is why it
presented as "cycles in the ui".
BLAST RADIUS (measured, two call sites)
components/market/class.market.php:178 removing a plugin from the market
components/filetree/class.filetree.php:59 DELETING ANY FILE OR FOLDER IN THE EDITOR
The second is core functionality, so this was never a plugin bug - installing the
Terminal plugin is just what made it visible.
FIX
patches/helpers-rDelete-GLOB_BRACE.diff - expand the brace pattern by hand when
GLOB_BRACE is undefined, keeping upstream behaviour where it is defined. Verified:
nested dirs + dotfiles + single files all delete correctly now.
Upstream has NOT fixed this (checked main) so it cannot be resolved by updating.
PLUGIN REMOVED
Once deletion worked, removal via the market succeeded and the plugin is now gone:
{"text":"Plugin successfully removed.","status":200}
That is the right end state - a browser shell was the largest attack surface, and
macros already provide command execution. Removed through the market's own code path
so the plugin list, addons cache and bundle stayed consistent.
TESTS (now 42 passed, 1 failed - the failure is branch protection, a genuine open item)
+ a GLOB_BRACE regression guard that fails loudly if the patch is lost to an update
+ the identity guard is INVERTED: FR-11 required an EMPTY identity so the agent could
not impersonate the operator, but the operator has since asked for it to be set, so
the test asserting emptiness was asserting the opposite of the current design. It
now requires the identity to be present and to NOT look agent-generated.
docs/known-issues.md: new section 1 documents the GLOB_BRACE fault in full, including the
honest note that this is the real cost of choosing Alpine over a glibc base image.
This commit is contained in:
+101
-21
@@ -9,7 +9,79 @@ Pinned Atheos commit: `f90cd1806dbe0e25673a77e27390e8b1a240c3f6` (build 611).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 🔴 Plugin market: the **Terminal** plugin does not work as shipped
|
## 1. 🔴 **Deleting any file or folder was broken** — `GLOB_BRACE` does not exist on musl
|
||||||
|
|
||||||
|
**Symptom:** removing the Terminal plugin from the market **cycled forever in the UI** (never
|
||||||
|
completed, never errored). Deleting a file or folder in the editor was broken by the same cause.
|
||||||
|
|
||||||
|
**Root cause — a glibc-only constant referenced on musl.** `traits/helpers.php::rDelete()`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$files = glob($target . "{*,.[!.]*,..?*}", GLOB_BRACE|GLOB_MARK);
|
||||||
|
```
|
||||||
|
|
||||||
|
`GLOB_BRACE` is a **glibc extension**. Alpine uses **musl**, where the constant is *undefined* —
|
||||||
|
and touching an undefined constant is a **fatal error** in PHP 8 (a warning in older versions):
|
||||||
|
|
||||||
|
```
|
||||||
|
PHP Fatal error: Uncaught Error: Undefined constant "GLOB_BRACE"
|
||||||
|
in /var/www/atheos/traits/helpers.php:138
|
||||||
|
#0 components/market/class.market.php(178): Common::rDelete()
|
||||||
|
#1 components/market/controller.php(46): Market->remove()
|
||||||
|
```
|
||||||
|
|
||||||
|
Because the fatal kills the request before `Common::send(200)`, the front end never receives a
|
||||||
|
settle response — so the UI **spins indefinitely**. A hang, not an error.
|
||||||
|
|
||||||
|
**Blast radius — measured, two call sites:**
|
||||||
|
|
||||||
|
| Caller | What breaks |
|
||||||
|
|---|---|
|
||||||
|
| `components/market/class.market.php:178` | removing a plugin from the market |
|
||||||
|
| `components/filetree/class.filetree.php:59` | **deleting any file or folder in the editor** |
|
||||||
|
|
||||||
|
The second is core editor functionality, so this is **not** a plugin bug — installing the Terminal
|
||||||
|
plugin is merely what made it visible. Verified directly: with the original code, `rDelete()` fatals
|
||||||
|
and the target is left on disk.
|
||||||
|
|
||||||
|
**Fix applied** (`patches/helpers-rDelete-GLOB_BRACE.diff`) — expand the brace pattern by hand when
|
||||||
|
`GLOB_BRACE` is absent, keeping upstream's exact behaviour where it *is* defined:
|
||||||
|
|
||||||
|
```php
|
||||||
|
if (defined("GLOB_BRACE")) {
|
||||||
|
$files = glob($target . "{*,.[!.]*,..?*}", GLOB_BRACE | GLOB_MARK);
|
||||||
|
} else {
|
||||||
|
$files = array_merge(
|
||||||
|
glob($target . "/*", GLOB_MARK) ?: [],
|
||||||
|
glob($target . "/.[!.]*", GLOB_MARK) ?: [],
|
||||||
|
glob($target . "/..?*", GLOB_MARK) ?: []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The three patterns are precisely what the brace expansion produces (`*`, dotfiles, and `.`/`..`-like
|
||||||
|
names), so the directory listing is equivalent.
|
||||||
|
|
||||||
|
**Verified** — `php83 -l` clean, then a real deletion of a tree containing nested dirs and two
|
||||||
|
dotfiles:
|
||||||
|
|
||||||
|
```
|
||||||
|
entries before: 10
|
||||||
|
rDelete() returned: true
|
||||||
|
exists after: gone (OK)
|
||||||
|
single file: rDelete=true exists=gone (OK)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upstream has NOT fixed this** — the same line is still in `main` (checked directly), so it cannot
|
||||||
|
be resolved by updating Atheos. Re-apply this patch after any update (`scripts/update-atheos.sh`).
|
||||||
|
|
||||||
|
**Consequence for the platform choice:** this is the real cost of Alpine. A glibc-based image
|
||||||
|
(Debian/Ubuntu) would not have hit it. It is patchable in one place, so Alpine stays — but it is worth
|
||||||
|
knowing that `GLOB_BRACE` is the kind of thing musl breaks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 🔴 Plugin market: the **Terminal** plugin does not work as shipped
|
||||||
|
|
||||||
**Symptom:** installed the Terminal plugin from the market; it failed / did not function.
|
**Symptom:** installed the Terminal plugin from the market; it failed / did not function.
|
||||||
|
|
||||||
@@ -86,17 +158,30 @@ Because of (1) and (2), the in-app password is treated as a **secret**:
|
|||||||
- it changes per install; `scripts/provision.sh` does not set one, so a rebuild returns to the
|
- it changes per install; `scripts/provision.sh` does not set one, so a rebuild returns to the
|
||||||
upstream default — **rotate it again after a rebuild**
|
upstream default — **rotate it again after a rebuild**
|
||||||
|
|
||||||
**Recommendation: remove this plugin unless you specifically want a browser shell.** It was installed
|
**Outcome: the plugin has been REMOVED.** Once the `GLOB_BRACE` fault (§1) was fixed, removal through
|
||||||
during testing to diagnose a rendering problem; it is the single largest attack surface in the
|
the market succeeded — and that is the right end state: a browser shell is the single largest attack
|
||||||
container. Note that Atheos macros already provide command execution, so the plugin adds little you
|
surface in the container, and **Atheos macros already provide command execution**, so it added little
|
||||||
do not already have — and macros are at least under your own configuration.
|
you did not already have. Removal was done by invoking the market's own `remove()` path, so the plugin
|
||||||
|
list, the `addons` cache and the generated bundle all stayed consistent:
|
||||||
|
|
||||||
|
```
|
||||||
|
{"text":"Plugin successfully removed.","status":200}
|
||||||
|
plugins/ -> (only README.md remains)
|
||||||
|
addons cache references to Terminal: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
The notes above are kept because they document what the plugin *is* and how it failed — and because
|
||||||
|
the same stale-API pattern will recur with any other Codiad-era plugin. To reinstall, use the
|
||||||
|
market: it will re-introduce the `Common::execute` fault, so `patches/Terminal-terminal.php.patched`
|
||||||
|
was the fix — **it has been deleted along with the plugin**. If you ever reinstall, re-derive it:
|
||||||
|
`grep -rhoE 'Common::[a-zA-Z_]+' plugins/Terminal/` and expect `Common::execute` to be missing.
|
||||||
|
|
||||||
`docs/security-notes.md` §2 S-7 covers disabling `exec`/`proc_open`, which would neutralise **both**
|
`docs/security-notes.md` §2 S-7 covers disabling `exec`/`proc_open`, which would neutralise **both**
|
||||||
this plugin and the macros. That is the correct lever if you ever expose this container.
|
this plugin and the macros. That is the correct lever if you ever expose this container.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 🟠 The Macro dialog crashed when folder-scoped macros existed
|
## 3. 🟠 The Macro dialog crashed when folder-scoped macros existed
|
||||||
|
|
||||||
**Symptom:** the Settings → Macro dialog returned HTTP 500.
|
**Symptom:** the Settings → Macro dialog returned HTTP 500.
|
||||||
|
|
||||||
@@ -139,7 +224,7 @@ Worth doing if the dialog starts 500-ing again after editing a macro in the UI.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Checking for plugin problems in future
|
## 4. Checking for plugin problems in future
|
||||||
|
|
||||||
The plugin's JS is bundled into `public/plugins.min.js` (built by `classes/sourcemanager.php`), and
|
The plugin's JS is bundled into `public/plugins.min.js` (built by `classes/sourcemanager.php`), and
|
||||||
its rightbar button is rendered by `components/sidebars/sb-right.php` from `plugin.json`. Both were
|
its rightbar button is rendered by `components/sidebars/sb-right.php` from `plugin.json`. Both were
|
||||||
@@ -173,22 +258,17 @@ verify them before trusting it.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Follow-ups not done (recorded so they are not forgotten)
|
## 5. Follow-ups not done (recorded so they are not forgotten)
|
||||||
|
|
||||||
These came out of review. They are **operator decisions**, not agent ones — several change what the
|
These came out of review. They are **operator decisions**, not agent ones — several change what the
|
||||||
editor can do.
|
editor can do.
|
||||||
|
|
||||||
### 4.1 Remove the Terminal plugin (recommended)
|
### 5.1 Remove the Terminal plugin — ✅ DONE
|
||||||
|
|
||||||
The strongest advice from review, and correct: a browser shell is the largest attack surface in this
|
Removed through the market's own code path once §1 was fixed (see §2). A browser shell was the largest
|
||||||
container, and **Atheos macros already provide command execution**, so the plugin adds little. It was
|
attack surface in the container and macros already cover command execution.
|
||||||
installed during testing to diagnose a plugin-rendering problem. Removing the plugin directory (or
|
|
||||||
renaming it to `Terminal.disabled` — `readDirectory()` skips `*.disabled`) removes the surface.
|
|
||||||
|
|
||||||
Left installed because it is the **operator's** plugin and removing it unilaterally would take away a
|
### 5.2 ❌ `disable_functions` cannot be "on by default" here — measured
|
||||||
tool without being asked.
|
|
||||||
|
|
||||||
### 4.2 ❌ `disable_functions` cannot be "on by default" here — measured
|
|
||||||
|
|
||||||
Review asked for `php.ini` to disable `exec`, `shell_exec`, `passthru`, `system` by default.
|
Review asked for `php.ini` to disable `exec`, `shell_exec`, `passthru`, `system` by default.
|
||||||
**This would break the editor's core function.** Evidence:
|
**This would break the editor's core function.** Evidence:
|
||||||
@@ -205,18 +285,18 @@ turns the editor into a text editor that cannot commit. So it stays **a delibera
|
|||||||
you would also lose macros and git integration, which is why S-7 already says *"test, do not
|
you would also lose macros and git integration, which is why S-7 already says *"test, do not
|
||||||
blind-apply"*.
|
blind-apply"*.
|
||||||
|
|
||||||
### 4.3 Rotate the terminal password automatically
|
### 5.3 Rotate the terminal password automatically
|
||||||
|
|
||||||
If the plugin is kept, `provision.sh` should generate a random password rather than leaving the
|
If the plugin is kept, `provision.sh` should generate a random password rather than leaving the
|
||||||
upstream default. Not implemented: it would hardcode a secret into a repo file, which is the same
|
upstream default. Not implemented: it would hardcode a secret into a repo file, which is the same
|
||||||
class of problem this file already documents.
|
class of problem this file already documents.
|
||||||
|
|
||||||
### 4.4 Deploy key could be read-only
|
### 5.4 Deploy key could be read-only
|
||||||
|
|
||||||
Review suggested making the deploy key read-only if writes are unnecessary. **They are necessary** —
|
Review suggested making the deploy key read-only if writes are unnecessary. **They are necessary** —
|
||||||
that is the whole point of the writer-branch workflow (macro 2 pushes). Not applicable.
|
that is the whole point of the writer-branch workflow (macro 2 pushes). Not applicable.
|
||||||
|
|
||||||
### 4.5 The real upstream fix for §2
|
### 5.5 The real upstream fix for §3
|
||||||
|
|
||||||
`class.macro.php::save()` should write `[]` instead of `"N/A"` for folder macros. See §2 — the fix is
|
`class.macro.php::save()` should write `[]` instead of `"N/A"` for folder macros. See §3 — the fix is
|
||||||
currently applied to the data, so it survives updates, at the cost of being reversible from the UI.
|
currently applied to the data, so it survives updates, at the cost of being reversible from the UI.
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
<?php
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Atheos Terminal
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Copyright (c) 2020 Liam Siira (liam@siira.io), distributed as-is and without
|
|
||||||
// warranty under the MIT License. See [root]/docs/LICENSE.md for more.
|
|
||||||
// This information must remain intact.
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Copyright (c) 2013 Codiad & Kent Safranski
|
|
||||||
// Source: https://github.com/Fluidbyte/Codiad-Terminal
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
require_once("../../common.php");
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Verify Session or Key
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
Common::checkSession();
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Globals
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
$project = SESSION("project");
|
|
||||||
|
|
||||||
define("PASSWORD", "<ROTATED - retrieve on the guest>");
|
|
||||||
define("ROOT", Common::getWorkspacePath($project));
|
|
||||||
define("BLOCKED", "ssh,telnet");
|
|
||||||
// define("ENABLED", "java,ls,cd,echo,javac");
|
|
||||||
define("ENABLED", false);
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Terminal Class
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
|
|
||||||
class Terminal {
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Constructor
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
public function __construct() {
|
|
||||||
if (!isset($_SESSION["activeDir"]) || !isset($_SESSION["activeDir"][$_SESSION["project"]]) || empty($_SESSION["activeDir"][$_SESSION["project"]])) {
|
|
||||||
if (ROOT === "") {
|
|
||||||
$output = Common::raw_execute("pwd");
|
|
||||||
$_SESSION["activeDir"] = array([$_SESSION["project"]] => implode("\n", (array)$output["output"]));
|
|
||||||
} else {
|
|
||||||
$this->changeDir(ROOT);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->changeDir($_SESSION["activeDir"][$_SESSION["project"]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Primary call
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
public function process($str) {
|
|
||||||
$cmd = $this->parseCommand($str);
|
|
||||||
if ($cmd === "") return;
|
|
||||||
$output = Common::raw_execute($cmd);
|
|
||||||
return implode("\n", (array)$output["output"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Parse command for special functions, blocks
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
public function parseCommand($str) {
|
|
||||||
|
|
||||||
// Explode command
|
|
||||||
$command_parts = explode(" ", $str);
|
|
||||||
|
|
||||||
// Handle "cd" command
|
|
||||||
if (in_array("cd", $command_parts)) {
|
|
||||||
$cd_key = array_search("cd", $command_parts);
|
|
||||||
$cd_key++;
|
|
||||||
|
|
||||||
$dir = $command_parts[$cd_key];
|
|
||||||
|
|
||||||
$this->changeDir($dir);
|
|
||||||
// Remove from command
|
|
||||||
$str = str_replace("cd $dir", "", $str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace text editors with cat
|
|
||||||
$editors = array("vim", "vi", "nano");
|
|
||||||
$str = preg_replace("/^(".join("|", $editors).")/", "cat", trim($str));
|
|
||||||
|
|
||||||
|
|
||||||
if (ENABLED) {
|
|
||||||
// Handle enabled commands
|
|
||||||
$enabled = explode(",", ENABLED);
|
|
||||||
if (in_array($command_parts[0], $enabled) === false) {
|
|
||||||
$str = "echo ERROR: Command not allowed";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Handle blocked commands
|
|
||||||
$blocked = explode(",", BLOCKED === true);
|
|
||||||
if (in_array($command_parts[0], $blocked)) {
|
|
||||||
$str = "echo ERROR: Command not allowed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Update exec command
|
|
||||||
return $str . " 2>&1";
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Chnage Directory
|
|
||||||
//////////////////////////////////////////////////////////////////////////80
|
|
||||||
public function changeDir($dir) {
|
|
||||||
chdir($dir);
|
|
||||||
// Store new directory
|
|
||||||
$_SESSION["activeDir"][$_SESSION["project"]] = exec("pwd");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
// Processing
|
|
||||||
//////////////////////////////////////////////////////////////////////////////80
|
|
||||||
|
|
||||||
$activeUser = SESSION("user");
|
|
||||||
$command = POST("command");
|
|
||||||
|
|
||||||
if (strtolower($command === "exit")) {
|
|
||||||
$_SESSION["term_auth"] = false;
|
|
||||||
$output = "[EXIT]";
|
|
||||||
|
|
||||||
} else if (!isset($_SESSION["term_auth"]) || $_SESSION["term_auth"] !== true) {
|
|
||||||
if ($command === PASSWORD) {
|
|
||||||
$_SESSION["term_auth"] = true;
|
|
||||||
$output = "[AUTHENTICATED]";
|
|
||||||
} else {
|
|
||||||
$output = "Enter Password:";
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$Terminal = new Terminal();
|
|
||||||
$output = "";
|
|
||||||
$command = explode(" && ", $command);
|
|
||||||
debug($command);
|
|
||||||
foreach ($command as $c) {
|
|
||||||
$output .= $Terminal->process($c);
|
|
||||||
// $output .= implode("\n",$Terminal->process($c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = array(
|
|
||||||
"data" => htmlentities($output),
|
|
||||||
"dir" => htmlentities(exec("pwd")),
|
|
||||||
"prompt" => "<span class=\"user\">$activeUser</span>:<span class=\"path\">" . exec("pwd") . "</span>$ "
|
|
||||||
);
|
|
||||||
|
|
||||||
Common::send("success", $output);
|
|
||||||
|
|
||||||
?>
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
--- /var/www/atheos/traits/helpers.php.orig
|
||||||
|
+++ /var/www/atheos/traits/helpers.php
|
||||||
|
@@ -135,7 +135,18 @@
|
||||||
|
|
||||||
|
if (is_dir($target)) {
|
||||||
|
|
||||||
|
- $files = glob($target . "{*,.[!.]*,..?*}", GLOB_BRACE|GLOB_MARK); //GLOB_MARK adds a slash to directories returned
|
||||||
|
+ // GLOB_BRACE is a glibc extension and is UNDEFINED under musl (Alpine), where
|
||||||
|
+ // referencing it is a fatal error in PHP 8. Expand the brace pattern by hand
|
||||||
|
+ // so this works on both libcs.
|
||||||
|
+ if (defined("GLOB_BRACE")) {
|
||||||
|
+ $files = glob($target . "{*,.[!.]*,..?*}", GLOB_BRACE | GLOB_MARK);
|
||||||
|
+ } else {
|
||||||
|
+ $files = array_merge(
|
||||||
|
+ glob($target . "/*", GLOB_MARK) ?: [],
|
||||||
|
+ glob($target . "/.[!.]*", GLOB_MARK) ?: [],
|
||||||
|
+ glob($target . "/..?*", GLOB_MARK) ?: []
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
// $files = glob($target . "/*");
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
@@ -108,7 +108,29 @@ done
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=============================================================="
|
echo "=============================================================="
|
||||||
echo "GUARD: is master PROTECTED on the blog repo? (the writer-branch model depends on it)"
|
echo "REGRESSION: rDelete works (GLOB_BRACE is glibc-only; musl lacks it)"
|
||||||
|
echo "=============================================================="
|
||||||
|
# traits/helpers.php::rDelete() used GLOB_BRACE, which is undefined under musl -> PHP 8 fatal.
|
||||||
|
# That broke deleting ANY file/folder in the editor AND removing a market plugin (the UI
|
||||||
|
# would spin forever because the fatal killed the response before Common::send()).
|
||||||
|
# docs/known-issues.md §1. This test fails loudly if the patch is lost (e.g. by an update).
|
||||||
|
rm -rf /tmp/rdel_guard 2>/dev/null
|
||||||
|
if mkdir -p /tmp/rdel_guard/a/b/c /tmp/rdel_guard/.hd 2>/dev/null \
|
||||||
|
&& touch /tmp/rdel_guard/f.txt /tmp/rdel_guard/.dot /tmp/rdel_guard/a/b/c/deep.txt; then
|
||||||
|
ROUT=$(php83 -r 'require_once("/var/www/atheos/common.php"); echo Common::rDelete("/tmp/rdel_guard/") ? "ok" : "fail";' 2>&1 | grep -viE 'deprecat|warning|notice' | tail -1)
|
||||||
|
if [ "$ROUT" = "ok" ] && [ ! -e /tmp/rdel_guard ]; then
|
||||||
|
ok "rDelete removed a nested tree incl. dotfiles (delete works)"
|
||||||
|
else
|
||||||
|
if echo "$ROUT" | grep -q 'GLOB_BRACE'; then
|
||||||
|
bad "rDelete FATALS on GLOB_BRACE - re-apply patches/helpers-rDelete-GLOB_BRACE.diff"
|
||||||
|
else
|
||||||
|
bad "rDelete failed (got: $ROUT)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
bad "could not build the rDelete test tree"
|
||||||
|
fi
|
||||||
|
rm -rf /tmp/rdel_guard 2>/dev/null
|
||||||
echo "=============================================================="
|
echo "=============================================================="
|
||||||
# The whole branch-based workflow assumes the deploy key CANNOT push master. That is a
|
# The whole branch-based workflow assumes the deploy key CANNOT push master. That is a
|
||||||
# repo-side setting we cannot verify from here by inspection - so test it the way an
|
# repo-side setting we cannot verify from here by inspection - so test it the way an
|
||||||
@@ -128,10 +150,22 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=============================================================="
|
echo "=============================================================="
|
||||||
echo "FR-11 GUARD: agent created NO identity or armistace credential"
|
echo "IDENTITY: git identity is SET by the operator (FR-11 superseded by explicit request)"
|
||||||
echo "=============================================================="
|
echo "=============================================================="
|
||||||
chk "git user.name empty" "$(asuser 'git config --get user.name' 2>/dev/null)" ""
|
# FR-11 originally required the agent to leave the identity EMPTY so it could not impersonate
|
||||||
chk "git user.email empty" "$(asuser 'git config --get user.email' 2>/dev/null)" ""
|
# the operator. The operator has since explicitly asked for it to be set, so the requirement is
|
||||||
|
# superseded - and the guard is inverted: the identity must now be present and must be the
|
||||||
|
# OPERATOR's, not an agent-invented one. Guarding the old expectation would assert the opposite
|
||||||
|
# of the current design and fail forever (which is what it did).
|
||||||
|
NAME=$(asuser 'git config --get user.name' 2>/dev/null)
|
||||||
|
MAIL=$(asuser 'git config --get user.email' 2>/dev/null)
|
||||||
|
if [ -n "$NAME" ]; then ok "git user.name set ($NAME)"; else bad "git user.name is empty - macro 2 will refuse to commit"; fi
|
||||||
|
if [ -n "$MAIL" ]; then ok "git user.email set ($MAIL)"; else bad "git user.email is empty"; fi
|
||||||
|
case "$MAIL" in
|
||||||
|
*hermes*|*bot*|*noreply@anthropic*|*agent*) bad "identity looks agent-generated: $MAIL" ;;
|
||||||
|
"") ;;
|
||||||
|
*) ok "identity is an operator address, not an agent-invented one" ;;
|
||||||
|
esac
|
||||||
if [ -f /home/atheos/.ssh/id_ed25519_blog ]; then ok "deploy key present"; else bad "deploy key missing"; fi
|
if [ -f /home/atheos/.ssh/id_ed25519_blog ]; then ok "deploy key present"; else bad "deploy key missing"; fi
|
||||||
chk "deploy key mode" "$(stat -c '%a' /home/atheos/.ssh/id_ed25519_blog 2>/dev/null)" "600"
|
chk "deploy key mode" "$(stat -c '%a' /home/atheos/.ssh/id_ed25519_blog 2>/dev/null)" "600"
|
||||||
chk "deploy key owner" "$(stat -c '%U' /home/atheos/.ssh/id_ed25519_blog 2>/dev/null)" "atheos"
|
chk "deploy key owner" "$(stat -c '%U' /home/atheos/.ssh/id_ed25519_blog 2>/dev/null)" "atheos"
|
||||||
|
|||||||
Reference in New Issue
Block a user