Forum teuk.org

🎵🛡️🔮 Mediabot v3: A Song Title Cannot Cast a Shell Spell, and the API Oracle Finally Speaks Clearly — MB417 to MB420

in Mediabot · started by TeuK · 1mo ago

TeuK · 1mo ago

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.


🎶🚫 MB417 — A song request can no longer disguise itself as a command-line option

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.


🧾🔍 MB418 — “The API did not answer” is no longer the whole diagnosis

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 fallback model is used only when it can help

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.


🧭🪄 MB419 — The diagnosis tells the operator what to do

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.

One timeout setting for the real command and its diagnostic

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 diagnose

The 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.


🔐🌐 MB420 — API keys now travel only through verified TLS

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 YouTube key disappears from error logs

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 Owner commands finally share one diagnostic path

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.


🧪 Validation ledger

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.


🧱 Database and release impact

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-*

✨ What this chapter leaves behind

Mediabot now:

  • prevents a radio search term from becoming a yt-dlp option;
  • distinguishes transient OpenAI rate limits from other API failures;
  • provides useful guidance for authentication, permissions, models, connectivity and provider errors;
  • applies one bounded timeout to the public OpenAI command and its Owner diagnostics;
  • exposes an explicit openai diagnose command;
  • avoids pointless fallback calls when another model cannot solve the reported condition;
  • verifies TLS for every audited request carrying an external API credential;
  • keeps the YouTube API key out of error logs;
  • uses one shared OpenAI diagnosis path for public replies, health checks and model listing;
  • sanitises provider error fields before they reach logs or IRC.

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.