From 69088e0bebc64a01bea30628dd1cde14e43b13f4 Mon Sep 17 00:00:00 2001 From: Darkaxt Date: Sat, 29 Aug 2026 17:50:04 +0200 Subject: [PATCH] fix: avoid binary path false positives Co-Authored-By: Claude --- docs/reports/qa-hardening/stage-07-closure.md | 4 +- docs/reports/qa-hardening/stage-08-closure.md | 4 +- release/RELEASE_NOTES_1.1.0-rc.78-hotfix.3.md | 53 +++++++++++++++++++ release/compatibility-evidence.json | 2 +- tools/release/release-privacy.test.mjs | 22 ++++++++ .../validate-public-release-assets.mjs | 2 +- 6 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 release/RELEASE_NOTES_1.1.0-rc.78-hotfix.3.md diff --git a/docs/reports/qa-hardening/stage-07-closure.md b/docs/reports/qa-hardening/stage-07-closure.md index 550f5029..a2f91025 100644 --- a/docs/reports/qa-hardening/stage-07-closure.md +++ b/docs/reports/qa-hardening/stage-07-closure.md @@ -53,10 +53,10 @@ A post-run inventory comparison found the same 333 eligible names and one intent | Post-remediation consolidated Gradle gate | `BUILD SUCCESSFUL` in 40m36s; 65 tasks, including parser, catalog, CLI, runtime, companion, and app unit suites. | | Companion web gate | 32 Vitest files / 268 tests passed; TypeScript/Vite production build passed. | | Release/governance gate at source stabilization | 80 tests passed. | -| Current release/governance gate after closure packaging | 83 Node tests passed. | +| Current release/governance gate after closure packaging and APK privacy correction | 85 Node tests passed. | | Fresh corpus summarization | Streaming raw-report hash, source/generator lineage, canonical multiset, terminal outcomes, and persistence/reopen checks passed. | -The expensive parser and consolidated Gradle gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, acceptance-test stabilization, the later replacement of two Android API-33-only bounded reads with the shared API-30-compatible reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to use public nonsecret endpoints instead of an integration token lacking administration scope, or binding the comparison range to the cache decision's prior parser schema. Focused checkpoint/save tests, the source-contract regression, Android test compilation, app lint, the PR managed-device suite, and all 83 release-governance tests passed after those corrections. `NONPARSER_REUSE` is explicit and does not permit parser, catalog, build, wrapper, or corpus-execution changes. +The expensive parser and consolidated Gradle gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, acceptance-test stabilization, the later replacement of two Android API-33-only bounded reads with the shared API-30-compatible reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to use public nonsecret endpoints instead of an integration token lacking administration scope, binding the comparison range to the cache decision's prior parser schema, or tightening the Windows-path detector so arbitrary compressed APK bytes cannot be misclassified as a private path. Focused checkpoint/save tests, the source-contract regression, Android test compilation, app lint, the PR managed-device suite, the failed candidate's complete unsigned build and packaged Android gates, the reproduced APK privacy regression, and the release-governance tests passed after those corrections. `NONPARSER_REUSE` is explicit and does not permit parser, catalog, build, wrapper, or corpus-execution changes. ## Missing-feature classification diff --git a/docs/reports/qa-hardening/stage-08-closure.md b/docs/reports/qa-hardening/stage-08-closure.md index 9f327942..38392d59 100644 --- a/docs/reports/qa-hardening/stage-08-closure.md +++ b/docs/reports/qa-hardening/stage-08-closure.md @@ -46,7 +46,7 @@ The denominator correction does not waive an input. The audited physical invento | Gate | Evidence | Verdict | | --- | --- | --- | | Post-remediation Gradle matrix | Parser core/CLI, catalog, RetroArch, mapper, battle, companion core/server/simulator, and app unit tests: `BUILD SUCCESSFUL` in 40m36s, 65 tasks | `PASS` | -| Release/governance matrix after closure packaging | 83 Node tests | `PASS` | +| Release/governance matrix after closure packaging and APK privacy correction | 85 Node tests | `PASS` | | Companion browser unit/build matrix | 32 Vitest files / 268 tests; TypeScript/Vite build | `PASS` | | Portable browser E2E after closure packaging | `npm run test:e2e:ci`: 3 tests in 15.4s | `PASS` | | Packaged Android/WebView | Stage 3 source-bound GitHub managed-device run: 4/4 tests with immutable JUnit/screenshot evidence | `PASS` | @@ -54,7 +54,7 @@ The denominator correction does not waive an input. The audited physical invento | Final corpus | 333/333 eligible inputs terminal; 0 parser/catalog/compatibility/persistence errors; 278/278 selected catalogs persisted and reopened | `PASS` | | Downstream evidence hardening | Bounded 1.63 GB streaming summary, corrected denominator, duplicate-aware canonical contract, promotion/readiness/privacy regressions | `PASS` | -The consolidated Gradle and hours-long parser gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, deterministic acceptance-test corrections, the replacement of two Android API-33-only read calls with the already-tested API-30-compatible bounded reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to public nonsecret endpoints, or binding the release comparison range to the cache decision's prior parser schema. Focused checkpoint/save tests, source contracts, Android test compilation, app lint, the 7/7 PR managed-device suite, and the complete release-governance suite passed afterward. The evidence validator rejects reuse if parser/catalog sources, build logic, wrapper, or corpus-execution tooling changes. +The consolidated Gradle and hours-long parser gates ran once after parser/catalog product-source stabilization. They were not repeated for downstream release packaging, deterministic acceptance-test corrections, the replacement of two Android API-33-only read calls with the already-tested API-30-compatible bounded reader, repository-policy alignment with the established single-maintainer signing process, correction of GitHub policy reads to public nonsecret endpoints, binding the release comparison range to the cache decision's prior parser schema, or tightening the Windows-path detector so arbitrary compressed APK bytes cannot be misclassified as a private path. Focused checkpoint/save tests, source contracts, Android test compilation, app lint, the 7/7 PR managed-device suite, the failed candidate's complete unsigned build and packaged Android gates, the reproduced APK privacy regression, and the release-governance suite passed afterward. The evidence validator rejects reuse if parser/catalog sources, build logic, wrapper, or corpus-execution tooling changes. No ad hoc emulator, ADB gesture, physical-device action, credential inspection, signing-material inspection, signing, tagging, or publication was performed for this closure. diff --git a/release/RELEASE_NOTES_1.1.0-rc.78-hotfix.3.md b/release/RELEASE_NOTES_1.1.0-rc.78-hotfix.3.md new file mode 100644 index 00000000..24e90cb8 --- /dev/null +++ b/release/RELEASE_NOTES_1.1.0-rc.78-hotfix.3.md @@ -0,0 +1,53 @@ +# DualDex 1.1.0-rc.78-hotfix.3 + +RC78 hotfix 3 is the release candidate for the completed project-wide QA hardening program. It closes the remaining Android setup, parser/catalog, runtime authority, companion transport, privacy, and release-governance gaps, then binds them to the final source-bound compatibility corpus. The hotfix qualifier preserves the immutable failed RC78 tags while keeping cache validation bound to the correct prior parser-schema baseline and preventing arbitrary compressed APK bytes from being misclassified as private Windows paths. + +## Android setup and recovery + +- Recover cleanly when a guide cannot be loaded instead of crashing the app or leaving the guide surface in a stale state. +- Reconcile direct-storage and folder-picker access without discarding the last valid index during a failed rescan. +- Quarantine revoked folder grants, route package-specific settings safely, and explain protected `Android/data` and `Android/obb` limitations accurately. +- Deliver overlay picker results exactly once across cold starts, new intents, retries, and activity recreation. +- Keep Area Guide projection failures local to that optional module. + +## Parser and catalog resilience + +- Bound complete-ROM probes, archive extraction, catalog payloads, concurrent corpus work, and detached Gen I species discovery. +- Add cancellation checks to long parser passes and replenish ordered corpus work after any completion so one slow input cannot stall unrelated inputs. +- Verify catalog identity and canonical content digests before activation, quarantine invalid snapshots, and fail closed on malformed optional data. +- Invalidate pre-hardening parser catalogs through schema revision 46 and retain seeded rebuild coverage. + +## Runtime authority and companion safety + +- Require verified ROM identity and monotonic session epochs before live memory or SaveRAM state can become authoritative. +- Fence queued mapper, socket, command, delayed-reply, and checkpoint work against stale sessions. +- Recover RetroArch configuration and command sockets transactionally while bounding memory reads, UDP drains, and retained snapshots. +- Bound Android and desktop companion transport, fence navigation/state/media responses, preserve structured retries, and isolate optional feature failures. +- Remove private paths, reversible player-state fingerprints, raw failures, stacks, workspace identifiers, and device identifiers from normal diagnostics and public evidence. + +## Source-bound QA closure + +- Audit 334 supported-extension files while evaluating all 333 scanner-eligible mainline and hack inputs; one known spin-off remains intentionally excluded by scanner policy. +- Reach terminal parser outcomes for 333/333 inputs: 278 selected, 2 ambiguous, 53 without a family match, and 0 parser errors. +- Record 20 complete, 302 partial, and 11 unresolved data-compatibility outcomes with 0 compatibility errors. +- Materialize, persist, close, reopen, and decode all 278 selected catalogs with 0 catalog or persistence errors. +- Close QA Stages 7 and 8 with zero blockers and zero referrals. + +## Measured validation + +- Post-remediation Kotlin and Android gate: 65 tasks passed in 40m36s across parser, catalog, CLI, runtime, companion, and app unit suites. +- Companion web gate: 32 Vitest files and 268 tests passed; the TypeScript/Vite production build passed. +- Portable Chromium acceptance: 3/3 Playwright tests passed. +- Release and governance gate: 85/85 Node tests passed. +- Public QA evidence: 7/7 assets passed structural privacy validation. +- PR Android acceptance: 7/7 managed-device tests passed; min-SDK-30 app lint and focused bounded-read tests passed after the final compatibility correction. +- Public nonsecret repository-policy reads were verified against the live tag and environment configuration without querying signing material. +- Cache validation selects the latest prior tag whose parser schema matches the explicit cache decision, so failed immutable delivery tags cannot replace the evidence baseline. +- Public-asset validation still rejects complete private paths embedded in binary payloads while ignoring isolated drive-prefix byte sequences in compressed APK data. +- Protected release managed-device acceptance, signing, and Thor validation remain mandatory before candidate promotion. + +## Delivery + +- This candidate uses Android version code `1010081`. +- The candidate is built and signed only through the protected GitHub Actions environment; production signing material is never exposed to the repository or local workspace. +- DualDex remains read-only and sends no game commands or emulator-memory writes. diff --git a/release/compatibility-evidence.json b/release/compatibility-evidence.json index 4860003e..b7e416ff 100644 --- a/release/compatibility-evidence.json +++ b/release/compatibility-evidence.json @@ -12,7 +12,7 @@ }, "scopeDecision": { "type": "NONPARSER_REUSE", - "attestation": "Fresh raw parser evidence was generated at the named source commit; downstream release packaging, acceptance tests, API-30-compatible Android bounded reads, repository-policy alignment, public nonsecret GitHub policy access, and schema-bound comparison selection changed afterward without changing parser, catalog, build, wrapper, or corpus-execution source." + "attestation": "Fresh raw parser evidence was generated at the named source commit; downstream release packaging, acceptance tests, API-30-compatible Android bounded reads, repository-policy alignment, public nonsecret GitHub policy access, schema-bound comparison selection, and binary-safe public-asset privacy validation changed afterward without changing parser, catalog, build, wrapper, or corpus-execution source." }, "cacheDecision": { "type": "BUMP_REQUIRED", diff --git a/tools/release/release-privacy.test.mjs b/tools/release/release-privacy.test.mjs index ab2749bb..eaed767f 100644 --- a/tools/release/release-privacy.test.mjs +++ b/tools/release/release-privacy.test.mjs @@ -72,6 +72,28 @@ test("rejects Windows backslash and forward-slash absolute paths and Unix home p } }); +test("ignores isolated drive-prefix bytes in binary APK payloads", () => { + for (const binaryFragment of [ + Buffer.from([0xff, 0x0a, 0x0a, 0x59, 0x3a, 0x2f, 0x0e, 0x60, 0x0a]), + Buffer.from([0xff, 0x6f, 0x3a, 0x2f, 0x44, 0x5b, 0xff, 0x37]), + Buffer.from([0x47, 0xff, 0x6b, 0x3a, 0x2f, 0x7a, 0xff, 0x6d]), + ]) { + validatePublicReleaseAsset({ name: "DualDex-candidate.apk", bytes: binaryFragment }); + } +}); + +test("still rejects a complete private path embedded in a binary payload", () => { + const bytes = Buffer.concat([ + Buffer.from([0xff, 0x00]), + Buffer.from("C:\\Users\\local-user\\project"), + Buffer.from([0x00, 0xff]), + ]); + assert.throws( + () => validatePublicReleaseAsset({ name: "DualDex-candidate.apk", bytes }), + /private path/i, + ); +}); + test("rejects local device and workspace identifiers without returning their values", () => { for (const privateText of [ "deploymentTarget=local-device", diff --git a/tools/release/validate-public-release-assets.mjs b/tools/release/validate-public-release-assets.mjs index 76c7b089..1fc69710 100644 --- a/tools/release/validate-public-release-assets.mjs +++ b/tools/release/validate-public-release-assets.mjs @@ -4,7 +4,7 @@ import { basename, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const PRIVATE_PATH_PATTERNS = [ - /\b[A-Za-z]:[\\/]/, + /\b[A-Za-z]:[\\/][\p{L}\p{N}._ -]{1,128}[\\/]/u, /\/(?:home|Users|private|tmp|var\/tmp)\/[A-Za-z0-9._-]+[\\/]/, /\/(?:data\/user|storage\/emulated|sdcard)\//, ];