<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: zowskyy</title>
    <description>The latest articles on DEV Community by zowskyy (@thewilliamboyd93oss).</description>
    <link>https://dev.to/thewilliamboyd93oss</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4032919%2F737121df-3647-421b-b27a-5a4f2ab61756.jpg</url>
      <title>DEV Community: zowskyy</title>
      <link>https://dev.to/thewilliamboyd93oss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thewilliamboyd93oss"/>
    <language>en</language>
    <item>
      <title>The Advisory Said It Was Fixed. I Didn't Believe It Until I Broke It Myself.</title>
      <dc:creator>zowskyy</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:22:08 +0000</pubDate>
      <link>https://dev.to/thewilliamboyd93oss/the-advisory-said-it-was-fixed-i-didnt-believe-it-until-i-broke-it-myself-513b</link>
      <guid>https://dev.to/thewilliamboyd93oss/the-advisory-said-it-was-fixed-i-didnt-believe-it-until-i-broke-it-myself-513b</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Update:&lt;/em&gt;&lt;/strong&gt; This investigation spawned a complete stress-testing toolkit. See the &lt;a href="https://github.com/zowskyy/apktool-diagnostics" rel="noopener noreferrer"&gt;apktool-diagnostics repo&lt;/a&gt; for the full tool with security scanning, round-trip validation, and fuzzing setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was deep into a project stress-testing apktool — the standard tool for decompiling and rebuilding Android APKs — hunting for real bugs to fix, not hypothetical ones. While digging through recent issues and advisories, I ran across a patched vulnerability: &lt;strong&gt;&lt;em&gt;CVE-2026-39973&lt;/em&gt;&lt;/strong&gt;, a path traversal bug that let a malicious &lt;strong&gt;resources.arsc&lt;/strong&gt; make apktool write decoded files outside the intended output directory. Already fixed upstream. Advisory published. Case closed, according to everyone else.&lt;/p&gt;

&lt;p&gt;I didn't believe it until I broke it myself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why "already patched" wasn't good enough&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Advisories describe what should happen. They don't prove it. And a tool like &lt;strong&gt;&lt;em&gt;apktool&lt;/em&gt;&lt;/strong&gt; has one job that matters more than any other: you point it at a file you don't trust — someone else's APK — and it processes that file on your machine. If the sanitization it depends on for that trust boundary can silently regress in a refactor (which is exactly what happened here — a PR reorganizing resource-file-path construction dropped the call to &lt;strong&gt;&lt;em&gt;BrutIO.detectPossibleDirectoryTraversal()&lt;/em&gt;&lt;/strong&gt; without anyone noticing), then "patched now" only tells you about today. It doesn't tell you the mechanism actually holds, and it doesn't tell you what "vulnerable" concretely looks like on your own machine, with your own files.&lt;/p&gt;

&lt;p&gt;So instead of writing "confirmed CVE-2026-39973, see upstream fix" and moving on, I decided to reproduce it myself, end to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building both sides of the fence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Step one:&lt;/em&gt;&lt;/strong&gt; build &lt;strong&gt;&lt;em&gt;apktool&lt;/em&gt;&lt;/strong&gt; twice from source — once at v3.0.1 (the last vulnerable tag), once at current HEAD (post-fix). Now I had a vulnerable binary and a patched binary sitting side by side, and no more reason to trust anyone's summary of what the difference actually did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two:&lt;/strong&gt; I needed a malicious input. The vulnerability lived in how &lt;strong&gt;&lt;em&gt;ResFileDecoder.java&lt;/em&gt;&lt;/strong&gt; constructs output file paths from resource type names inside the binary &lt;strong&gt;&lt;em&gt;resources.ars&lt;/em&gt;&lt;/strong&gt;c table. So I took a real APK, parsed its Type String Pool directly by hand — walking the binary chunk format byte by byte until I found it at offset 2236800, 21 entries — and overwrote one type name,&lt;strong&gt;_ interpolator_&lt;/strong&gt;, with &lt;strong&gt;&lt;em&gt;../../../etc&lt;/em&gt;&lt;/strong&gt;. Both strings were exactly 12 bytes in UTF-16, so no chunk resizing, no length fields to fix up, just a clean byte-for-byte swap. Repackaged the APK with the patched table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The moment it actually mattered&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran &lt;strong&gt;apktool d&lt;/strong&gt; against the crafted APK using the vulnerable v3.0.1 build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;18 real resource files landed three directories above the intended output folder.&lt;/em&gt;&lt;/strong&gt; Not a warning. Not a crash. A silent arbitrary-file-write, exactly as described, except now it wasn't a paragraph in an advisory — it was files sitting in &lt;strong&gt;_/home/claude/etc/ _&lt;/strong&gt;that had no business being there.&lt;/p&gt;

