The previous chapter ended with every channel hall using one canonical name, specialised URL doors checking their real host, and Claude summaries learning to speak publicly without waking the entire owlery.
This chapter begins immediately after MB416.
It covers four focused rounds:
MB417 yt-dlp argument-injection guard
MB418 OpenAI error classification
MB419 actionable and consistent OpenAI diagnostics
MB420 verified API transport and final pre-commit security audit
The project remains on the 3.2dev line.
This is not the 3.3 release.
As always:
No database schema was changed.
Mediabot launches yt-dlp with Perl’s list-form exec, so the request never passed through a shell.
That prevented classic shell expansion, but it did not prevent a different class of attack.
The IRC search text was appended directly to the yt-dlp argument vector:
push @cmd, $query;
A request beginning with a dash could therefore be interpreted by yt-dlp as one of its own options rather than as a search term.
Some of those options are powerful. A title shaped like an execution option could cross the boundary between “find this song” and “run this program”.
MB417 inserts the standard end-of-options marker before the user-controlled text:
push @cmd, '--', $query;
Everything after -- is positional data.
A normal request behaves exactly as before, while text beginning with - or -- can no longer masquerade as a yt-dlp switch.
The gate now knows the difference between a song title and a spell.
The public OpenAI path used to collapse every failed HTTP request into one reply:
Sorry, API did not answer.
The server log contained only the HTTP status and reason phrase. The JSON body returned by the provider — the part that actually explained the failure — was discarded.
That made several very different situations look identical:
transient request or token rate limit
account-level quota or budget condition
authentication failure
model or permission problem
provider-side service failure
MB418 adds one shared parser for the provider error envelope:
error.type
error.code
error.message
The public response and server log can now distinguish the failure class instead of guessing from HTTP 429 alone.
A transient, model-specific rate limit may be avoided by a configured fallback model.
An account-level quota condition cannot.
MB418 therefore allows a fallback on an ordinary transient 429, but deliberately avoids spending a second request when the error class says that switching models cannot repair the problem.
MB418 exposed the provider’s error class. MB419 turned that information into operational guidance.
The command paths now distinguish:
401 authentication or invalid API key
403 project, model, access or regional permission refusal
404 unavailable or inaccessible model/endpoint
429 transient rate limit
5xx provider service error
0 DNS, TLS, firewall or endpoint connectivity failure
A 403 is no longer automatically described as a dead key. It may be a permission or model-access decision.
A transient 429 no longer recommends rotating credentials.
A 401 tells the operator to inspect or replace the configured key.
openai.TIMEOUT already affected the Owner diagnostic path, but the public tellme/ChatGPT request still used a separate hard-coded 30-second value.
MB419 connects the public path to the same bounded configuration:
minimum 5 seconds
default 20 seconds
maximum 60 seconds
The status output, setter, reset command and sample configuration all describe the same value.
openai diagnoseThe existing Owner health check now has an explicit diagnostic alias:
openai diagnose [prompt]
It uses the same error parser and classification as the public command, reports the selected model and fallback behaviour, and keeps the API key out of every NOTICE.
The live OpenAI reply path was validated before this chapter was prepared.
The final pre-commit review found a security problem outside the new error classifier.
Mediabot’s shared HTTP factory historically defaults to disabled certificate verification for compatibility with a few old or local endpoints.
That default is unsuitable for a request carrying an API credential.
Before MB420, several official API clients inherited the compatibility default rather than explicitly demanding certificate validation.
MB420 now sets:
verify_SSL => 1
for credential-bearing requests to:
OpenAI
Anthropic
TMDB
YouTube Data API
Fortnite API
The generic URL-title fetcher and the explicitly local Icecast compatibility path are not changed by this round.
This is a targeted rule:
Public or local scraping may have compatibility policy. A secret-bearing API request must verify its peer.
The same audit found a separate credential leak.
getYoutubeDetails() built a URL containing the YouTube API key and then included the complete URL in two error logs:
HTTP failure
empty response
MB420 keeps the useful information — HTTP status and video ID — but never writes the key-bearing query string.
The log now identifies the failed video without copying the credential into the journal.
openai test and openai diagnose used the MB419 parser, while openai models still maintained a smaller, older error parser.
MB420 removes that discrepancy.
The model-list command now reports the same bounded fields:
HTTP status
type
code
actionable diagnosis
sanitised provider message
All three Owner paths use the configured timeout and verified TLS.
The help text and sample configuration now document:
openai diagnose
transient 429 fallback behaviour
openai set timeout 20
openai reset timeout
Provider-controlled error fields also lose ASCII control bytes before entering logs or IRC notices.
The supplied baseline before MB420 reported:
full suite through MB418: 8693/8693
The final audit added:
MB420 contract test: 13/13
MB417/OpenAI/YouTube focused selection: 344/344
fresh-copy installation: OK
second installation: idempotent
forced rollback: OK
embedded payload checks: SHA256 verified
The audit container does not contain the complete Perl runtime used by the server, so it does not claim a new full-suite total or a standalone compilation result.
The installer performs the real Perl syntax checks on teuk.org and restores every original file automatically if one fails.
Focused and full-suite totals overlap and must not be added together.
0 new tables
0 altered columns
0 migrations
0 schema changes
0 private configuration committed
0 release to 3.3
Mediabot remains on:
3.2dev-*
Mediabot now:
yt-dlp option;openai diagnose command;The radio gate no longer confuses a title with an executable instruction.
The oracle no longer answers every failure with the same shrug.
And the keys carried beyond the castle walls now travel under a seal whose identity is actually checked.
You must be logged in to reply.