Commit: fd44628
Version: 3.4dev-20260823_142740
Branch: master
Status: pushed to origin/master — security hardening validated; one repository-hygiene follow-up remains.
MB696 was not a feature round. After MB695 had reconciled the long-lived database schema and established a trustworthy upgrade path, the next step toward a future stable release was deliberately less visible: review what could still hurt Mediabot from a security, secrets, TLS or repository-hygiene perspective.
The feature freeze therefore remained in force. The round concentrated on four main areas:
No database schema change was introduced. No database write was required by the final validation. No Mediabot, Undernet or mbweb service restart was performed during the final proof.
This was a round about tightening doors rather than adding new rooms to the castle. 🏰
The first part of MB696 audited both the current public source tree and the full Git history with Gitleaks.
For the current tree, the result was clean:
Gitleaks findings: 0
The publication audit also reconfirmed that sensitive local material remains outside the public repository:
commit.sh NOT TRACKED
mediabot.conf NOT TRACKED
mbundernet.conf NOT TRACKED
No sensitive tracked filename was identified by the dedicated checks.
The important conclusion is simple:
The source code currently published by Mediabot does not contain a secret detected by Gitleaks.
Scanning the complete Git history produced four findings. They are all concentrated in one historical location:
Rule : generic-api-key
File : Mediabot/Mediabot.pm
Commit : c59c27f702f8
Date : 2023-08-07
The four matches correspond to two historical pairs of:
client_id
client_secret
The actual values were deliberately never printed during the review.
A sanitized inspection of the historical source identified the surrounding provider as the former Twitter API path:
api.twitter.com
This means these were not simply arbitrary words that happened to trigger a generic regular expression. They were credential-shaped historical assignments.
The safe operational position is therefore:
The old Twitter/X credentials must be considered historically exposed and should be confirmed revoked or otherwise unusable at the provider.
They do not exist in the current public source tree.
MB696 deliberately did not rewrite years of Git history merely to make a historical scanner counter disappear. A history rewrite would disrupt clones, forks and references while providing little security benefit if the historical provider credentials have already been invalidated.
One of the most important findings was hidden in a piece of historical compatibility logic.
Mediabot centralizes many HTTP calls through:
Mediabot::External::_make_http(...)
Historically this factory defaulted to:
verify_SSL = 0
The source explicitly explained that behavior as an OVH/Kimsufi compatibility workaround.
In other words, TLS certificate verification was available, but the common factory disabled it unless a caller explicitly requested it.
Some sensitive API paths had already compensated by supplying:
verify_SSL => 1
but the global default itself remained insecure.
Before changing such an old compatibility rule, MB696 tested whether it was still actually required.
The deployed host reported:
HTTP::Tiny : 0.080
IO::Socket::SSL : 2.098
Net::SSLeay : 1.92
HTTP can_ssl : YES
Verified HTTPS requests were then tested against real public services without sending application credentials.
Successful paths included:
GitHub HTTP 200
Google News HTTP 200
country.is HTTP 200
YouTube HTTP 200
OpenAI HTTP 401
Anthropic HTTP 401
The OpenAI and Anthropic 401 responses are expected here. No credentials were intentionally supplied. Receiving an HTTP application response proves that the verified TLS/network layer completed successfully before authentication was rejected.
An initial Google News probe returned:
status=599
That initially looked like it might confirm the historical TLS problem. It did not.
A dedicated probe tested the same endpoint with verified and unverified TLS, using different response-size limits:
secure-64K
status=599
detail=Size of response body exceeds the maximum allowed of 65536
secure-512K
status=200
insecure-64K
status=599
insecure-512K
status=200
The feed was approximately 123 KiB. The failure was therefore caused by the deliberately small 64 KiB test ceiling, not certificate verification.
OpenSSL independently confirmed the certificate chain:
Verification: OK
Verify return code: 0 (ok)
curl also completed successfully with HTTP 200.
The conclusion was clear:
The historical OVH/Kimsufi justification for disabling TLS verification globally is no longer valid on the current host.
MB696 therefore reverses the shared HTTP policy.
Before:
verify_SSL defaults to 0
After:
verify_SSL defaults to 1
The caller can still explicitly opt out where compatibility genuinely requires it. The important difference is that insecure TLS is no longer silent. It must now be visible in source code.
This changes the security model from:
remember to request certificate verification
into:
certificate verification is automatic unless somebody deliberately disables it.
A secure common default is useful, but authenticated requests deserve an even clearer contract.
The Tavily news path carries an API credential. MB696 therefore gives that client an explicit:
verify_SSL => 1
even though the factory would already provide it by default.
That makes the requirement local and obvious. A future modification of the shared HTTP factory cannot silently weaken the authenticated Tavily path without also breaking its regression/security contract.
country.is no longer inherits HTTP::Tiny’s insecure defaultThe whereis implementation used a direct HTTP::Tiny->new(...) constructor rather than Mediabot’s common HTTP factory.
HTTP::Tiny itself does not provide the new Mediabot secure default. The direct HTTPS request therefore now explicitly uses:
verify_SSL => 1
The surrounding eval protection remains in place so a network error cannot unexpectedly propagate through the IRC command path.
MB696 does not blindly pretend that every endpoint has the same operational constraints.
There remains one explicit production TLS opt-out in the configurable Icecast administration/radio client.
Final inventory:
Mediabot/AdminCommands.pm:198
PRODUCTION_TLS_BYPASS_COUNT=1
That exception is deliberately scoped.
The policy is now:
TLS verification by default everywhere. Any insecure compatibility exception must be explicit, narrow and reviewed.
That is fundamentally safer than making the entire application inherit the weakest endpoint’s requirements.
tools/security_audit.pl was strengthened to match the new architecture.
Before MB696, it primarily checked that authenticated APIs explicitly enabled TLS even though the common factory was insecure by default.
After MB696, it validates stronger invariants:
_make_http() defaults to verified TLS;verify_SSL => 0 calls are rejected;country.is request verifies TLS;Final result:
Verdict: GO — all 14 security invariants hold.
Several older tests encoded the previous OVH/Kimsufi behavior.
They were not simply removed because the old architecture changed. Instead, their intent was preserved while the expected policy was updated.
Modified regression contracts include:
t/cases/138_helpers_getversion_no_curl_shell.t
t/cases/139_helpers_whereis_no_curl_process.t
t/cases/207_helpers_http_guard_regression.t
t/cases/820_mb640_version_check_never_silent.t
A dedicated MB696 TLS contract was added:
t/cases/909_mb696_tls_secure_default.t
It guards:
While reviewing security and release hygiene, MB696 also discovered something unrelated to Perl TLS:
526 files under root node_modules/ were tracked by Git
This deserved investigation rather than an immediate rm -rf.
History showed that the vendor tree had entered the repository in a single old mbweb commit:
8ae1c75
2026-04-27
🪄 Alohomora mbweb: unlock the first Mediabot v3 web console
The commit brought roughly 500 dependency files with the initial web console. They were never meaningfully maintained afterward.
The working tree also contained:
package.json
package-lock.json
at repository root.
They were not tracked and contained only:
express-rate-limit
That immediately looked suspicious because the actual mbweb application imports:
bcryptjs
dotenv
express
express-session
helmet
mysql2
The root npm state was therefore not the application dependency contract.
The source documentation and running service established the actual architecture.
Canonical source:
contrib/mbweb/
Live deployed application:
/opt/mbweb/app
systemd confirms:
WorkingDirectory=/opt/mbweb/app
ExecStart=/usr/bin/node /opt/mbweb/app/app.js
The real manifests are:
contrib/mbweb/package.json
contrib/mbweb/package-lock.json
They were compared with the deployed manifests:
/opt/mbweb/app/package.json
/opt/mbweb/app/package-lock.json
The SHA-256 values matched exactly for both files.
The canonical direct dependency contract is therefore known:
bcryptjs ^2.4.3
dotenv ^16.6.1
express ^5.2.1
express-session ^1.19.0
helmet ^8.0.0
mysql2 ^3.22.2
This finally separates three concepts that had historically become mixed together:
contrib/mbweb/ canonical project source
/opt/mbweb/app live deployed runtime
root node_modules/ historical repository residue
The repository historically ignored all JSON files with:
*.json
That also accidentally hid npm manifests.
MB696 adds explicit exceptions for:
contrib/mbweb/package.json
contrib/mbweb/package-lock.json
Those two files are now included in the public repository.
A second dedicated test was added:
t/cases/910_mb696_node_dependency_hygiene.t
Its intended contract ensures that:
node_modules/ must not remain part of the repository.Focused validation:
2/2 files
19/19 tests
PASS
fd44628Before commit, the root vendor tree was removed from the Git index without deleting its local files.
Proof:
Git node_modules before : 526
Git node_modules after : 0
Local files before : 640
Local files after : 640
LOCAL_NODE_MODULES : PRESERVED
The complete intended staged tree — including those 526 Git deletions — was then fully validated.
However, the local commit helper deliberately protects node_modules/.
During the actual commit it reported:
Protected paths were staged and have been removed from the commit
for the node_modules/** deletion entries.
The resulting commit summary was:
16 files changed, 1515 insertions(+), 54 deletions(-)
Therefore:
TLS/security changes COMMITTED
mbweb manifests COMMITTED
security tests COMMITTED
security audit changes COMMITTED
node_modules Git purge VALIDATED, BUT NOT INCLUDED
This does not affect the live runtime. It is a repository-hygiene follow-up that must be completed before MB696 is marked completely closed.
The intended MB696 final tree passed all major gates.
12/12 files
145/145 tests
PASS
2/2 files
19/19 tests
PASS
333/333 files
6137/6137 tests
PASS
793/793 files
16034/16034 tests
PASS
Verdict: GO — all 14 security invariants hold.
GITLEAKS_FINDINGS=0
RC_GITLEAKS=0
The final validation observed unchanged processes:
DEV
3819132 -> 3819132
Undernet
3815619 -> 3815619
mbweb
228188 -> 228188
No database write occurred. No service restart occurred.
The security hardening was therefore validated without disturbing live application state.
fd44628.gitignore
CHANGELOG.md
VERSION
Mediabot/External.pm
Mediabot/External/News.pm
Mediabot/External/YouTube.pm
Mediabot/Helpers.pm
contrib/mbweb/package.json
contrib/mbweb/package-lock.json
t/cases/138_helpers_getversion_no_curl_shell.t
t/cases/139_helpers_whereis_no_curl_process.t
t/cases/207_helpers_http_guard_regression.t
t/cases/820_mb640_version_check_never_silent.t
t/cases/909_mb696_tls_secure_default.t
t/cases/910_mb696_node_dependency_hygiene.t
tools/security_audit.pl
Commit:
fd44628
🛡️ Ward the Network Gates and Clear the Forgotten Node Vault
Version:
3.4dev-20260823_142740
Push:
a191682..fd44628
master -> master
MB696 changes something more important than a visible IRC command.
It changes the direction of the project’s defaults.
Before:
secure HTTPS was possible, but callers sometimes had to remember to request it.
Now:
secure HTTPS is the normal behavior, and weakening it requires an explicit and reviewable decision.
Likewise, mbweb’s dependency model is no longer something that has to be reverse-engineered from an old node_modules directory.
The canonical dependency contract is identified, reproducible and represented by its proper manifest and lockfile.
There is one small repository cleanup still to seal because the local commit safety guard correctly refused to manipulate a protected path automatically.
Once that deliberate cleanup is landed, MB696 can be closed.
The castle did not learn a flashy new spell this time.
It got stronger locks, fewer forgotten keys, and a much clearer map of the hidden passages. 🛡️🗝️🪄
Immediate:
finish the root node_modules Git-index cleanup
verify the historical Twitter/X credentials are revoked
close MB696
Then:
MB697 — runtime cleanup
MB698 — documentation and release contract
MB699 — final readiness report
A fresh Debian 13 manual verification remains useful but lower priority.
And, as before:
3.5only happens after an explicit GO.
You must be logged in to reply.