Forum teuk.org

🛡️ Mediabot v3 — MB697: Fortifying the Web Ward Before 3.5

in Mediabot · started by TeuK · 1h ago

TeuK · 1h ago

Commit: 581cc7e
Version: 3.4dev-20260823_165236
Branch: master
Goal: prepare a trustworthy Mediabot 3.5 release without reopening the feature scope
Status: MB697 security/runtime baseline committed and deployed; functional mbweb review is next


Why this checkpoint exists

MB697 starts immediately after two large pre-release hardening rounds:

  • MB695 reconciled long-lived database drift and made the database upgrade path explicit and testable;
  • MB696 audited secrets and TLS behavior, moved HTTP clients to secure-by-default certificate verification, restored the real mbweb npm contract to Git, and finally removed the historical root node_modules/ vendor tree from version control.

At that point the next question was no longer “can Mediabot run?” but something closer to:

Can the parts that actually run in production — including mbweb — be reproduced, secured, cleaned up and trusted enough to become part of a stable 3.5 release?

That is the purpose of MB697.

This first MB697 checkpoint deliberately concentrates on mbweb dependency security and runtime hygiene before opening the broader functional/UX review.

No new IRC feature was added in this round.


1. First, MB696 was really closed

Before starting MB697, one leftover repository-hygiene problem from MB696 had to be finished properly.

The repository historically tracked 526 files under the root node_modules/ directory.