&lt;p&gt;Then I ran the identical malicious APK against the HEAD build. The sanitizer caught the traversal attempt on the same &lt;strong&gt;&lt;em&gt;interpolator&lt;/em&gt;&lt;/strong&gt; type name and rerouted all 18 files safely into &lt;strong&gt;&lt;em&gt;res/invalid0B/&lt;/em&gt;&lt;/strong&gt; inside the intended directory. Same input. Same code path. One version bleeds files outside the sandbox, the other doesn't.&lt;/p&gt;

&lt;p&gt;That's the whole difference between "I read that this was fixed" and "I watched it fail, then watched it not fail."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'm proud of&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not finding the CVE — I didn't find it, the advisory already existed. What I'm proud of is refusing to stop at reading it. The advisory's escalation path talks about overwriting things like &lt;strong&gt;&lt;em&gt;~/.bashrc&lt;/em&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;em&gt;~/.ssh/config&lt;/em&gt;&lt;/strong&gt; for real RCE — I didn't need to point the same technique at a real target to know it was real, because I'd already watched it write 18 files somewhere it wasn't supposed to. Redirecting the same demonstrated mechanism at a sensitive path is a difference of where, not whether, and that's exactly why I deleted the PoC APK and the patching script the moment verification was done — the writeup documents the method for transparency, since the vulnerability is already public and patched, but it isn't a ready-to-run weapon sitting in a repo somewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;What I learned&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Version-pin discipline matters more than I used to give it credit for. Every APK I'd already round-trip-tested earlier in this project turned out to sit cleanly on either side of this vulnerability window without me planning it that way — apktool 2.9.3 predates the vulnerable refactor entirely, and the HEAD build I'd compiled for unrelated testing already had the fix. Anyone frozen on 3.0.0 or 3.0.1 specifically, though, is exposed right now, running a tool whose entire purpose is processing untrusted input.&lt;br&gt;
And the bigger lesson: a security fix isn't real to me until I've watched the exploit fail against it. Reading that a sanitization call got removed in a refactor is an abstraction. Watching 18 files land in the wrong directory, then watching the same 18 files land in the right one after a version bump, is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full verification writeup:&lt;/strong&gt; &lt;a href="https://github.com/zowskyy/apktool-diagnostics/blob/main/SECURITY_FINDING_CVE-2026-39973.md" rel="noopener noreferrer"&gt;zowskyy/apktool-diagnostics — SECURITY_FINDING_CVE-2026-39973.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>A Fix That Broke Something Else — Caught Before It Shipped</title>
      <dc:creator>zowskyy</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:07:15 +0000</pubDate>
      <link>https://dev.to/thewilliamboyd93oss/a-fix-that-broke-something-else-caught-before-it-shipped-3kk6</link>
      <guid>https://dev.to/thewilliamboyd93oss/a-fix-that-broke-something-else-caught-before-it-shipped-3kk6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Project Overview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;repurpose-engine&lt;/em&gt;&lt;/strong&gt; is a single-source content repurposing tool: one blog post, transcript, or voice-memo dump goes in, and six platform-native versions come out — X, LinkedIn, Reddit, newsletter, YouTube Shorts caption, and Instagram. It's built for solo/part-time creators who don't have time to manually reformat the same idea six different ways. Scope is deliberately narrow: text transformation only, no video/audio editing, no analytics, no team features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug Fix or Performance Improvement&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stress-testing the content-validity gate (&lt;em&gt;&lt;strong&gt;is_valid_content&lt;/strong&gt;&lt;/em&gt;, the function that decides whether source text is even worth generating from) turned up three real bugs — and the most interesting part isn't any single one of them, it's that the fix for the first bug directly caused the second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Punctuation-only transcript residue passed through as valid content.&lt;/strong&gt; The original check was just &lt;strong&gt;&lt;em&gt;bool(stripped)&lt;/em&gt;&lt;/strong&gt; — any non-empty string counted as valid. Feed it a transcript that reduced to "... - -- ," after cleanup and it happily generated six platform posts out of nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix for that bug broke emoji-only content.&lt;/strong&gt; The obvious next step — reject anything with no real characters, via &lt;strong&gt;&lt;em&gt;any(c.isalnum() for c in stripped)&lt;/em&gt;&lt;/strong&gt; — correctly fixed the punctuation case. But emoji aren't alphanumeric either, so a legitimate reaction-only post ("🔥🔥🔥") started getting silently rejected as "empty."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Separately, the filler-word regex mishandled comma-attached tics.&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;\blike\b&lt;/em&gt;&lt;/strong&gt; correctly matched and removed the word "like," but left the trailing comma behind on discourse-marker usage (&lt;strong&gt;&lt;em&gt;"So, like, I think..."&lt;/em&gt;&lt;/strong&gt;), producing visible artifacts like "So,, I think..." in the cleaned output.&lt;br&gt;
