jegly/jegly

boring readme

1

33 commits

updated Oct 3, 2026

See the code

See what people are saying

README

Chaotic Cubes

My experience with VRP, Google assigned 30+ of my zero-click Android bugs, had me sign a CLA, then closed them "duplicate" or "not a vulnerability" and shipped the fixes. Eight months on, a bounty they confirmed in writing is still half unpaid. 85+ total bugs submitted over 8 months of everyday work.

This repository documents a pattern across my own Android VRP reports to Google. Everything below concerns reports Google has already closed (as duplicate, infeasible, can't-repro, or working-as-intended). Nothing here concerns a report that is still open or under active coordinated disclosure. Where I quote Google, the quote is verbatim from the ticket.

Summary

I have spent most of the last eight months doing security research on a current Pixel, reverse engineering the Samsung radio-interface binaries, the IMS and RTP native stack, the Trusty trusted apps, and the telephony framework. Over thirty distinct findings, most with a working proof of concept, several confirmed with AddressSanitizer against real compiled code.

Here is what I have to show for it. One bounty, for two older bugs, that Google confirmed in writing at five hundred dollars total. Half of it, two hundred fifty dollars, has been paid. The other half has been "processing" since January 2026. That is over eight months of "manual exception workflow" and "we cannot give you an exact date."

This is not one unlucky ticket, and it is two separate problems, not one. Going through Google's own current factory images function by function, I can confirm 8 of my closed findings were fixed in the shipped code with no reward or credit at all. That is the money and credit problem. Separately, 14 more were closed as duplicate, infeasible, or can't-repro, and the exact defect I reported is still unchanged in the current build. Nobody owes a reward for an unfixed bug. What that second group proves is different: that the stated reason for closing them does not hold up against the actual code. Both tables below list these by ticket number, kept apart on purpose.

Methodology

For every bug in the case index below, the verdict was reached the same way:

  1. Pull the exact binary, library, or jar that the original report named, out of Google's current factory image (rango and yogi, Pixel builds dated September 2026).
  2. Extract it with the correct tool for its container: debugfs for ext4 partitions, fsck.erofs for EROFS, double extraction through apex_payload.img for APEX modules.
  3. Locate the exact function the report cited, using symbol resolution where the binary still has symbols, or byte-pattern matching against the report's own quoted disassembly where it does not.
  4. Compare the current code against what the report described as vulnerable.

Four possible verdicts:

  • PATCHED — the code changed. A bound check, a permission check, or a validation step was added that was not there when I reported it.
  • STILL PRESENT — the code is unchanged. The defect described in the report is still shipping today.
  • N/A — the bug was reported against this exact build, so there is no before/after window to check, or Google already acknowledged the behavior is an intentional design choice rather than a defect.
  • CANT-DETERMINE — the binary is not present in this factory image, or the build differs enough (different device, different compiler) that the comparison is not reliable.

I did not use Google's own security bulletin or the supplemental patch list as a substitute for this. A silent fix with no bulletin entry and no credit would not show up there. The only way to find one is to diff the actual shipped code, which is what this repository documents.

The reward they promised me in writing, and still have not paid

Before the thirty-plus newer bugs, there were two older ones. On ticket 467575499, on January 23rd 2026, Google wrote that they had "confirmed this is a valid bug," were "increasing severity," "increasing reward," and would "issue a payment for the new amount once it has been processed." Three months later, on April 24th, with no new analysis and no new evidence, they reversed all three determinations. The same bug now "does not qualify for a reward or severity increase," called a "Final Assessment."

Then, in June, they wrote that they wanted to "stand by our word regarding the reward." In July, on the 10th, they confirmed the number directly: "$250 for Safety Center, $250 for the mic bug, $500 total," and said they were "honoring our initial promise," even while classifying the Safety Center half as a duplicate with no security impact. The mic-bug half was paid. The Safety Center half has been sitting in a "discretionary payment exception" that Google "cannot give an exact credit date" for, since January. That is a debt confirmed in their own writing, on the record, unpaid for over eight months.

The admitted duplicate-ID collision

Google's basis for closing a report as a duplicate is an internal canonical bug ID. I asked them to produce the specific numbers behind several of these closures.

They cited the exact same internal canonical ID, 479203197, as the original report for two completely unrelated bugs of mine: a Rust keystore panic in a trusted app, and a C++ out-of-bounds read in the RTP media parser. Different languages, different subsystems, different teams. One internal report cannot be the root cause of both.

Google admitted the error in writing, on both tickets independently: "Giving you this ID for your other report was a mistake on our end, and we apologise for the confusion," and separately, "we made a mistake when linking the duplicate tickets." That means at least one "verified duplicate" was stamped without anyone actually checking it against the report it supposedly matched.

They have used the exact "not a vulnerability" template before, and retracted it

Earlier in this campaign, Google closed one finding with the boilerplate "not a security vulnerability... logged for potential remediation in a future version... considered closed." They later reversed it in writing: "You are completely correct. This finding is indeed a legitimate security vulnerability... it was incorrectly closed as 'not a security vulnerability' due to an oversight during triage of this specific ticket."

The same template that closed several more of my bugs afterward has a documented track record, on my own tickets, of being wrong by Google's own admission.

Case index: confirmed fixed, closed with no reward

Every row below is a closed report, checked directly against the current September 2026 factory image. "Fixed" means the specific code the report described changed; the report was correct, the fix shipped, and the ticket was still closed with no reward.

TicketComponent / functionClosure reasonVerdict
549662989PixelModemService MintReceiverDuplicateFixed. The guard permission MINT_CONFIGURATION_UPDATE_BROADCAST is now defined, signature|privileged.
538369053framework.jar PduParser.parsePartsDuplicate, "previously reported by an internal Google engineer"Fixed. A bound check against stream.available() was added before the allocation that used to run unconditionally.
560866996framework.jar PduParser (same defect as above)Duplicate, canonical 513581684, "internal Google engineer"Fixed, same underlying code change as 538369053.
545948346libpixelimsmedia.so RtpSession::numberOfReportBlocksDuplicate, "previously reported by an internal Google engineer"Fixed. An underflow check was added before the division that used to produce garbage.
540755547libpixelimsmedia.so RtcpChunk::decodeRtcpChunkDuplicateFixed. A bound check on the SDES item length was added before the allocation and copy.
541096815libpixelimsmedia.so RtpDecoderNode::DecodeRtpHeaderExtensionInfeasible, "not a security vulnerability"Fixed. The length byte is now read unsigned instead of signed, with a new bound check added after it.
540753210libpixelimsmedia.so RtcpFbPacket::decodeRtcpFbPacketDuplicateFixed. A length check was added before the operation that previously underflowed unconditionally.
529518244ShannonRcs.apk DeviceProvisioningServiceDuplicateFixed. The service now requires a signature|privileged permission where none existed before.

That is 8 findings, each with its own ticket number, that Google closed as duplicate or infeasible and then fixed in the shipping image.

Case index: still unpatched, closed with reasoning that does not hold up

This table is not a payment claim. None of these bugs were fixed, so no reward is owed for them. What this table shows is that the stated reason for closing each one does not match the actual code: the defect described in the original report is still there, unchanged, in the current factory image. Every row is a closed report.

TicketComponent / functionClosure reasonVerdict
561846309ConfigUpdater.apk PhenotypeHelper.getCertificate()WAIUnchanged. Returns one certificate for every config type while its two sibling methods correctly switch on type.
552305734CrossDeviceAccessServicePrimary.apk PermissionBroadcastReceiverInfeasibleUnchanged. Exported, no permission.
558478942DCMO.apk DcmoReceiverDuplicateUnchanged. Exported, no permission, matches its unguarded siblings.
555981160telephony-common.jar ValueParser.retrieveItemsIconIdCan't ReproUnchanged. No lower-bound check before new-array on a decremented length.
538367541 / 543174762telephony-common.jar WapPushOverSms.decodeWapPduDuplicateUnchanged. Unvalidated decoded uintvar used directly as an allocation size.
557568085TelephonyProvider.apk updateSynchronized()DuplicateUnchanged. Caller-supplied selection string concatenated with no parentheses before the ownership clause.
560453159service-ranging.jar RangingServiceImpl (UWB Digital Key)Can't ReproUnchanged on the stable image. The same fix exists on the Android 17 QPR2 beta track, raising the question of which build was actually tested.
555984702grilservice.apk RadioConfig.readFromParcelCan't ReproUnchanged. Raw readInt() size used directly as an allocation with no upper bound.
552188030services.jar Vpn.setVpnForcedLocked()DuplicateUnchanged. UID 0 is excluded from the VPN lockdown range.
558472707service-npumanager.jar NpuManagerServiceImpl.cancelModelLoadInfeasibleUnchanged. No caller-identity check, unlike sibling methods on the same class.
537920968nfc_nci.st21nfc.default.so handlePollingLoopDataInfeasibleUnchanged. Same undersized length-check logic, confirmed at the function's new address after a build-layout shift.
538311587android.hardware.secure_element-service.uiccInfeasibleUnchanged. Same mutex/slot/type offsets, no per-call correlation token.
538314136IntentResolver.apk UriMetadataReaderImpl.getMetadata (DISPLAY_ICON_URI)InfeasibleUnchanged. No ownership check on the cross-profile URI read.
560878423libimsstack.so SipMessageFraming::ParseMessageBodyCan't ReproUnchanged. A negative content length still passes a signed comparison into the completed-message state instead of being rejected.

Full audit: factory-image verification of closed reports

Baseline images: rango (Pixel 10 Pro Fold, build CP3A.260905.009) and yogi (Pixel 11 Pro Fold, build CD1A.260905.001.B1), both dated September 2026. Every row below is a closed report. Reports still open or under active coordinated disclosure are deliberately excluded and are not listed here.

TicketComponent / functionDeviceVerdictEvidence
-sit.stream.so DecodingBarringInfosblazer/rango (Tensor)STILL PRESENTByte-identical check-after-copy sequence at the reported offset.
-sit.stream.so FillCellInforangoSTILL PRESENTByte-identical; unconditional 2-byte read still precedes the length-validation call.
-libsitril.so OnReadPbEntryDoneblazerCANT-DETERMINEBinary differs between blazer and rango; needs a blazer image.
-vendor.radio.base.so DecodingNetworkNameFromSIMblazerCANT-DETERMINEDifferent binary/device; needs a blazer image.
-libsitril.so BuildSimAuthenticateblazerCANT-DETERMINESame cross-device issue as above.
555425202libmtkmipc-ril.so MIPC deserialize / onPcoInfoNotify TLV-length discardyogiSTILL PRESENTByte-identical at the cited addresses; TLV length still discarded.
561843639libmtkmipc-ril.so handleUssdIndyogiSTILL PRESENTByte-identical; incorrect TLV length field still used for the copy length.
-libimsmediahalproxy_mtk.so processRtpSessionReceivedDtmfIndyogiSTILL PRESENTByte-identical VLA-size mask, no upper-bound check added.
-sit.stream.so processSlicingConfigblazerCANT-DETERMINEAddress exceeds this image's binary size; needs a blazer image.
-android.hardware.samsung.uwb-service CalDataReadCmd::handleRspDatablazerCANT-DETERMINEBuild/layout differs from this image; needs a blazer image.
-cpif.ko pktproc_get_pkt_from_sktbuf_modeblazerSTILL PRESENT (high confidence)Same twin bound-check pattern (inclusive upper bound) found at the resolved symbol offset.
537918512libframesequence.so (WebP compositor)rango/yogiCANT-DETERMINELibrary rebuilt with a newer toolchain since the report; the specific function could not be relocated.
549662989PixelModemService.apk MintReceiver + guard permissionyogiPATCHEDThe guard permission MINT_CONFIGURATION_UPDATE_BROADCAST is now defined, signature|privileged.
560448300com.android.omadm.service DMService.TelephonyBroadcastReceiveryogiSTILL PRESENTExported, no permission, unchanged.
560448300com.google.android.configupdater (8 receivers)yogiSTILL PRESENTAll 8 receivers still exported, no permission.
560448300com.android.ons ONSProfileResultReceiveryogiSTILL PRESENTExported, no permission, unchanged.
561846309ConfigUpdater.apk PhenotypeHelper.getCertificate()yogiSTILL PRESENTByte-for-byte identical; no switch on config type, unlike its sibling methods.
552305734CrossDeviceAccessServicePrimary.apk PermissionBroadcastReceiveryogiSTILL PRESENTExported, no permission, unchanged.
558478942DCMO.apk DcmoReceiveryogiSTILL PRESENTExported, no permission, unchanged.
555981160telephony-common.jar ValueParser.retrieveItemsIconIdyogiSTILL PRESENTByte-exact; no lower-bound check before array allocation.
538367541 / 543174762telephony-common.jar WapPushOverSms.decodeWapPduyogiSTILL PRESENTUnchecked decoded value used directly as an allocation size.
538369053framework.jar PduParser.parsePartsyogiPATCHEDBound check added against available stream bytes before the allocation.
560866996framework.jar PduParser (same defect)yogiPATCHEDSame fix as above.
557568085TelephonyProvider.apk updateSynchronized()yogiSTILL PRESENTSelection string still concatenated with no parentheses around the caller-supplied fragment.
563677600WebAppServiceGoogle.apkyogiSTILL PRESENTExported, no permission, unchanged at the manifest level.
-AppDirectedSMSService.apk (Verizon)rangoPOSSIBLY PATCHED, unconfirmedThe receiver block does not appear in this image; needs direct re-diff before being called a confirmed fix.
565498857framework-res.apk EXECUTE_APP_FUNCTIONSyogiN/AThis image's protection level matches the report's own description of the safe baseline.
560453159service-ranging.jar RangingServiceImpl (UWB)yogiSTILL PRESENT on stableNo permission check on either affected method. The same fix exists on the Android 17 QPR2 beta track.
560453159CccDkTimeSyncService.apkyogiSTILL PRESENTExported, no permission, unchanged.
555984702grilservice.apk RadioConfig.readFromParcelyogiSTILL PRESENTRaw size field used directly as an allocation size, no upper bound.
552188030services.jar Vpn.setVpnForcedLocked()yogiSTILL PRESENTUID 0 still excluded from the VPN lockdown range.
552196751framework.jar NetworkCapabilities (architectural)rangoN/AGoogle has acknowledged this is an intentional design choice, not a defect.
558472707service-npumanager.jar NpuManagerServiceImpl.cancelModelLoadyogiSTILL PRESENTNo caller-identity check, unlike sibling methods on the same class.
540753210libpixelimsmedia.so RtcpFbPacket::decodeRtcpFbPacketyogiPATCHEDLength check added before the operation that previously underflowed unconditionally.
540755547libpixelimsmedia.so RtcpChunk::decodeRtcpChunkyogiPATCHEDBound check added on the SDES item length before the allocation and copy.
541096815libpixelimsmedia.so RtpDecoderNode::DecodeRtpHeaderExtensionyogiPATCHEDLength byte now read unsigned; new bound check added.
545948346libpixelimsmedia.so RtpSession::numberOfReportBlocksyogiPATCHEDUnderflow check added before the division that previously produced garbage.
540746953libimsstack.so SipMsgBody::EncodeSingleMsgBody / SetMsgBufferyogiSTILL PRESENTNo cap against the maximum message size anywhere in the call path.
560878423libimsstack.so SipMessageFraming::ParseMessageBodyyogiSTILL PRESENTSame signed-comparison logic at the function's new address; a negative content length is still accepted.
538314136IntentResolver.apk UriMetadataReaderImpl.getMetadata (DISPLAY_ICON_URI)yogiSTILL PRESENTNo ownership check on the cross-profile URI read; logic unchanged despite a refactor.
-rkpdapp.google.apk X509Utils.verifyCertChainyogiSTILL PRESENTCaller-supplied root used as the sole trust anchor, no hardcoded pin.
559533853pmgd (native Rust binary)yogiCANT-DETERMINEAggressive symbol stripping; the relevant function could not be identified.
537920968nfc_nci.st21nfc.default.so handlePollingLoopDatarangoSTILL PRESENTSame undersized length-check logic confirmed at the function's new address.
537923579nfc_nci.st21nfc.default.so FwUpdateHandlerrangonot rigorously checkedFunction located but no precise comparison pattern available.
538311587android.hardware.secure_element-service.uiccrangoSTILL PRESENTSame mutex/slot/type offsets, no per-call correlation token.
-PrebuiltBugle.apk MMS PDU decoderrangoSTILL PRESENTAllocation site still has no upper-bound check.
-android.hardware.samsung.uwb-service match_dev_ctrl_cmdrangoCANT-DETERMINEBuild differs from the reported device; needs the matching image.
529102332ShannonRcs.apk DebugBroadcastReceiverrangoSTILL PRESENTExported, no permission, unchanged.
-ShannonRcs.apk ScheduleAlarmReceiverrangoSTILL PRESENTExported, no permission, unchanged.
529518244ShannonRcs.apk DeviceProvisioningServicerangoPATCHEDService now requires a signature|privileged permission where none existed before.
-ShannonRcs.apk SipDelegateMessageInfoParser.getViaHeader (+siblings)rangoSTILL PRESENTHeader-index result used directly with no failure check.
-ShannonRcs.apk MultipartParser.parseMessage / ResourceListInfoDecoder.parserangoSTILL PRESENTNo array-length or null checks added.
545958850ShannonIms.apk RilIndCallComposerMt.toString()rangoSTILL PRESENTLength check against the decoded string's true size still missing.
-OemRilService.apk DataReader.getBytes(int)rangoSTILL PRESENTAllocation size unchecked.
-VendorSatelliteService.apk ByteUtil.primitiveArrayToIntrangoSTILL PRESENTNo length check before indexed array access.
541986810framework.jar RecoverySystem.verifyPackageyogiSTILL PRESENTCertificate selected for the trust check and the certificate actually used by signature verification can still differ.
547481980services.jar FileService$FileServiceStub.enqueueOperationyogiSTILL PRESENT (high confidence)No ownership/permission enforcement found before dispatch.
-telephony-common.jar WspTypeDecoder.seekXWapApplicationIdyogiSTILL PRESENTEmpty catch block unchanged; the decode loop can still spin indefinitely.
-ShannonRcs.apk SipDelegateMessageInfoParser.retrieveFeaturesFromSipMessagerangoSTILL PRESENTUnanchored substring match unchanged.
-IntentResolver.apk PayloadToggleCursorResolver (EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI)yogiPATCHED (not counted)New validation layer added, routing the URI through the system's real grant-permission check. Not counted among the 8: this specific vector does not map to a report I filed.
547477982bcmdhd4390.ko wl_cfgnan_parse_sd_attr_datarangonot conclusively checkedFunction location shifted; could not confirm whether this reflects a fix or unrelated drift.

Summary of the full audit

  • Confirmed fixed, no reward or credit: 8 (each with its own ticket number; one further fixed function, IntentResolver PayloadToggleCursorResolver, is listed but not counted because it does not map to a report I filed).
  • Confirmed still present, closed with reasoning that does not hold up: the Google VRP tickets in the table above, plus the Samsung and MediaTek submissions through the same program.
  • Could not be determined: a build/device mismatch, missing partition, or stripped binary prevented a reliable comparison; marked as such rather than guessed at.

This is not just me

In June 2026, The Register ran "Google told researcher 'Nice catch!' Then denied bug bounty for flaw it still hasn't fixed." Praise, then denial, then an unfixed bug. A separate researcher documented their own Google VRP case moving from "not a bug" to "not a vulnerability" to, finally, duplicate, meaning Google knew the whole time it was real. CSO Online has reported that legal experts believe these programs may raise labor-law questions, treating people who do the work of employees as disposable contractors whose finished work can be rejected with no explanation and no recourse.

I am not against Google. One of my own projects, Box, started as a fork of their AI Edge Gallery, and I have built on it every day for the past twelve months. Over that time Google has pulled features from my fork back into their own project, so the contribution has gone both ways and in good faith. This is specifically about what happened when I reported real, working security findings through their own stated process.

jegly/jegly

boring readme

1

33 commits

updated Oct 3, 2026

See the code

See what people are saying

README

Chaotic Cubes

My experience with VRP, Google assigned 30+ of my zero-click Android bugs, had me sign a CLA, then closed them "duplicate" or "not a vulnerability" and shipped the fixes. Eight months on, a bounty they confirmed in writing is still half unpaid. 85+ total bugs submitted over 8 months of everyday work.

This repository documents a pattern across my own Android VRP reports to Google. Everything below concerns reports Google has already closed (as duplicate, infeasible, can't-repro, or working-as-intended). Nothing here concerns a report that is still open or under active coordinated disclosure. Where I quote Google, the quote is verbatim from the ticket.

Summary

I have spent most of the last eight months doing security research on a current Pixel, reverse engineering the Samsung radio-interface binaries, the IMS and RTP native stack, the Trusty trusted apps, and the telephony framework. Over thirty distinct findings, most with a working proof of concept, several confirmed with AddressSanitizer against real compiled code.

Here is what I have to show for it. One bounty, for two older bugs, that Google confirmed in writing at five hundred dollars total. Half of it, two hundred fifty dollars, has been paid. The other half has been "processing" since January 2026. That is over eight months of "manual exception workflow" and "we cannot give you an exact date."

This is not one unlucky ticket, and it is two separate problems, not one. Going through Google's own current factory images function by function, I can confirm 8 of my closed findings were fixed in the shipped code with no reward or credit at all. That is the money and credit problem. Separately, 14 more were closed as duplicate, infeasible, or can't-repro, and the exact defect I reported is still unchanged in the current build. Nobody owes a reward for an unfixed bug. What that second group proves is different: that the stated reason for closing them does not hold up against the actual code. Both tables below list these by ticket number, kept apart on purpose.

Methodology

For every bug in the case index below, the verdict was reached the same way:

  1. Pull the exact binary, library, or jar that the original report named, out of Google's current factory image (rango and yogi, Pixel builds dated September 2026).
  2. Extract it with the correct tool for its container: debugfs for ext4 partitions, fsck.erofs for EROFS, double extraction through apex_payload.img for APEX modules.
  3. Locate the exact function the report cited, using symbol resolution where the binary still has symbols, or byte-pattern matching against the report's own quoted disassembly where it does not.
  4. Compare the current code against what the report described as vulnerable.

Four possible verdicts:

  • PATCHED — the code changed. A bound check, a permission check, or a validation step was added that was not there when I reported it.
  • STILL PRESENT — the code is unchanged. The defect described in the report is still shipping today.
  • N/A — the bug was reported against this exact build, so there is no before/after window to check, or Google already acknowledged the behavior is an intentional design choice rather than a defect.
  • CANT-DETERMINE — the binary is not present in this factory image, or the build differs enough (different device, different compiler) that the comparison is not reliable.

I did not use Google's own security bulletin or the supplemental patch list as a substitute for this. A silent fix with no bulletin entry and no credit would not show up there. The only way to find one is to diff the actual shipped code, which is what this repository documents.

The reward they promised me in writing, and still have not paid

Before the thirty-plus newer bugs, there were two older ones. On ticket 467575499, on January 23rd 2026, Google wrote that they had "confirmed this is a valid bug," were "increasing severity," "increasing reward," and would "issue a payment for the new amount once it has been processed." Three months later, on April 24th, with no new analysis and no new evidence, they reversed all three determinations. The same bug now "does not qualify for a reward or severity increase," called a "Final Assessment."

Then, in June, they wrote that they wanted to "stand by our word regarding the reward." In July, on the 10th, they confirmed the number directly: "$250 for Safety Center, $250 for the mic bug, $500 total," and said they were "honoring our initial promise," even while classifying the Safety Center half as a duplicate with no security impact. The mic-bug half was paid. The Safety Center half has been sitting in a "discretionary payment exception" that Google "cannot give an exact credit date" for, since January. That is a debt confirmed in their own writing, on the record, unpaid for over eight months.

The admitted duplicate-ID collision

Google's basis for closing a report as a duplicate is an internal canonical bug ID. I asked them to produce the specific numbers behind several of these closures.

They cited the exact same internal canonical ID, 479203197, as the original report for two completely unrelated bugs of mine: a Rust keystore panic in a trusted app, and a C++ out-of-bounds read in the RTP media parser. Different languages, different subsystems, different teams. One internal report cannot be the root cause of both.

Google admitted the error in writing, on both tickets independently: "Giving you this ID for your other report was a mistake on our end, and we apologise for the confusion," and separately, "we made a mistake when linking the duplicate tickets." That means at least one "verified duplicate" was stamped without anyone actually checking it against the report it supposedly matched.

They have used the exact "not a vulnerability" template before, and retracted it

Earlier in this campaign, Google closed one finding with the boilerplate "not a security vulnerability... logged for potential remediation in a future version... considered closed." They later reversed it in writing: "You are completely correct. This finding is indeed a legitimate security vulnerability... it was incorrectly closed as 'not a security vulnerability' due to an oversight during triage of this specific ticket."

The same template that closed several more of my bugs afterward has a documented track record, on my own tickets, of being wrong by Google's own admission.

Case index: confirmed fixed, closed with no reward

Every row below is a closed report, checked directly against the current September 2026 factory image. "Fixed" means the specific code the report described changed; the report was correct, the fix shipped, and the ticket was still closed with no reward.

TicketComponent / functionClosure reasonVerdict
549662989PixelModemService MintReceiverDuplicateFixed. The guard permission MINT_CONFIGURATION_UPDATE_BROADCAST is now defined, signature|privileged.
538369053framework.jar PduParser.parsePartsDuplicate, "previously reported by an internal Google engineer"Fixed. A bound check against stream.available() was added before the allocation that used to run unconditionally.
560866996framework.jar PduParser (same defect as above)Duplicate, canonical 513581684, "internal Google engineer"Fixed, same underlying code change as 538369053.
545948346libpixelimsmedia.so RtpSession::numberOfReportBlocksDuplicate, "previously reported by an internal Google engineer"Fixed. An underflow check was added before the division that used to produce garbage.
540755547libpixelimsmedia.so RtcpChunk::decodeRtcpChunkDuplicateFixed. A bound check on the SDES item length was added before the allocation and copy.
541096815libpixelimsmedia.so RtpDecoderNode::DecodeRtpHeaderExtensionInfeasible, "not a security vulnerability"Fixed. The length byte is now read unsigned instead of signed, with a new bound check added after it.
540753210libpixelimsmedia.so RtcpFbPacket::decodeRtcpFbPacketDuplicateFixed. A length check was added before the operation that previously underflowed unconditionally.
529518244ShannonRcs.apk DeviceProvisioningServiceDuplicateFixed. The service now requires a signature|privileged permission where none existed before.

That is 8 findings, each with its own ticket number, that Google closed as duplicate or infeasible and then fixed in the shipping image.

Case index: still unpatched, closed with reasoning that does not hold up

This table is not a payment claim. None of these bugs were fixed, so no reward is owed for them. What this table shows is that the stated reason for closing each one does not match the actual code: the defect described in the original report is still there, unchanged, in the current factory image. Every row is a closed report.

TicketComponent / functionClosure reasonVerdict
561846309ConfigUpdater.apk PhenotypeHelper.getCertificate()WAIUnchanged. Returns one certificate for every config type while its two sibling methods correctly switch on type.
552305734CrossDeviceAccessServicePrimary.apk PermissionBroadcastReceiverInfeasibleUnchanged. Exported, no permission.
558478942DCMO.apk DcmoReceiverDuplicateUnchanged. Exported, no permission, matches its unguarded siblings.
555981160telephony-common.jar ValueParser.retrieveItemsIconIdCan't ReproUnchanged. No lower-bound check before new-array on a decremented length.
538367541 / 543174762telephony-common.jar WapPushOverSms.decodeWapPduDuplicateUnchanged. Unvalidated decoded uintvar used directly as an allocation size.
557568085TelephonyProvider.apk updateSynchronized()DuplicateUnchanged. Caller-supplied selection string concatenated with no parentheses before the ownership clause.
560453159service-ranging.jar RangingServiceImpl (UWB Digital Key)Can't ReproUnchanged on the stable image. The same fix exists on the Android 17 QPR2 beta track, raising the question of which build was actually tested.
555984702grilservice.apk RadioConfig.readFromParcelCan't ReproUnchanged. Raw readInt() size used directly as an allocation with no upper bound.
552188030services.jar Vpn.setVpnForcedLocked()DuplicateUnchanged. UID 0 is excluded from the VPN lockdown range.
558472707service-npumanager.jar NpuManagerServiceImpl.cancelModelLoadInfeasibleUnchanged. No caller-identity check, unlike sibling methods on the same class.
537920968nfc_nci.st21nfc.default.so handlePollingLoopDataInfeasibleUnchanged. Same undersized length-check logic, confirmed at the function's new address after a build-layout shift.
538311587android.hardware.secure_element-service.uiccInfeasibleUnchanged. Same mutex/slot/type offsets, no per-call correlation token.
538314136IntentResolver.apk UriMetadataReaderImpl.getMetadata (DISPLAY_ICON_URI)InfeasibleUnchanged. No ownership check on the cross-profile URI read.
560878423libimsstack.so SipMessageFraming::ParseMessageBodyCan't ReproUnchanged. A negative content length still passes a signed comparison into the completed-message state instead of being rejected.

Full audit: factory-image verification of closed reports

Baseline images: rango (Pixel 10 Pro Fold, build CP3A.260905.009) and yogi (Pixel 11 Pro Fold, build CD1A.260905.001.B1), both dated September 2026. Every row below is a closed report. Reports still open or under active coordinated disclosure are deliberately excluded and are not listed here.

TicketComponent / functionDeviceVerdictEvidence
-sit.stream.so DecodingBarringInfosblazer/rango (Tensor)STILL PRESENTByte-identical check-after-copy sequence at the reported offset.
-sit.stream.so FillCellInforangoSTILL PRESENTByte-identical; unconditional 2-byte read still precedes the length-validation call.
-libsitril.so OnReadPbEntryDoneblazerCANT-DETERMINEBinary differs between blazer and rango; needs a blazer image.
-vendor.radio.base.so DecodingNetworkNameFromSIMblazerCANT-DETERMINEDifferent binary/device; needs a blazer image.
-libsitril.so BuildSimAuthenticateblazerCANT-DETERMINESame cross-device issue as above.
555425202libmtkmipc-ril.so MIPC deserialize / onPcoInfoNotify TLV-length discardyogiSTILL PRESENTByte-identical at the cited addresses; TLV length still discarded.
561843639libmtkmipc-ril.so handleUssdIndyogiSTILL PRESENTByte-identical; incorrect TLV length field still used for the copy length.
-libimsmediahalproxy_mtk.so processRtpSessionReceivedDtmfIndyogiSTILL PRESENTByte-identical VLA-size mask, no upper-bound check added.
-sit.stream.so processSlicingConfigblazerCANT-DETERMINEAddress exceeds this image's binary size; needs a blazer image.
-android.hardware.samsung.uwb-service CalDataReadCmd::handleRspDatablazerCANT-DETERMINEBuild/layout differs from this image; needs a blazer image.
-cpif.ko pktproc_get_pkt_from_sktbuf_modeblazerSTILL PRESENT (high confidence)Same twin bound-check pattern (inclusive upper bound) found at the resolved symbol offset.
537918512libframesequence.so (WebP compositor)rango/yogiCANT-DETERMINELibrary rebuilt with a newer toolchain since the report; the specific function could not be relocated.
549662989PixelModemService.apk MintReceiver + guard permissionyogiPATCHEDThe guard permission MINT_CONFIGURATION_UPDATE_BROADCAST is now defined, signature|privileged.
560448300com.android.omadm.service DMService.TelephonyBroadcastReceiveryogiSTILL PRESENTExported, no permission, unchanged.
560448300com.google.android.configupdater (8 receivers)yogiSTILL PRESENTAll 8 receivers still exported, no permission.
560448300com.android.ons ONSProfileResultReceiveryogiSTILL PRESENTExported, no permission, unchanged.
561846309ConfigUpdater.apk PhenotypeHelper.getCertificate()yogiSTILL PRESENTByte-for-byte identical; no switch on config type, unlike its sibling methods.
552305734CrossDeviceAccessServicePrimary.apk PermissionBroadcastReceiveryogiSTILL PRESENTExported, no permission, unchanged.
558478942DCMO.apk DcmoReceiveryogiSTILL PRESENTExported, no permission, unchanged.
555981160telephony-common.jar ValueParser.retrieveItemsIconIdyogiSTILL PRESENTByte-exact; no lower-bound check before array allocation.
538367541 / 543174762telephony-common.jar WapPushOverSms.decodeWapPduyogiSTILL PRESENTUnchecked decoded value used directly as an allocation size.
538369053framework.jar PduParser.parsePartsyogiPATCHEDBound check added against available stream bytes before the allocation.
560866996framework.jar PduParser (same defect)yogiPATCHEDSame fix as above.
557568085TelephonyProvider.apk updateSynchronized()yogiSTILL PRESENTSelection string still concatenated with no parentheses around the caller-supplied fragment.
563677600WebAppServiceGoogle.apkyogiSTILL PRESENTExported, no permission, unchanged at the manifest level.
-AppDirectedSMSService.apk (Verizon)rangoPOSSIBLY PATCHED, unconfirmedThe receiver block does not appear in this image; needs direct re-diff before being called a confirmed fix.
565498857framework-res.apk EXECUTE_APP_FUNCTIONSyogiN/AThis image's protection level matches the report's own description of the safe baseline.
560453159service-ranging.jar RangingServiceImpl (UWB)yogiSTILL PRESENT on stableNo permission check on either affected method. The same fix exists on the Android 17 QPR2 beta track.
560453159CccDkTimeSyncService.apkyogiSTILL PRESENTExported, no permission, unchanged.
555984702grilservice.apk RadioConfig.readFromParcelyogiSTILL PRESENTRaw size field used directly as an allocation size, no upper bound.
552188030services.jar Vpn.setVpnForcedLocked()yogiSTILL PRESENTUID 0 still excluded from the VPN lockdown range.
552196751framework.jar NetworkCapabilities (architectural)rangoN/AGoogle has acknowledged this is an intentional design choice, not a defect.
558472707service-npumanager.jar NpuManagerServiceImpl.cancelModelLoadyogiSTILL PRESENTNo caller-identity check, unlike sibling methods on the same class.
540753210libpixelimsmedia.so RtcpFbPacket::decodeRtcpFbPacketyogiPATCHEDLength check added before the operation that previously underflowed unconditionally.
540755547libpixelimsmedia.so RtcpChunk::decodeRtcpChunkyogiPATCHEDBound check added on the SDES item length before the allocation and copy.
541096815libpixelimsmedia.so RtpDecoderNode::DecodeRtpHeaderExtensionyogiPATCHEDLength byte now read unsigned; new bound check added.
545948346libpixelimsmedia.so RtpSession::numberOfReportBlocksyogiPATCHEDUnderflow check added before the division that previously produced garbage.
540746953libimsstack.so SipMsgBody::EncodeSingleMsgBody / SetMsgBufferyogiSTILL PRESENTNo cap against the maximum message size anywhere in the call path.
560878423libimsstack.so SipMessageFraming::ParseMessageBodyyogiSTILL PRESENTSame signed-comparison logic at the function's new address; a negative content length is still accepted.
538314136IntentResolver.apk UriMetadataReaderImpl.getMetadata (DISPLAY_ICON_URI)yogiSTILL PRESENTNo ownership check on the cross-profile URI read; logic unchanged despite a refactor.
-rkpdapp.google.apk X509Utils.verifyCertChainyogiSTILL PRESENTCaller-supplied root used as the sole trust anchor, no hardcoded pin.
559533853pmgd (native Rust binary)yogiCANT-DETERMINEAggressive symbol stripping; the relevant function could not be identified.
537920968nfc_nci.st21nfc.default.so handlePollingLoopDatarangoSTILL PRESENTSame undersized length-check logic confirmed at the function's new address.
537923579nfc_nci.st21nfc.default.so FwUpdateHandlerrangonot rigorously checkedFunction located but no precise comparison pattern available.
538311587android.hardware.secure_element-service.uiccrangoSTILL PRESENTSame mutex/slot/type offsets, no per-call correlation token.
-PrebuiltBugle.apk MMS PDU decoderrangoSTILL PRESENTAllocation site still has no upper-bound check.
-android.hardware.samsung.uwb-service match_dev_ctrl_cmdrangoCANT-DETERMINEBuild differs from the reported device; needs the matching image.
529102332ShannonRcs.apk DebugBroadcastReceiverrangoSTILL PRESENTExported, no permission, unchanged.
-ShannonRcs.apk ScheduleAlarmReceiverrangoSTILL PRESENTExported, no permission, unchanged.
529518244ShannonRcs.apk DeviceProvisioningServicerangoPATCHEDService now requires a signature|privileged permission where none existed before.
-ShannonRcs.apk SipDelegateMessageInfoParser.getViaHeader (+siblings)rangoSTILL PRESENTHeader-index result used directly with no failure check.
-ShannonRcs.apk MultipartParser.parseMessage / ResourceListInfoDecoder.parserangoSTILL PRESENTNo array-length or null checks added.
545958850ShannonIms.apk RilIndCallComposerMt.toString()rangoSTILL PRESENTLength check against the decoded string's true size still missing.
-OemRilService.apk DataReader.getBytes(int)rangoSTILL PRESENTAllocation size unchecked.
-VendorSatelliteService.apk ByteUtil.primitiveArrayToIntrangoSTILL PRESENTNo length check before indexed array access.
541986810framework.jar RecoverySystem.verifyPackageyogiSTILL PRESENTCertificate selected for the trust check and the certificate actually used by signature verification can still differ.
547481980services.jar FileService$FileServiceStub.enqueueOperationyogiSTILL PRESENT (high confidence)No ownership/permission enforcement found before dispatch.
-telephony-common.jar WspTypeDecoder.seekXWapApplicationIdyogiSTILL PRESENTEmpty catch block unchanged; the decode loop can still spin indefinitely.
-ShannonRcs.apk SipDelegateMessageInfoParser.retrieveFeaturesFromSipMessagerangoSTILL PRESENTUnanchored substring match unchanged.
-IntentResolver.apk PayloadToggleCursorResolver (EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI)yogiPATCHED (not counted)New validation layer added, routing the URI through the system's real grant-permission check. Not counted among the 8: this specific vector does not map to a report I filed.
547477982bcmdhd4390.ko wl_cfgnan_parse_sd_attr_datarangonot conclusively checkedFunction location shifted; could not confirm whether this reflects a fix or unrelated drift.

Summary of the full audit

  • Confirmed fixed, no reward or credit: 8 (each with its own ticket number; one further fixed function, IntentResolver PayloadToggleCursorResolver, is listed but not counted because it does not map to a report I filed).
  • Confirmed still present, closed with reasoning that does not hold up: the Google VRP tickets in the table above, plus the Samsung and MediaTek submissions through the same program.
  • Could not be determined: a build/device mismatch, missing partition, or stripped binary prevented a reliable comparison; marked as such rather than guessed at.

This is not just me

In June 2026, The Register ran "Google told researcher 'Nice catch!' Then denied bug bounty for flaw it still hasn't fixed." Praise, then denial, then an unfixed bug. A separate researcher documented their own Google VRP case moving from "not a bug" to "not a vulnerability" to, finally, duplicate, meaning Google knew the whole time it was real. CSO Online has reported that legal experts believe these programs may raise labor-law questions, treating people who do the work of employees as disposable contractors whose finished work can be rejected with no explanation and no recourse.

I am not against Google. One of my own projects, Box, started as a fork of their AI Edge Gallery, and I have built on it every day for the past twelve months. Over that time Google has pulled features from my fork back into their own project, so the contribution has gone both ways and in good faith. This is specifically about what happened when I reported real, working security findings through their own stated process.