The intended MB696 cleanup had already been tested successfully, but the normal local commit.sh helper protects node_modules/** and therefore removed those staged deletions before creating commit fd44628.

A dedicated follow-up was therefore performed.

The operation used:

git rm -r --cached node_modules

The critical point is the --cached flag: the dependency files were removed from Git’s index, not from the local filesystem.

Validation proved this explicitly:

tracked_node_modules=0
local_files=640->640
local_node_modules=PRESERVED
mbweb_manifests=2

The dedicated repository-hygiene contract also passed:

1/1 files
9/9 tests
PASS

The cleanup was committed separately as:

7a46617 đź§ą Vanish the Vendored Node Vault from the Grimoire

with version:

3.4dev-20260823_154141

and pushed successfully to origin/master.

That closed MB696 for real:

tracked_node_modules=0
mbweb_manifests=2
master synchronized with origin/master
MB696=CLOSED

2. mbweb becomes a first-class 3.5 release concern

The roadmap was then deliberately adjusted.

Rather than treating mbweb as a peripheral contrib/ extra to be reviewed at the very end, MB697 now treats it as part of the runtime that must be reliable for 3.5.

The real architecture was established in MB696 and confirmed again in MB697:

Canonical source : contrib/mbweb/
Live runtime     : /opt/mbweb/app
systemd service  : mbweb.service
WorkingDirectory : /opt/mbweb/app
ExecStart        : /usr/bin/node /opt/mbweb/app/app.js

The canonical npm manifests are now public project files:

contrib/mbweb/package.json
contrib/mbweb/package-lock.json

The direct dependency contract is:

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 is important because it replaces a historical, ambiguous state where the repository contained a vendor tree but did not publicly expose the actual mbweb dependency manifest.


3. MB697-R1 — baseline of the live web runtime

The first MB697 pass was intentionally read-only.

It checked:

  • systemd health;
  • source/live manifest equality;
  • npm direct dependencies;
  • npm security advisories;
  • application source drift between contrib/mbweb and /opt/mbweb/app.

Service health

The live service was healthy:

MainPID=228188
NRestarts=0
ExecMainStatus=0
ActiveState=active
SubState=running

Manifest contract

Both canonical manifests matched the deployed runtime:

package.json=MATCH
package-lock.json=MATCH

Source/runtime application drift

Excluding intentional runtime state such as .env, npm manifests, .git and node_modules, the comparison found:

source_files=32
live_files=102
missing_live=0
changed=0
extra_live=70

This was a very useful result.

There was no missing source file and no changed application file.

In other words, the running mbweb application code matched the canonical source exactly.

The only divergence consisted of 70 additional historical files in /opt/mbweb/app.


4. GitHub/Dependabot immediately exposed two npm advisories

Once the canonical mbweb manifests were committed publicly, GitHub reported two dependency vulnerabilities on the default branch:

1 moderate
1 low

A local npm audit --omit=dev reproduced the same findings:

critical:0
high:0
moderate:1
low:1
info:0

The affected packages were both transitive, not direct application dependencies.

body-parser

Observed version:

body-parser=2.2.2

Severity:

low

Advisory summary:

body-parser vulnerable to denial of service when an invalid limit value
silently disables size enforcement

qs

Observed version:

qs=6.15.1

Severity:

moderate

Advisory summary:

qs has a remotely triggerable DoS involving comma-format arrays when
encodeValuesOnly is enabled and null/undefined entries are encountered

Both advisories reported:

fixAvailable=True

For a 3.5 release candidate, leaving known fixable web-stack DoS advisories in the canonical lockfile would have been unnecessary risk.


5. No blind npm audit fix on the real runtime

The remediation was rehearsed first in a disposable /tmp directory.

Only copies of:

package.json
package-lock.json

were used.

The temporary operation was:

npm audit fix --package-lock-only --ignore-scripts

The result was clean and minimal.

Before:

express=5.2.1
body-parser=2.2.2
qs=6.15.1

After:

express=5.2.1
body-parser=2.3.0
qs=6.15.3

The direct Express version did not move.

The application manifest did not change.

Only the dependency lock needed refreshing.

The temporary audit then reported:

critical:0
high:0
moderate:0
low:0

6. Lockfile cleanup also removed stale express-rate-limit residue

The lockfile delta revealed another useful cleanup.

The canonical application does not declare express-rate-limit as a direct dependency and no current mbweb source file uses it.

The old lock still contained a stale entry:

express-rate-limit 7.5.1

After the controlled lock refresh:

express-rate-limit=ABSENT

This is preferable to preserving a package simply because it happened to exist in an old installation state.

The lockfile is now more accurately derived from the real mbweb dependency contract.


7. Exact dependency changes

The audited lock refresh changed the following dependency nodes:

body-parser       2.2.2 -> 2.3.0
qs                6.15.1 -> 6.15.3
es-object-atoms   1.1.1 -> 1.1.2
hasown            2.0.3 -> 2.0.4
side-channel      1.1.0 -> 1.1.1
type-is           2.0.1 -> 2.1.0

and introduced newer nested content-type nodes where required by the dependency tree.

The stale lock entry:

express-rate-limit 7.5.1

was removed.

The direct mbweb dependency declaration itself remained unchanged.


8. Clean install rehearsal

A lockfile update is not sufficient evidence by itself.

The canonical manifest and refreshed lockfile were copied into a clean temporary directory and installed with:

npm ci --omit=dev --ignore-scripts

Result:

RC_NPM_CI=0

The clean installation resolved the expected direct dependencies:

bcryptjs=2.4.3
dotenv=16.6.1
express=5.2.1
express-session=1.19.0
helmet=8.1.0
mysql2=3.22.2

This is an important release property:

mbweb can now be reconstructed from the public canonical manifest and lockfile rather than from an old working directory.


9. A new regression contract: test 911

MB697 adds:

t/cases/911_mb697_mbweb_dependency_security.t

Its purpose is to make the security cleanup durable.

The test verifies at least the following contract:

body-parser >= 2.3.0
qs          >= 6.15.2
express-rate-limit absent from canonical lock

This converts a one-time manual npm audit fix into a regression-protected project rule.

Together with MB696’s Node hygiene test:

t/cases/910_mb696_node_dependency_hygiene.t

the project now protects both sides of the dependency contract:

  • do not vendor root node_modules/ into Git;
  • do publish canonical mbweb manifests;
  • do not regress into the audited vulnerable dependency ranges.

Focused validation:

2/2 files
12/12 tests
PASS

10. The live runtime contained 70 historical files

The source/live comparison found no application-code drift but did find exactly 70 extra files in /opt/mbweb/app.

They were classified as:

env_backup=2
bak=67
example=1
other=0

That classification matters.

There were no unknown live-only application files that needed to be reverse-engineered back into contrib/mbweb.

The extras were maintenance residue.

Examples included historical copies such as:

app.js.bak.*
lib/config.js.bak.*
lib/dashboardData.js.bak.*
lib/mediabotRepository.js.bak.*

and two .env.bak* files.


11. One old .env backup had unsafe permissions

The content of .env and .env.bak* was deliberately not displayed during the audit.

Only metadata was inspected.

Observed permissions included:

-rw------- mediabot:mediabot .env
-rw------- mediabot:mediabot .env.bak.runtime-json-run-path.20260510-034029
-rw-r--r-- mediabot:mediabot .env.bak.20260424-021451

The last historical backup was world-readable.

Even though mbweb only serves explicitly configured public directories and not the application root, keeping historical environment backups with broad local read permissions is poor release hygiene.

For 3.5, old .env backup material should not accumulate inside the live application tree.


12. The cleanup was backed up privately before deletion

Nothing was deleted blindly.

Before touching the 70 extra runtime files, they were archived privately to:

/home/mediabot/backups/mb697_mbweb_before_20260823_164350/runtime-debris.tar.gz

The backup directory was restricted and the archive was made private.

Archive SHA-256:

28ba71b5dcf355588d080c5efbdf720fc7e8cee8213bad11f1e3fee1586f5781

The canonical runtime manifests were also backed up before deployment.

No .env value or historical secret was printed into the terminal transcript.


13. Controlled live dependency deployment

After the rehearsal succeeded, the canonical refreshed lockfile was deployed to:

/opt/mbweb/app/package-lock.json

The service was stopped deliberately for the dependency replacement.

A clean production install then ran with:

npm ci \
    --omit=dev \
    --ignore-scripts \
    --no-audit \
    --no-fund

The install succeeded:

RC_INSTALL=0
ROLLBACK=NO

The rollback path was prepared but never needed.


14. mbweb restarted cleanly

Before deployment:

MainPID=228188
NRestarts=0

After controlled restart:

MainPID=3856992
NRestarts=0
ActiveState=active

The PID change is expected because this was an intentional stop/start cycle.

The important part is:

NRestarts=0

There was no systemd crash/restart loop.

The Node listener was detected on:

127.0.0.1:4002

A request to / returned:

HTTP 404

That is not considered a health failure here: it proves that the Node process is listening and answering HTTP, while / is not necessarily the application’s configured base route.

A route-aware health check belongs to the upcoming functional mbweb pass.


15. Live npm security state after deployment

The deployed runtime was audited again after npm ci.

Result:

critical:0
high:0
moderate:0
low:0
RC_NPM_AUDIT=0

npm ls --omit=dev --depth=0 also returned cleanly:

RC_NPM_LS=0

The source and live npm contracts remained identical:

package_json=MATCH
package_lock=MATCH

16. Historical runtime debris removed

Only after all of the following were true:

backup PASS
npm install PASS
service active
HTTP listener responding
npm audit clean
npm ls clean
package.json MATCH
package-lock.json MATCH

were the 70 historical extra files removed from /opt/mbweb/app.

Result:

removed=70
RC_CLEAN=0

The live .env was retained and restricted to private permissions.


17. Final source/live drift: zero

After cleanup, the same application-tree comparison was run again.

Result:

missing=0
extra=0
changed=0

This is one of the strongest results of the entire round.

Outside intentional runtime state such as:

.env
node_modules/
package manifests handled explicitly

the deployed mbweb application tree is now identical to the canonical source tree.

That makes future deployment, rollback and debugging much less ambiguous.


18. Final targeted proof before commit

The relevant Node/repository security contracts passed again after the live deployment:

2/2 files
12/12 tests
PASS

The source tree contained exactly the intended changes:

M  CHANGELOG.md
M  contrib/mbweb/package-lock.json
A  t/cases/911_mb697_mbweb_dependency_security.t

No unrelated source modification entered this checkpoint.


19. Commit

The mbweb dependency/runtime hardening was committed as:

581cc7e 🛡️ Fortify the Web Ward and Sweep the Runtime Cobwebs

The version advanced to:

3.4dev-20260823_165236

Commit scope:

CHANGELOG.md
VERSION
contrib/mbweb/package-lock.json
t/cases/911_mb697_mbweb_dependency_security.t

Summary:

4 files changed
171 insertions
49 deletions

The commit was pushed successfully:

7a46617..581cc7e  master -> master

20. Dependabot branches appeared during the commit

During the final fetch, GitHub exposed two new Dependabot branches:

dependabot/npm_and_yarn/contrib/mbweb/body-parser-2.3.0
dependabot/npm_and_yarn/contrib/mbweb/qs-6.15.3

Those branch targets correspond exactly to the versions already integrated manually and validated in this MB697 round:

body-parser=2.3.0
qs=6.15.3

The push output still displayed GitHub’s previous:

1 moderate
1 low

vulnerability summary immediately after the push.

The local canonical lockfile and the deployed runtime both report:

npm audit = 0 vulnerabilities

So the remaining GitHub banner should be treated as pending repository-side refresh until verified, not as evidence that the local remediation failed.

The next release-hardening pass should re-check the GitHub/Dependabot state before calling this point administratively closed.


21. Current verified source state from the fresh snapshot

A fresh private project snapshot taken after this work reports:

VERSION=3.4dev-20260823_165236

and contains:

contrib/mbweb/package-lock.json
t/cases/911_mb697_mbweb_dependency_security.t
CHANGELOG entry: ### mb697 — mbweb dependency and runtime hardening

The lockfile in that snapshot resolves:

express=5.2.1
body-parser=2.3.0
qs=6.15.3
express-rate-limit=ABSENT

This confirms that the checkpoint is present in the current source snapshot, not merely in an earlier terminal state.


22. Where Mediabot now stands on the road to 3.5

The pre-release roadmap is now deliberately narrow.

MB695        database compatibility / drift        CLOSED
MB696        secrets / TLS / Git hygiene           CLOSED
MB697        runtime + mbweb stabilization          IN PROGRESS
MB698        install / upgrade / rollback           NEXT LATER
MB699        3.5 release-candidate qualification    FINAL GATE
3.5                                                   EXPLICIT GO ONLY

The first technical foundation of MB697 is now complete:

mbweb canonical npm contract        established
known npm advisories                remediated locally
clean npm install                   proven
live npm deployment                 proven
runtime debris                      privately archived and removed
source/live application drift       zero
systemd restart                     clean
regression test 911                 added

23. What MB697 has NOT done yet

This checkpoint is deliberately not being confused with “mbweb is finished”.

The next MB697 phase is the functional review.

That will start from real operator/user observations rather than another generic automated audit.

Items may include, depending on the actual feedback:

functional bugs
missing or confusing web actions
authentication/session behavior
dashboard usefulness
runtime diagnostics
UX inconsistencies
web/IRC state consistency
error reporting
health/readiness route
operational controls

Each observation will be classified as:

BLOCKER 3.5
BUG
SECURITY
RELIABILITY
UX / FUNCTIONAL
DOCUMENTATION
POST-3.5

The rule remains simple:

If it threatens the reliability, security, installability or credible operation of 3.5, fix it before release. If it is merely an attractive improvement, move it to the post-3.5 backlog.


24. The larger release plan

After MB697 functional/runtime stabilization:

MB698 — installation, upgrade and rollback contract

The project must prove that an administrator can actually perform:

fresh install
stable 3.3 -> 3.5 upgrade
recent 3.4dev -> 3.5 upgrade
rollback / recovery

with documented service, database and mbweb ordering.

Debian 13 fresh-install gate

A fresh Debian 13 installation is now considered a real release gate before final 3.5 qualification.

It no longer belongs in an indefinite “nice to test someday” bucket.

MB699 — 3.5 Release Candidate Qualification

At MB699, development stops.

The candidate must be challenged across:

source hygiene
secret scanning
database compatibility
migrations
Doctor
fast/full tests
DEV runtime
Undernet runtime
mbweb
Debian 12
Debian 13 fresh install
installation documentation
upgrade documentation
rollback documentation
release artifact hygiene
known residual risks

The desired final condition is:

BLOCKERS=0

Only then is a technical GO recommendation appropriate.

And even then:

the move from 3.4dev-* to stable 3.5 remains an explicit release decision.


Final note

This was not a flashy feature round.

It was the kind of work that determines whether a stable release is merely functional on the maintainer’s machine or genuinely reproducible and supportable.

mbweb now has:

  • a public canonical dependency contract;
  • a clean lockfile;
  • zero locally known production npm vulnerabilities;
  • a clean npm ci proof;
  • a live runtime rebuilt from that contract;
  • no historical backup debris mixed into application source;
  • no source/live application drift;
  • regression protection against the vulnerable dependency ranges.

The web ward is considerably less improvised than it was a few hours ago. 🛡️🕸️🪄

Next stop: the actual functional mbweb review — driven by real remarks, not theoretical polishing.

You must be logged in to reply.