**&lt;br&gt;
Code**&lt;/p&gt;

&lt;p&gt;The final, correct validity check — accepts emoji-only content, rejects punctuation-only residue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;`-def is_valid_content(text: str) -&amp;gt; bool:
&lt;span class="gd"&gt;-    """Reject empty source content before generating anything."""
-    stripped = text.strip()
-    return bool(stripped)
&lt;/span&gt;&lt;span class="gi"&gt;+def is_valid_content(text: str) -&amp;gt; bool:
+    """Reject empty/junk source content before generating anything."""
+    stripped = text.strip()
+    if not stripped:
+        return False
+    if EMOJI_RE.fullmatch(stripped):
+        return True
+    if PUNCT_ONLY_RE.match(stripped):
+        return False
+    return True`
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filler-word fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;`FILLER_PATTERNS = [
     r"\bum+\b", r"\buh+\b", r"\bahh?\b", r"\berr?\b",
     r"\byou know\b", r"\bkind of\b", r"\bsort of\b",
&lt;span class="gd"&gt;-    r"\blike\b",
&lt;/span&gt;&lt;span class="gi"&gt;+    r"\blike,\s*",
&lt;/span&gt; ]`
&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full three-commit history: &lt;a href="https://github.com/zowskyy/repurpose-engine" rel="noopener noreferrer"&gt;zowskyy/repurpose-engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/zowskyy/repurpose-engine/commit/0611a7c" rel="noopener noreferrer"&gt;&lt;strong&gt;0611a7c&lt;/strong&gt;&lt;/a&gt; — naive baseline (has both bugs)&lt;br&gt;
&lt;a href="https://github.com/zowskyy/repurpose-engine/commit/d362bb2" rel="noopener noreferrer"&gt;&lt;strong&gt;d362bb2&lt;/strong&gt;&lt;/a&gt; — fixes punctuation-only residue; introduces the emoji regression as a side effect&lt;br&gt;
&lt;a href="https://github.com/zowskyy/repurpose-engine/commit/d76f48d" rel="noopener noreferrer"&gt;&lt;strong&gt;d76f48d&lt;/strong&gt;&lt;/a&gt; — fixes the emoji regression and the filler-comma bug&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My Improvements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The interesting engineering decision here isn't the final fix — it's the order the emoji check runs in. &lt;em&gt;&lt;strong&gt;EMOJI_RE.fullmatch&lt;/strong&gt;&lt;/em&gt; is checked before &lt;em&gt;&lt;strong&gt;PUNCT_ONLY_RE.match&lt;/strong&gt;&lt;/em&gt;, not after, because emoji characters are non-word characters in regex terms &lt;strong&gt;&lt;em&gt;(\W)&lt;/em&gt;&lt;/strong&gt;, so a punctuation-only pattern like &lt;strong&gt;&lt;em&gt;^[\W&lt;/em&gt;]+$_&lt;/strong&gt; would also match a string of emoji. If the checks ran in the other order, the punctuation rule would swallow the emoji case right back into rejection — the exact bug we were trying to fix, just reintroduced by a different code path.&lt;br&gt;
I verified all three bug states empirically rather than trusting my read of the code: I ran punctuation-only and emoji-only strings through each of the three historical versions of &lt;strong&gt;&lt;em&gt;is_valid_content&lt;/em&gt;&lt;/strong&gt; (naive, alnum-only, and final) and confirmed the naive version wrongly accepts punctuation, the alnum-only version wrongly rejects emoji, and only the final version gets both right. Same for the filler regex — ran a transcript with a comma-attached "like," tic through the before and after versions and confirmed the artifact (&lt;strong&gt;&lt;em&gt;"So,,"&lt;/em&gt;&lt;/strong&gt;) only appears in the unfixed version.&lt;br&gt;
The broader lesson, consistent with the other submission I'm making for this challenge: a fix that looks obviously correct for the case that motivated it can still be wrong for a case you didn't think to test. any*&lt;em&gt;&lt;em&gt;(c.isalnum())&lt;/em&gt;&lt;/em&gt;* is a completely reasonable first instinct for "reject junk text" — it's still wrong for anything Unicode that isn't a letter or digit. The value of stress-testing isn't finding the bug you already suspect; it's finding the one your fix for that bug just created.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Two Silent Killers in an APK Round-Trip Verifier</title>
      <dc:creator>zowskyy</dc:creator>
      <pubDate>Fri, 17 Jul 2026 02:38:01 +0000</pubDate>
      <link>https://dev.to/thewilliamboyd93oss/two-silent-killers-in-an-apk-round-trip-verifier-40go</link>
      <guid>https://dev.to/thewilliamboyd93oss/two-silent-killers-in-an-apk-round-trip-verifier-40go</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Project Overview&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;apktool_diagnostics.py&lt;/strong&gt; is a diagnostic tool built on top of apktool, the standard tool for decompiling and rebuilding Android APKs. It checks whether an APK survives a decompile → rebuild round trip without silent data loss, flags known-CVE-style path traversal risks in &lt;strong&gt;.arsc&lt;/strong&gt; resource tables, and diffs &lt;strong&gt;.dex&lt;/strong&gt; output when a rebuild diverges from the original. It's meant to answer one question reliably: "did apktool actually reproduce this APK correctly, or did something quietly go missing?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bug Fix or Performance Improvement&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While stress-testing the tool against real-world APKs (F-Droid client, NewPipe), I found two bugs in the tool itself — both are the "silent, not loud" kind that are worse than a crash because they let you trust a wrong answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bug 1&lt;/em&gt;&lt;/strong&gt; — &lt;strong&gt;-unbounded allocation while scanning **&lt;em&gt;.arsc&lt;/em&gt;&lt;/strong&gt; string pools (crash / OOM risk).**&lt;br&gt;
