ADR-044: Error-analysis tools with fail-closed guards 

Status

Accepted

Date

2026-07-09

Authors

Netresearch DTT GmbH

Context 

The agent can read content and configuration (ADR-042), but the debugging use cases admins actually bring to the playground — "why does this URL answer 500", "how do I fix this PHP error" — need capabilities the tool set lacked: reading the TYPO3 file logs with the failing source code, reading arbitrary project files, searching the code base, and probing a frontend URL. All four egress host-level data to an external LLM provider and take model-chosen (attacker-influenceable) arguments, so each needs a hard, fail-closed containment story.

Decision 

Four admin-only tools, two shared guards.

Tools 

get_last_exception (group code)
Newest error-level entries from the TYPO3 file logs, with the parsed stack trace and ±6 lines of source context inlined for up to three project-local frames (vendor/core frames are listed, not expanded). index steps back through older errors, search filters.
read_source (group code)
Line-ranged, line-numbered read of one project file (default 60, max 200 lines).
search_code (group code)
Literal-substring (opt-in regex) search over project source files — a pure-PHP walk, no shell-out — returning path:line hits under a hard budget (20 000 files / 5 s), reported when exhausted.
probe_url (group system)
One GET against the instance's own frontend: status, key headers, timing, a 2 KB tag-stripped body excerpt — and on a 5xx the newest error-log entries from the probe's ±30 s window are appended through the same log parser, so probe and cause arrive in one result.

Shared guards 

SourcePathGuard (used by 1–3)
Every file access resolves through realpath and must stay inside the project root — one containment check defeats ../ traversal and symlink escapes alike. Denied outright: dot segments (.env, .git, .ddev), var/* except var/log, config/system/* and any settings.php/additional.php, key-material extensions (key/pem/crt/p12/pfx) and paths mentioning credential. Credential-looking assignment lines are value-redacted on read; the code walk skips vendor/, node_modules/, var/ and dot directories and only considers source extensions.
LogExceptionReader (used by 1 and 4)
Parses TYPO3 FileWriter records (error levels only, newest first, bounded to each file's 2 MB tail) into timestamp, level, component, message, exception class and stack frames. One parser, two consumers — probe_url's 5xx↔log correlation reuses it instead of duplicating the format knowledge.
probe_url SSRF containment
Only http(s), and the target host[:port] must match a site base or base variant of this instance (SiteFinder); relative paths resolve against the first site. Redirects are reported, never followed — a 3xx cannot bounce the probe off-host. Transport errors surface sanitized (URL credential parameters masked).

Consequences 

  • The "analyse the error" loop closes: probe_url → correlated log entry → get_last_exception (full trace + code) → read_source / search_code for the fix site — without leaving the playground.
  • All four tools are requiresAdmin() = true and live behind the ADR-043 group cascade (new group code; probe_url joins system), so one central toggle silences the whole family.
  • settings.php and other credential carriers are structurally unreadable even for admins — the guard has no bypass parameter by design.
  • probe_url performs real frontend requests (cache warm-up, log entries, load); it is deliberately GET-only, single-request, 15 s-capped.