Forum teuk.org

🧪 Salvio Hexia: Isolating Partyline `.eval` in a Timed Subprocess

in Mediabot · started by TeuK · 1w ago

TeuK · 1w ago

🧪 Salvio Hexia: Isolating Partyline .eval in a Timed Subprocess

This Mediabot v3 update focuses on one sensitive operator feature:

Partyline .eval

The command is useful for live debugging and emergency inspection, but it is also dangerous if it runs directly inside the bot process.

This pass restores and hardens the safer design: .eval now runs in a forked subprocess with a hard timeout, output limits, and regression tests to prevent the old unsafe implementation from coming back.


Context

The Partyline .eval command is Owner-only, but Owner-only does not mean harmless.

The previous implementation had reverted to an in-process eval style:

eval { local $_ = undef; eval $code; }

That meant dangerous code such as:

while (1) {}

could freeze the entire bot.

For an IRC bot built on an event loop, that is unacceptable.

The goal of this pass was to keep the operator utility of .eval, while making sure bad or blocking code cannot hang Mediabot itself.


What changed

Main changes:

Partyline .eval now runs in a forked subprocess
hard timeout added for eval execution
confirmation window preserved
stdout/stderr capture made safer
output sanitized and limited
sample configs updated
new regression test added

Files touched:

Mediabot/Partyline.pm
mediabot.sample.conf
Mediabot/mediabot.sample.conf
t/cases/13_partyline_eval_safety.t

B1 — In-process .eval removed

The old dangerous pattern was removed from _cmd_eval.

Before, .eval executed inside the live bot process.

That was risky because any blocking Perl code could freeze:

IRC handling
Partyline sessions
timers
scheduled tasks
metrics updates
DCC handling

Now, _cmd_eval forks a subprocess using:

open(my $pipe, "-|")

The child process executes the eval code. The parent process only reads output from the pipe.

That means a bad eval can kill or block the child process, but not the live bot.


B2 — Hard timeout for .eval

A timeout was added around eval execution.

The setting is configurable:

PARTYLINE_EVAL_TIMEOUT_SECONDS=5

The value is clamped internally between:

1 second
15 seconds

If the eval code runs too long, the child exits with a timeout marker and the Partyline user receives:

Eval timed out after 5s.

This protects against commands such as:

while (1) {}
sleep 999;

B3 — Safer output capture

The old implementation attempted to capture output through localized global filehandles.

That approach is fragile and can behave differently across Perl versions and contexts.

The new implementation captures output through the subprocess pipe.

This is cleaner:

child process writes output
parent reads from pipe
bot process stays isolated

STDERR is redirected to STDOUT inside the child, so both normal output and errors can be captured by the parent.


Confirmation window preserved

.eval still requires confirmation.

The operator must type the same command twice within 30 seconds.

Example:

.eval print "hello\n"
--- .eval confirmation required ---
Type the same .eval command again within 30 seconds to execute.

.eval print "hello\n"
--- eval output ---
hello
--- ok ---

If the confirmation expires, the command must be confirmed again.

This keeps the protection against accidental execution.


Output sanitation

The output returned to the Partyline is now cleaned before being displayed.

The handler:

strips unsafe ASCII control characters
limits each output line to 500 characters
limits output to 20 lines
prints a truncation marker when needed

This avoids flooding the Partyline with huge output or weird control characters.


Sample configuration updated

Both sample configuration files now document the new timeout option:

mediabot.sample.conf
Mediabot/mediabot.sample.conf

Configuration entry:

PARTYLINE_EVAL_TIMEOUT_SECONDS=5

The comment explains that this option controls the Owner-only .eval subprocess timeout.


Regression test added

A new test file was added:

t/cases/13_partyline_eval_safety.t

It protects the new behavior statically.

The test verifies that:

.eval remains Owner-only
confirmation expires after 30 seconds
PARTYLINE_EVAL_TIMEOUT_SECONDS is used
.eval runs through a forked subprocess pipe
timeout reporting exists
old in-process eval block is absent
old STDOUT scalar-ref capture is absent
old STDERR scalar-ref capture is absent
sample configs document the timeout option

This matters because this bug had already come back once. Now, if the old unsafe implementation returns, the test suite should catch it.


Suggested validation

Syntax checks:

perl -I. -c Mediabot/Partyline.pm
perl -I. -c mediabot.pl
perl -I. -c t/cases/13_partyline_eval_safety.t

Focused tests:

env LANG=C.UTF-8 LC_ALL=C.UTF-8 perl t/test_commands.pl --filter 'partyline_eval_safety|partyline|auth|metrics' --verbose

Full suite:

env LANG=C.UTF-8 LC_ALL=C.UTF-8 perl t/test_commands.pl --verbose

Runtime tests from the Partyline, as Owner:

.eval print "hello\n"
.eval while (1) {}
.eval print "A" x 1000

Expected behavior:

normal eval returns output
infinite loop times out
long output is truncated safely
bot remains responsive

Suggested commit

🧪 Salvio Hexia: isolate Partyline eval in a timed subprocess

Why this matters

.eval is a powerful operator tool.

Powerful tools should be treated with respect.

This update keeps the command useful while protecting the bot from the most obvious failure mode: executing bad code inside the live event loop.

The new design is safer, test-covered, configurable, and much harder to accidentally regress.

Mediabot v3 keeps its sharp tools — but now they come with a sheath.

You must be logged in to reply.