The scanner walks a binary resource table looking for &lt;em&gt;&lt;strong&gt;ResStringPool&lt;/strong&gt;&lt;/em&gt; chunk headers &lt;em&gt;&lt;strong&gt;(0x0001)&lt;/strong&gt;&lt;/em&gt;. Because it's a best-effort byte scan rather than a full chunk-tree walk, a coincidental two-byte match on unrelated data could be misread as a real chunk header, handing the parser a garbage &lt;em&gt;&lt;strong&gt;stringCount&lt;/strong&gt;&lt;/em&gt; with no sanity check. The following loop would then try to iterate that many entries — on a crafted or corrupted &lt;em&gt;&lt;strong&gt;.arsc&lt;/strong&gt;&lt;/em&gt;, that's an unbounded allocation/iteration with no upper bound, i.e. an OOM or hang.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bug 2&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;-silent undercount in the round-trip summary (data-loss-masking bug).&lt;/strong&gt;&lt;br&gt;
The round-trip check classifies problems into two buckets — files missing from the rebuild, and files whose content diverged — then folds both into a single summary dict keyed by category. The original code did &lt;em&gt;&lt;strong&gt;summary[k] = len(v)&lt;/strong&gt;&lt;/em&gt; for each source dict in turn. If the same category key showed up in both buckets (which happens), the second write silently clobbered the first instead of adding to it. The tool would report a lower anomaly count than actually existed — exactly the kind of bug that erodes trust in a tool whose entire purpose is catching silent problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;_&lt;br&gt;
Fix 1_&lt;/strong&gt; — bounds-check &lt;strong&gt;&lt;em&gt;stringCount&lt;/em&gt;&lt;/strong&gt; before trusting it:&lt;/p&gt;

