Author: dwolven

  • Bills 41, Lions 31: The Defense Is the Story Two Weeks Running

    Same result as last week in one sense: another close-ish final score. Everything else about how it felt was worse.

    Detroit went into Buffalo already missing pieces up front, with two starting offensive linemen out and both starting safeties unavailable. Josh Allen and the Bills offense made that count immediately. Buffalo scored on their first three drives and it was 21-0 before the Lions ran a single successful play that mattered. By halftime it was 27-10.

    Watching it live, the offense looked like it couldn’t decide what it wanted to be early on. Goff started spreading throws to whoever was open, then within a series or two it was back to feeding Gibbs almost by reflex, like the play-calling defaulted to its most comfortable answer instead of what the defense was actually giving up. Gibbs finished with just 52 yards on 16 carries on the ground, which tells you Buffalo had that answer scouted and Detroit kept asking the question anyway for a stretch.

    The second half was a different team. Once Goff started living through Jameson Williams, Amon-Ra St. Brown, and Sam LaPorta instead of forcing the ground game, Detroit actually outscored Buffalo 21-14 the rest of the way. St. Brown finished with 142 yards and two touchdowns, Goff ended the night 327 yards and four touchdown passes, and for about a quarter and a half it looked like a real comeback instead of garbage-time stat padding.

    Then the defense gave up 41 points, for the second straight week after allowing 30 to the Saints. Two straight games of that is a pattern, not a blip. And it’s happening with the two starting safeties already out and the offensive line already missing two starters, which is exactly the kind of situation where thin depth turns a bad week into a genuinely ugly one. Allen finished with 317 total yards and five total touchdowns and the Bills converted 9 of 12 third downs. Detroit’s defense was beaten and gassed, on the field almost the entire night.

    Final: Bills 41, Lions 31. A game the offense nearly stole back in the fourth quarter, and a defense that made that theft necessary in the first place.

    Jets come to Ford Field next. Plenty of time to figure out who’s walking through that door to help the back end of this defense, though at this point I’m not holding my breath.

  • Lions 31, Saints 30: A Win That Felt Like a Loss

    Full disclosure up front: I’m not a football theory guy. I couldn’t diagram a cover-2 if you paid me. I watch as a casual fan who likes the Lions, yells at the TV a little, and moves on with my day. So take these as impressions, not analysis.

    Sunday’s game against the Saints was a weird one to sit through, because Detroit won 31-30 and it still didn’t feel like a win.

    The first half and change went about how you’d hope. Detroit built a 21-0 lead through the third quarter on two Jahmyr Gibbs rushing touchdowns and a Jared Goff touchdown pass to Amon-Ra St. Brown, and the Saints hadn’t put a point on the board yet. This is the part of the game I actually enjoyed watching.

    Then it fell apart. New Orleans scored on four straight possessions, a touchdown, another touchdown, a field goal, and a third touchdown, to erase the entire lead and tie the game at 24 with just over a minute left in the fourth quarter. A 21-0 lead turned into a tie game that fast. Their quarterback finished with 410 passing yards on the day, which is a number that should not be possible against a defense that also racked up five sacks and two interceptions. Both things happened in the same game.

    Detroit did answer. Goff found St. Brown again for a short touchdown to make it 31-24 with barely a minute on the clock, which should have sealed it. Instead the Saints marched right back down and scored again to make it 31-30, then went for the two-point conversion and the win instead of the extra point and overtime. The pass fell incomplete, and that’s the game.

    So Detroit wins by one point after giving up 30 unanswered and needing the Saints to miss a two-point try to close it out. The Lions also lost the total yardage battle, 369 to 469, and Detroit held the ball for five more minutes than New Orleans did while still surrendering nearly 500 yards. None of that shows up in the final score, but it’s exactly why the game felt like it did.

    I know a win is a win, and I’m sure there are Lions fans out there right now telling me to stop complaining about a victory. Fair. But if you were watching live, you know exactly the feeling I mean: that slow drain where a comfortable laugher turns into a nail-biter you didn’t ask for, and by the end you’re relieved more than happy.

    On to Buffalo tonight.

  • News Roundup: September 7–16, 2026

    A heavier week than the last one. The MikroTik router chain I flagged in the last roundup turned out to be a live zero-day, a popular WordPress events plugin picked up a 9.8 RCE, and Cisco dropped another advisory batch.

    • MikroTik’s “no details yet” RouterOS release from the last roundup turned out to be actively exploited. CERT Polska disclosed the chain, dubbed MikroTrick: CVE-2026-67276 (SSH auth bypass, CVSS 9.2) combined with CVE-2026-86060 (privilege escalation) gives an unauthenticated attacker full admin on any RouterOS device with SSH reachable from the internet. Exploitation reportedly started September 2, a day before the patch shipped, so this was a zero-day in practice. CISA added both CVEs to its KEV catalog on September 10, and Shadowserver counted roughly 122,500 internet-exposed RouterOS SSH endpoints as of September 5. If you haven’t patched to 7.24.2, 7.23.4, or 6.49.21 yet, do that now, then check for a “Flagged” device status and an unexpected user named something like “ops” before assuming you’re clean.
    • The Events Calendar plugin patched two critical code-injection bugs. CVE-2026-78159 (CVSS 9.8) is an unauthenticated code injection from insufficient validation; a second flaw, CVE-2026-78006, is also critical. Both need comments enabled on the plugin to be exploitable. Fixed in 6.17.3.1, but the plugin has over 600,000 active installs and download data suggests roughly half may still be sitting on a vulnerable version.
    • Cisco’s September 16 advisory batch covers hardening releases for BroadWorks CommPilot, Identity Services Engine, Nexus Dashboard, ThousandEyes Virtual Appliance, and a combined release for all three Secure Firewall products (ASA, FMC, and FTD). Worth calling out separately: the same batch includes a Secure Email Gateway hardening release with a SQL injection flaw that Cisco says is already being exploited, which bumps it above the usual “patch when convenient” priority.
    • WordPress 7.1.1 is still on track as a bug-fix-only release for tomorrow, September 17, covering 16 core tickets and 22 Gutenberg pull requests. No security content in this one, but if anything you run leans on responsive styles, the Icon Registration API, or the iframed editor, it’s worth a spin on staging before it lands.
  • Theming the site: a dark palette, a read-only wall, and two follow-up fixes

    Theming the site: a dark palette, a read-only wall, and two follow-up fixes

    Two posts in, this site was still using WordPress defaults with zero customization. Given the subject matter, that felt like an oversight. So the next request was simple: restyle the theme, pick a direction based on the site’s own content, and make it happen. It mostly worked. The interesting part was where “mostly” showed up.

    The direction

    Twenty Twenty-Five, the active theme, is a block theme, which means colors, typography, and spacing all live in a theme.json file rather than scattered across a Customizer panel. Given the content here is server racks, networking gear, and ISP work, the natural direction was something dark and technical rather than another white background with black text: deep slate, off-white body copy, a signal-green accent, and monospace for anything code-related. Status-light green felt more honest to the subject matter than a generic blue.

    Read access, no write access

    Here is where it got interesting. The MCP plugin exposes a tool to read the site’s current global styles, but nothing to write them. The wp_global_styles object isn’t a normal post either. Its REST endpoint expects a JSON body with top-level styles and settings keys, not the title and content fields every other post type uses, so even the generic custom-post-type update tool couldn’t touch it. Forcing a write through a mismatched schema on a live site was a bad trade for saving a few minutes, so instead of guessing, the actual theme.json got handed over as text to paste in directly.

    That meant a child theme rather than an edit to Twenty Twenty-Five itself, so a future core update to the parent theme wouldn’t wipe out the customization. Two small files, a folder created over SSH, activate from the dashboard. From there it was a normal back and forth: apply the palette, load the site, screenshot it, spot what needed adjusting.

    What needed a second pass

    The first version looked right at a glance but missed two things once actually rendered:

    • Inline code, the kind that shows up mid-sentence, wasn’t styled at all. Only the block-level code element had a rule, so a word like xfs_growfs in a sentence just inherited body text. Needed its own entry in the theme.json elements block.
    • Nav links stayed off-white instead of picking up the accent color. Block themes sometimes save color settings directly on a template part through the Site Editor, which can override theme.json for that one instance. Adding an explicit core/navigation block style to the file fixed it without needing to touch the Site Editor at all.

    Both went back into the same theme.json, pasted in once, confirmed live with a screenshot afterward rather than assumed. Inline code now has a dark background with green monospace text, matching the block-level code it sits next to, and the nav bar is green across the board.

    Where that leaves things

    The gap between what a plugin can read and what it can write is worth remembering. It’s tempting to assume access implies control, but here the honest answer was to say so and hand off a copy-paste step rather than force a fix that might have corrupted live styling. The site now actually looks like it belongs to an ISP nerd instead of a stock WordPress install, and the process for getting there, read what exists, design deliberately, verify by looking rather than assuming, is the part worth keeping around for next time.

  • Getting Codex connected to WordPress through MCP

    I finally got Codex talking to this WordPress site through MCP. The short version: the local config was close, the WordPress endpoint itself was fine, but the thing that actually made the tools show up here was setting it up through the web interface as a plugin and then reloading the client.

    What did not work at first

    I started with a local MCP server entry in the Codex config pointing at this site’s Easy MCP AI endpoint:

    [mcp_servers.wordpress]
    enabled = true
    url = "https://example.com/wp-json/easy-mcp-ai/v1/mcp"
    bearer_token_env_var = "WORDPRESS_MCP_TOKEN"

    That looked right on disk, but it did not make the WordPress tools appear in the running chat. The config was present, the server was enabled, and the endpoint URL was correct, but Codex’s live tool list still only showed the built-in app/plugin resources. In other words: configured is not the same thing as loaded.

    The token/header trap

    The other easy mistake was treating the token name as if it were the header name. Putting a value under a header called WORDPRESS_MCP_TOKEN is not the same thing as setting an environment variable named WORDPRESS_MCP_TOKEN, and it is not the same thing as sending Authorization: Bearer ....

    When I tested the WordPress endpoint directly with a bearer token, the MCP initialize request came back clean. The server identified itself as easy-mcp-ai and advertised tools/resources for managing posts, pages, media, comments, users, and site settings. So the WordPress side was alive. The problem was getting Codex to expose it as an actual callable tool in the session.

    What finally worked

    The working path was to add it through the web interface as a plugin, then reload the Codex client and resume the chat. After that, the WordPress MCP namespace appeared immediately, with tools like:

    • wp_list_posts
    • wp_get_post
    • wp_create_post
    • wp_update_post
    • wp_list_media
    • wp_get_site_settings

    The first real verification was intentionally boring: list one recent post. That returned a scheduled post and proved the connection was not just installed-looking, but actually usable.

    The practical checklist

    • Install and configure the Easy MCP AI plugin on WordPress.
    • Generate a token from the WordPress side.
    • Add the MCP endpoint through the web/plugin interface, not just local config, if you want it available inside this Codex session model.
    • Reload the Codex client after adding it.
    • Verify with a read-only call first, like listing posts or reading site settings.

    That last bit matters. A connector can be present in a settings screen, reachable over HTTPS, and still not be available to the model as a tool. The only test that counts is whether the WordPress tools actually show up and a harmless read works.

    Net result: this post was created from Codex through the WordPress MCP plugin, which is probably the cleanest possible proof that the setup is working.

  • Adding images to the pipeline: an image connector, MCP, and one unverified email

    Adding images to the pipeline: an image connector, MCP, and one unverified email

    The last post covered getting Claude write access to this site through an MCP plugin. The next obvious gap was images: every post here has been text-only, mostly because I never bothered setting up a source for them. An image-search service has an MCP connector for exactly this, so I hooked it up. It did not go straight through.

    Connected, but not really

    The connector showed as installed almost immediately, but calling it just returned a generic could not connect error. No useful detail, just a dead end. Turned out the OAuth handshake was stalling because my account’s email had never been verified. Not something the connector UI surfaced. I confirmed the email, reauthorized, and the connector came back with an actual checkmark this time.

    First real run

    With the connector live, the tools showed up as expected: search photos, search illustrations, search collections, search users. I searched for something generic and homelab-appropriate, a server rack, and picked a result under a standard license rather than one of the entries demanding a specific attribution link back to the photographer’s site.

    Pulling it into the media library with the upload-from-url tool failed on the first attempt: file type not allowed. The service’s image URLs do not end in a file extension, they are just an ID with a query string, and the upload tool appears to infer file type from the URL path rather than the actual content type. Passing an explicit filename with a .jpg extension fixed it immediately. Second attempt uploaded clean, alt text and all.

    Where that leaves things

    Small saga, but a real one: a connector that looked broken for an infrastructure reason that had nothing to do with MCP at all, and an upload tool that needed a nudge on file naming to work with an external image host. Both are now filed away as known steps rather than mysteries. Image sourcing is officially part of the pipeline.

  • Tomb Raider King: Nine Episodes In Verdict

    Picked this one up mostly on the “not much else airing” logic again. It’s the one everyone’s calling Solo Leveling-adjacent: regression, revenge, magic relics, a protagonist who already knows how the next fight goes. Nine episodes in (three to go, finale’s the 23rd), here’s where I’ve landed. Watched dubbed, for what it’s worth.

    The Solo Leveling comparison. It’s uncanny how much this feels like Solo Leveling, and I can’t fully put my finger on why. The systems are different (relics vs. leveling), the setting’s different. But something about it lands the same, and my best guess is it’s the protagonist: same flavor of quiet, already-knows-how-this-goes overpowered lead who the show builds everything else around. Once you notice it you can’t unnotice it.

    The premise. Jooheon dies in the present, gets sent back to before “Tombs” and “Relics” started popping up worldwide, and spends his second run stacking advantages he shouldn’t have yet. It’s a clean enough hook on paper. The show just doesn’t do much with it beyond “he already knows, so he wins.”

    The Jooheon problem. This is the actual issue, not the premise. He’s not an underdog with foreknowledge, he’s just better than everyone at everything, and his stated motivation is closer to “be pettier than my past self” than anything resembling a goal. There’s no fight in this show where I’ve believed for a second he could lose. Once or twice that’s fine. Nine episodes of it gets old.

    Animation. Studio EEK is competent but not doing anything that’ll stick with me. Fights are readable, nothing more. Not a Heavy Knight-tier visual selling point here, it’s coasting on the source material’s popularity, not the craft.

    The dub. Cast is solid enough, but there’s one genuinely annoying quirk: the glowing status-screen pop-ups (the game-like ability notifications Jooheon interacts with constantly) don’t get translated into English. So even watching dubbed, you’re stuck flipping subtitles on anyway whenever one of those matters. A couple of reviews also flag stiff facial animation and some inconsistent censorship as ongoing issues, for what it’s worth.

    Worldbuilding. Genuinely the more interesting part, when it bothers. The relic lore (the Crow being a betrayed god-relic, the whole “abuser of relics” angle) is more compelling than the revenge plot wrapped around it. I’d almost rather watch the show about the relics than the show about Jooheon.

    Where it’s landed for me: comfortable background watching, not appointment viewing. I’ll finish it since it’s short and there’s nothing else on, but I’m not champing at the bit for episode 10.

  • News Roundup: August 27–September 6, 2026

    A quieter week than the last one, but still a few things worth patching for: a fresh WordPress migration-plugin bug with a genuinely clever exploit chain, a MikroTik security release that’s light on details, and a heavy batch of critical Cisco advisories.

    • All-in-One WP Migration and Backup patches a second-order SQL injection (CVE-2026-19949, CVSS 8.8) affecting over 3 million sites. The exploit chain is worth knowing about even if you don’t run this plugin: an attacker submits two trackbacks containing a trailing backslash and a payload URL, which the plugin fails to sanitize; once an admin exports and re-imports a site archive, that unsanitized input gets promoted into executable SQL, leaking the plugin’s secret key into a public comment. From there, an unauthenticated attacker can import a crafted archive containing a malicious must-use plugin for full RCE. Multi-step, but entirely unauthenticated end to end.
    • MikroTik shipped RouterOS 7.24.2 (stable/testing), 7.23.4 (long-term), and 6.49.21 (legacy) on September 3 as “important security updates,” deliberately withholding vulnerability details for now to give admins time to patch. Worth noting: this release adds a “Flagged” device status that RouterOS will set automatically if it detects your device has already been compromised, check your Log for it after updating. Regardless of what the underlying bug turns out to be, this is a good week to confirm Winbox, SSH, and HTTP management access are firewalled to trusted hosts only.
    • Cisco’s September 2 advisory batch included two critical (CVSS 9.8) issues worth flagging if you’re running the affected gear: an IOS XR security hardening release covering seven CVEs, and a Nexus 9000 Series Silicon One remote code execution bug (CVE-2026-20212). Neither is reported as exploited in the wild yet, but a 9.8 on core routing/switching platforms isn’t one to defer.
    • WordPress 7.1.1 is scheduled as a bug-fix-only maintenance release for September 17, with RC1 landing September 10. No security fixes listed so far, so it’s a lower-priority update than the plugin issue above, but worth having on the calendar for the current WP 7.1 stack.
  • Turning Claude loose on this WordPress site with MCP

    Turning Claude loose on this WordPress site with MCP

    This site has 17 posts going back to 2021, most of them stale in one way or another. I’ve been meaning to clean it up forever and never did, mostly because “go read every old post and check if the commands still work” is exactly the kind of task I’ll put off indefinitely. So instead I hooked Claude up to the site via an MCP-enabling WordPress plugin and had it do the pass. Here’s what that actually looked like.

    What the connector exposes

    The plugin wraps the WP REST API as a set of MCP tools: create/read/update posts and pages, media uploads, categories and tags, users, comments, custom post types, block templates, the works. A few things about it were worth noting up front:

    • New posts default to draft status unless you explicitly ask for publish (and the plugin has a setting to force draft regardless, ignoring the model).
    • Every write goes through an audit log with before/after diffs, queryable by tool, user, or date range.
    • There’s a search-and-replace tool that edits one field of a post without resending the whole thing, which matters once posts have any real length to them.

    None of that is exotic, but it’s the difference between “AI with a REST API key” and something I was actually comfortable pointing at a live site.

    The first pass: just look at everything

    First thing was just inventory: list every post regardless of status, sort by last modified, get a feel for what’s actually here. That surfaced the two abandoned drafts, the real publish/draft counts, and one post with a modified date of today that I hadn’t touched. More on that below.

    Before touching anything live, I had it walk me through what “update the outdated ones” would actually mean in practice, and we settled on: publish updates as it goes, flag anything major instead of quietly deciding on its own, batch a few posts per pass instead of one at a time. That took about thirty seconds and saved a bunch of back and forth later.

    The bug hunting was the actually useful part

    I expected this to mostly be “note that Ubuntu 20.04 is EOL” busywork. Some of it was. But going through post by post also turned up things a simple find-and-replace never would have:

    • A post titled “ext4 partition resize” that actually used xfs_growfs, an XFS-only command, in the example. Wrong filesystem entirely.
    • A typo in a Perl JSON example (Dumepr instead of Dumper) that would have just thrown an error for anyone who copy-pasted it.
    • An InfluxDB install post pinned to a repo path and package name that don’t exist anymore — InfluxDB’s on 3.x now, not 1.x.
    • A Netplan example using gateway4, which still works but is deprecated in favor of a routes: block.

    Then there was the image problem. A couple of posts had images pointing at an internal IP address that obviously doesn’t resolve from the outside — that one was a clear miss on the initial pass, since it was flagged but not fixed. When I asked to go fix it, checking the media library against what was actually in the post content turned up more of the same pattern in another post that had looked fine at a glance: right domain, wrong protocol (http instead of https), on three separate images. That one hadn’t tripped any obvious “this looks broken” signal on the first read-through. It only showed up because the correction pass checked the actual media library records against what the posts referenced, instead of trusting the URLs looked plausible.

    The mystery edit

    One post showed a modified timestamp from minutes before the session even started. Instead of guessing, the audit log answered it directly: same OAuth client, a wp_update_post call, timestamped two minutes before the first tool call in this conversation. Almost certainly a separate session on my end, not a mystery. Being able to just check that instead of speculating about it is exactly the kind of thing the audit trail is for.

    Where I still drew a line

    Not everything got auto-corrected. The two genuinely stale drafts (a Wireguard setup and a Radarr install guide, both built around dependency situations that don’t exist anymore) needed real rewrites, not patches, so those got flagged and reworked from scratch rather than papered over. And a couple of posts got an editor’s note added rather than a rewrite, because I’d rather flag “this external tool has a history of going down” than assert something I can’t fully verify.

    Net result: a handful of real bugs fixed, several posts with honest editor’s notes instead of silently stale info, and a repeatable process for the next time this backlog builds up. Given this site is mostly an excuse to tinker with self-hosting and automation in the first place, that last part is the actual point.

  • XGS-PON vs GPON: What Actually Changed

    XGS-PON vs GPON: What Actually Changed

    If you’ve spent any time around FTTH deployments, you’ve heard both terms thrown around like they’re interchangeable steps on the same ladder. They’re related, but the differences matter for anyone speccing an OLT/ONU deployment or trying to explain to a customer why “fiber” doesn’t mean one fixed speed. Here’s the breakdown, grounded in the actual ITU-T recommendations rather than marketing copy.

    GPON: ITU-T G.984

    GPON (Gigabit-capable Passive Optical Network) is defined by the ITU-T G.984 series, first standardized in 2003. It’s asymmetric:

    • Downstream: 2.488 Gbps
    • Upstream: 1.244 Gbps
    • Wavelengths: 1490 nm downstream, 1310 nm upstream
    • Encapsulation: GEM (GPON Encapsulation Method)
    • Typical split ratio: 1:32, with 1:64 supported in later profiles
    • Reach: up to 20 km. ITU-T defines several optical budget classes (A, B, B+, C, C+), ranging from 5 dB up to 32 dB depending on class; B+ (13–28 dB) is the most commonly deployed

    GPON has been the dominant residential FTTH standard for close to two decades, and it’s still what most ONTs in the field are running.

    XGS-PON: ITU-T G.9807.1

    XGS-PON (10 Gigabit-capable Symmetric PON) is the direct evolution, standardized under ITU-T G.9807.1, approved in 2016. The “S” is the whole point: unlike the earlier asymmetric XG-PON1 (G.987, 10 Gbps down / 2.5 Gbps up), XGS-PON is fully symmetric:

    • Downstream: 10 Gbps
    • Upstream: 10 Gbps
    • Wavelengths: 1577 nm downstream, 1270 nm upstream
    • Encapsulation: XGEM
    • Typical split ratio: 1:64, extending to 1:256 in some deployments
    • Reach: 20 km physical reach in the base standard (up to 60 km logical/differential distance); longer physical reach (up to 40 km) requires the separate G.9807.2 reach-extension recommendation

    The wavelength choices aren’t arbitrary. GPON and XGS-PON were deliberately assigned non-overlapping bands so that both systems can run on the same outside plant simultaneously, using WDM coexistence elements at the OLT and passive filters at the ONU. That’s the mechanism that lets an operator light a GPON and an XGS-PON service off the same PON splitter without touching the fiber plant. G.984.5 defines the enhancement band reservations that make this coexistence possible, and it gets updated as new PON generations are added to the stack.

    Why It’s Not Just “Faster GPON”

    A few things get lost when this is summarized as “XGS-PON is 4x the speed”:

    It’s symmetric, GPON isn’t. GPON’s upstream is barely half its downstream. That’s fine for residential browsing and streaming, but it’s a real constraint for anything upload-heavy — backup traffic, cloud workloads, business customers pushing data outbound. XGS-PON removes that asymmetry entirely.

    FEC is mandatory, not optional. XGS-PON specifies forward error correction as a baseline requirement to hit its higher line rates reliably over the same class of optical budget GPON uses. This is part of why XGS-PON can extend split ratios and reach without a proportional jump in optical launch power.

    It’s designed to coexist, not replace overnight. Because XGS-PON was built to share ODN infrastructure with GPON via WDM, an operator can overbuild an XGS-PON overlay onto existing GPON splitters and migrate subscribers ONT-by-ONT rather than doing a fork-lift upgrade of the outside plant. That’s a meaningfully different migration story than earlier PON generation jumps.

    Practical Takeaways

    • If you’re still running GPON at scale, you don’t need to panic-migrate. It’s a mature, well-understood standard with an enormous installed base of ONT hardware.
    • If you’re planning new builds or serving upload-sensitive customers (small business, anyone doing real cloud backup), XGS-PON is the sane default now — the ONU cost premium has come down enough that it’s not the barrier it was five years ago.
    • Coexistence means the two aren’t mutually exclusive on the same PON. You can run both off one OLT chassis with the right optics and splitter plant, which is usually how operators actually do the transition.

    The short version: GPON and XGS-PON aren’t “old” and “new” versions of the same thing so much as they’re two standards deliberately engineered to run side by side on the same glass, with XGS-PON picking up the symmetric bandwidth and split-ratio headroom that GPON’s asymmetric design never had room for.