&lt;p&gt;**`if stringCount == 0 or size &amp;lt;= 0 or size &amp;gt; len(data) - off:&lt;br&gt;
                     off += 4&lt;br&gt;
                     continue&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;               # Sanity bounds: a coincidental 0x0001 match on non-chunk&lt;/li&gt;
&lt;li&gt;               # bytes can yield a garbage stringCount that would try to&lt;/li&gt;
&lt;li&gt;               # allocate/iterate absurdly - bail out instead of OOMing.&lt;/li&gt;
&lt;li&gt;               idx_off = off + 28&lt;/li&gt;
&lt;li&gt;               if stringCount &amp;gt; 5_000_000 or idx_off + stringCount * 4 &amp;gt; off + size:&lt;/li&gt;
&lt;li&gt;                   off += 4&lt;/li&gt;
&lt;li&gt;                   continue
             is_utf8 = bool(flags &amp;amp; (1 &amp;lt;&amp;lt; 8))
             strings = []&lt;/li&gt;
&lt;li&gt;               idx_off = off + 28
             base = off + stringsStart
             for i in range(stringCount):
                 entry_off = struct.unpack_from('&amp;lt;I', data, idx_off + i * 4)[0]`**&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Fix 2&lt;/em&gt;&lt;/strong&gt; — accumulate instead of overwrite:&lt;/p&gt;

&lt;p&gt;`**summary = {}&lt;br&gt;
     for src in (missing_classified, diverged_classified):&lt;br&gt;
         for k, v in src.items():&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;           summary[k] = len(v)&lt;/li&gt;
&lt;li&gt;           summary[k] = summary.get(k, 0) + len(v)**`&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Full diff:&lt;/strong&gt; &lt;a href="https://github.com/zowskyy/apktool-diagnostics/commit/33d7924" rel="noopener noreferrer"&gt;zowskyy/apktool-diagnostics@33d7924&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;My Improvements&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the OOM fix, the guard has two conditions rather than one: &lt;strong&gt;&lt;em&gt;stringCount &amp;gt; 5_000_000&lt;/em&gt;&lt;/strong&gt; catches the "obviously garbage" case cheaply, and &lt;strong&gt;&lt;em&gt;idx_off + stringCount * 4 &amp;gt; off + size&lt;/em&gt;&lt;/strong&gt; catches the subtler case where &lt;strong&gt;&lt;em&gt;stringCount&lt;/em&gt;&lt;/strong&gt; is plausible-looking but would still read past the chunk's own declared size — the second check is the one that actually matters for a maliciously crafted file, since an attacker tuning the value to dodge a flat cap would still get caught by the bounds check. I moved the check to sit immediately after the existing stringCount == 0 guard rather than tacking it on later, so all the "is this chunk even real" logic lives in one place instead of being scattered.&lt;/p&gt;

&lt;p&gt;For the summary bug, the fix is a one-character-class change (&lt;strong&gt;&lt;em&gt;= → +=&lt;/em&gt;&lt;/strong&gt; in spirit), but finding it required noticing that &lt;strong&gt;&lt;em&gt;missing_classified&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;diverged_classified&lt;/em&gt;&lt;/strong&gt; aren't guaranteed to have disjoint keys — they're independent classifiers that happen to share a category vocabulary. I caught it by deliberately constructing a test APK where a category appeared in both buckets and asserting the summary count against a hand-computed total, rather than trusting the tool's own output as ground truth.&lt;/p&gt;

&lt;p&gt;Both bugs were caught by the same practice: never trust a diagnostic tool's clean report without an independent check against a real, adversarial input. The OOM bug only showed up because I fed the scanner a byte sequence deliberately chosen to produce a false chunk-header match; the summary bug only showed up because I hand-verified counts on a fixture built to collide across both classifiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;apktool_diagnostics.py&lt;/code&gt; tool is now complete, production-ready, and live on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full toolkit:&lt;/strong&gt; 5 subcommands (security-scan, framework-check, roundtrip, dex-diff, corpus)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/zowskyy/apktool-diagnostics" rel="noopener noreferrer"&gt;zowskyy/apktool-diagnostics&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Quick start:&lt;/strong&gt; &lt;code&gt;python3 apktool_diagnostics.py security-scan your-app.apk&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The two bugs described in this post have been fixed (commits &lt;a href="https://github.com/zowskyy/apktool-diagnostics/commit/d362bb2" rel="noopener noreferrer"&gt;d362bb2&lt;/a&gt; and &lt;a href="https://github.com/zowskyy/apktool-diagnostics/commit/33d7924" rel="noopener noreferrer"&gt;33d7924&lt;/a&gt;). Real test results from F-Droid client and NewPipe are included in the repo.&lt;/p&gt;

&lt;p&gt;Related: See &lt;a href="https://dev.to/thewilliamboyd93oss/the-advisory-said-it-was-fixed-i-didnt-believe-it-until-i-broke-it-myself-513b"&gt;The Advisory Said It Was Fixed&lt;/a&gt; for a full CVE-2026-39973 reproduction using this toolkit.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>I gave my agent the right memory and it ignored it anyway</title>
      <dc:creator>zowskyy</dc:creator>
      <pubDate>Fri, 17 Jul 2026 01:15:39 +0000</pubDate>
      <link>https://dev.to/thewilliamboyd93oss/i-gave-my-agent-the-right-memory-and-it-ignored-it-anyway-li7</link>
      <guid>https://dev.to/thewilliamboyd93oss/i-gave-my-agent-the-right-memory-and-it-ignored-it-anyway-li7</guid>
      <description>&lt;p&gt;A few weeks ago I was testing a support-agent setup — nothing fancy, just&lt;br&gt;
an LLM with a memory layer bolted on so it could remember basic facts&lt;br&gt;
about a user across sessions. Subscription tier, shipping address, that&lt;br&gt;
kind of thing.&lt;/p&gt;

&lt;p&gt;I ran a simple scenario: the user is already on the enterprise plan. I&lt;br&gt;
confirmed the memory retrieval was working — the fact &lt;strong&gt;&lt;code&gt;subscription_tier:&lt;br&gt;
enterprise&lt;/code&gt;&lt;/strong&gt; came back correctly when I queried "what tier is the user's&lt;br&gt;
subscription."&lt;/p&gt;

&lt;p&gt;Then I asked the agent, in a support-chat style prompt, what plan the user&lt;br&gt;
was on.&lt;/p&gt;

&lt;p&gt;The response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Sure, upgrading to our enterprise plan would unlock that feature for&lt;br&gt;
you."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The user is &lt;em&gt;already on&lt;/em&gt; enterprise. The agent had the correct fact sitting&lt;br&gt;
right there in its context. It just... used it wrong. Not "forgot it" —&lt;br&gt;
that's a different, more talked-about failure mode. This one is worse in a&lt;br&gt;
specific way: retrieval succeeded, the fact was injected, and the response&lt;br&gt;
was still confidently incorrect. Nothing failed loudly. Nothing threw an&lt;br&gt;
error. If I hadn't been staring at the raw context myself, I'd have had no&lt;br&gt;
way to know this happened except a confused (or annoyed) user telling me&lt;br&gt;
about it after the fact.&lt;/p&gt;

&lt;p&gt;I went looking for how the popular memory frameworks handle this — Mem0,&lt;br&gt;
Zep, Letta, the usual suspects. They're all solving real problems: storage,&lt;br&gt;
retrieval, contradiction handling as facts change over time. Zep in&lt;br&gt;
particular does well on temporal accuracy benchmarks.&lt;/p&gt;

&lt;p&gt;But as far as I can tell, none of them check the thing that actually broke&lt;br&gt;
in my test: &lt;em&gt;did the LLM's response actually reflect the memory that got&lt;br&gt;
retrieved for it?&lt;/em&gt; Every framework I looked at seems to assume that once a&lt;br&gt;
fact is in context, the model uses it correctly. My test says that&lt;br&gt;
assumption doesn't always hold.&lt;/p&gt;

&lt;p&gt;So now I'm curious what other people are seeing. If you're running agents&lt;br&gt;
with any kind of persistent memory in production —&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have you actually checked whether your agent uses retrieved facts
correctly, or are you trusting that retrieval working means the response
will be right?&lt;/li&gt;
&lt;li&gt;Has anyone built something that catches this — not "did retrieval return
the right fact" but "did the response actually reflect it"?&lt;/li&gt;
&lt;li&gt;Is this a known, named failure mode I'm just not aware of, or does
everyone kind of just... not check?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Genuinely asking — I've been digging into this for a bit and I'm not sure&lt;br&gt;
if I'm looking at something under-discussed or just late to a&lt;br&gt;
well-known problem.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>llm</category>
      <category>rag</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
