[GH-ISSUE #2563] fix(apktool): exclude configuration qualifiers from discovered locales #880

Closed
opened 2026-10-15 18:29:15 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2563

Summary

Decoding an APK that has strings tables under Android configuration directories reports those directory suffixes as available locales. The returned locale list can therefore include entries such as night and v21, which are not language locales and cannot be used as valid locale requests. This affects locale discovery in the Apktool resource result.

Steps to reproduce

On the baseline under Runtime or environment, create a temporary focused test beside src/apktool/ApktoolProvider.test.ts using the fixture below, run npm run test:focused -- src/apktool/resourceQualifierProbe.test.ts, and remove the temporary test. The launcher stub writes decoded resource directories into the provider's workspace; it does not invoke a real Apktool binary.

import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import { ApktoolProvider } from "./ApktoolProvider.js";
import { apktoolRequestSchema } from "../domain/apktool/apktoolResourceAnalysis.js";

test("resource qualifiers appear in locale discovery", async () => {
  const root = await mkdtemp(join(tmpdir(), "resource-qualifiers-"));
  try {
    const command = join(root, "apktool");
    await writeFile(command, [
      "#!/bin/sh",
      "if [ \"$1\" = \"--version\" ]; then printf '2.7.0\\n'; exit 0; fi",
      "if [ \"$1\" = \"d\" ]; then",
      "  printf 'versionInfo:\\n  versionName: 1.0\\n' > \"$5/apktool.yml\"",
      "  for name in values values-car values-de values-night values-v21; do",
      "    mkdir -p \"$5/res/$name\"",
      "    printf '<resources/>' > \"$5/res/$name/strings.xml\"",
      "  done",
      "  printf '<resources><string name=\"kind\">car</string></resources>' > \"$5/res/values-car/strings.xml\"",
      "  exit 0",
      "fi",
      "exit 1",
    ].join("\n"), { mode: 0o755 });
    const apk = join(root, "input.apk");
    await writeFile(apk, "apk");
    const provider = new ApktoolProvider({ environment: { REA_APKTOOL_COMMAND: command } });
    const result = await provider.execute({ operation: "decode_android_resources", input: { path: apk, include_strings: true } });
    if (!result.ok) throw result.error;
    expect((result.value.result as { locales: string[] }).locales).toEqual(["car", "de", "night", "v21"]);
    for (const locale of ["night", "v21"]) {
      expect(apktoolRequestSchema.safeParse({ operation: "decode_android_resources", input: { path: apk, include_strings: true, locale } }).success).toBe(false);
    }
    expect(apktoolRequestSchema.safeParse({ operation: "decode_android_resources", input: { path: apk, include_strings: true, locale: "car" } }).success).toBe(true);
    const car = await provider.execute({ operation: "decode_android_resources", input: { path: apk, include_strings: true, locale: "car" } });
    if (!car.ok) throw car.error;
    expect((car.value.result as { strings: unknown[] }).strings).toEqual([{ name: "kind", value: "car" }]);
    await provider.close();
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

Expected behavior

The locale list should identify available locale-specific strings tables, so this fixture should return only de. Configuration tables for car mode, night mode, and API level should not appear as locales. A caller can then use each listed locale in the resource decode request.

Actual behavior

An independent focused run through ApktoolProvider.execute returned locales: ["car", "de", "night", "v21"] from a fixture that also included a values-car table. The request schema rejected night and v21; it accepted car, and requesting car read that table. The decoder behavior was observed with a launcher stub; behavior of a real Apktool binary on a particular APK was not tested.

Evidence

  • Expected source: docs/apktool-resource-analysis.md:44-47,62 describes locale selection and says locales lists values-<locale> strings tables; src/domain/apktool/apktoolResourceAnalysis.ts:22-33 defines accepted request locales.
  • Failure source: src/apktool/ApktoolProvider.ts:263-297 adds every values-* suffix with a strings.xml file to the locale list, and src/apktool/ApktoolProvider.ts:656-700 returns that list with the decode result; the focused provider execution returned the configuration suffixes.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete
  • Related context: Android analysis roadmap #717 covers resource inspection broadly; it does not report this classification failure.

Restoration check

For the same decoded fixture, locales should omit car, night, and v21 while retaining de. Keep the de control so an empty or suppressed locale list cannot pass. A request for de should still project its strings table.

Runtime or environment

Failure verified at a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and still present at 8b181716aca2cb467be499cba226abfb5ae19141; Node v26.10.0 and npm 11.19.1. The observed run used a simulated decoded workspace and the focused test runner.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A valid decode can give incorrect locale discovery for APKs with configuration-specific strings tables, causing callers to choose invalid locale values; this affects a bounded portion of resource analysis, and frequency across real APKs was not measured.
  • Workaround status: unverified
  • Workaround basis: Requesting an already known locale can retrieve its table, but that does not recover accurate locale discovery; no complete safe filtering procedure was verified.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2563 ## Summary Decoding an APK that has strings tables under Android configuration directories reports those directory suffixes as available locales. The returned locale list can therefore include entries such as `night` and `v21`, which are not language locales and cannot be used as valid locale requests. This affects locale discovery in the Apktool resource result. ## Steps to reproduce On the baseline under Runtime or environment, create a temporary focused test beside `src/apktool/ApktoolProvider.test.ts` using the fixture below, run `npm run test:focused -- src/apktool/resourceQualifierProbe.test.ts`, and remove the temporary test. The launcher stub writes decoded resource directories into the provider's workspace; it does not invoke a real Apktool binary. ```ts import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; import { ApktoolProvider } from "./ApktoolProvider.js"; import { apktoolRequestSchema } from "../domain/apktool/apktoolResourceAnalysis.js"; test("resource qualifiers appear in locale discovery", async () => { const root = await mkdtemp(join(tmpdir(), "resource-qualifiers-")); try { const command = join(root, "apktool"); await writeFile(command, [ "#!/bin/sh", "if [ \"$1\" = \"--version\" ]; then printf '2.7.0\\n'; exit 0; fi", "if [ \"$1\" = \"d\" ]; then", " printf 'versionInfo:\\n versionName: 1.0\\n' > \"$5/apktool.yml\"", " for name in values values-car values-de values-night values-v21; do", " mkdir -p \"$5/res/$name\"", " printf '<resources/>' > \"$5/res/$name/strings.xml\"", " done", " printf '<resources><string name=\"kind\">car</string></resources>' > \"$5/res/values-car/strings.xml\"", " exit 0", "fi", "exit 1", ].join("\n"), { mode: 0o755 }); const apk = join(root, "input.apk"); await writeFile(apk, "apk"); const provider = new ApktoolProvider({ environment: { REA_APKTOOL_COMMAND: command } }); const result = await provider.execute({ operation: "decode_android_resources", input: { path: apk, include_strings: true } }); if (!result.ok) throw result.error; expect((result.value.result as { locales: string[] }).locales).toEqual(["car", "de", "night", "v21"]); for (const locale of ["night", "v21"]) { expect(apktoolRequestSchema.safeParse({ operation: "decode_android_resources", input: { path: apk, include_strings: true, locale } }).success).toBe(false); } expect(apktoolRequestSchema.safeParse({ operation: "decode_android_resources", input: { path: apk, include_strings: true, locale: "car" } }).success).toBe(true); const car = await provider.execute({ operation: "decode_android_resources", input: { path: apk, include_strings: true, locale: "car" } }); if (!car.ok) throw car.error; expect((car.value.result as { strings: unknown[] }).strings).toEqual([{ name: "kind", value: "car" }]); await provider.close(); } finally { await rm(root, { recursive: true, force: true }); } }); ``` ## Expected behavior The locale list should identify available locale-specific strings tables, so this fixture should return only `de`. Configuration tables for car mode, night mode, and API level should not appear as locales. A caller can then use each listed locale in the resource decode request. ## Actual behavior An independent focused run through `ApktoolProvider.execute` returned `locales: ["car", "de", "night", "v21"]` from a fixture that also included a `values-car` table. The request schema rejected `night` and `v21`; it accepted `car`, and requesting `car` read that table. The decoder behavior was observed with a launcher stub; behavior of a real Apktool binary on a particular APK was not tested. ## Evidence - Expected source: `docs/apktool-resource-analysis.md:44-47,62` describes locale selection and says `locales` lists `values-<locale>` strings tables; `src/domain/apktool/apktoolResourceAnalysis.ts:22-33` defines accepted request locales. - Failure source: `src/apktool/ApktoolProvider.ts:263-297` adds every `values-*` suffix with a `strings.xml` file to the locale list, and `src/apktool/ApktoolProvider.ts:656-700` returns that list with the decode result; the focused provider execution returned the configuration suffixes. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete - Related context: [Android analysis roadmap #717](https://github.com/morluto/rea/issues/717) covers resource inspection broadly; it does not report this classification failure. ## Restoration check For the same decoded fixture, `locales` should omit `car`, `night`, and `v21` while retaining `de`. Keep the `de` control so an empty or suppressed locale list cannot pass. A request for `de` should still project its strings table. ## Runtime or environment Failure verified at `a2d8b94dd50dab8d966239dd63ab84718aa8b4b4` and still present at `8b181716aca2cb467be499cba226abfb5ae19141`; Node v26.10.0 and npm 11.19.1. The observed run used a simulated decoded workspace and the focused test runner. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A valid decode can give incorrect locale discovery for APKs with configuration-specific strings tables, causing callers to choose invalid locale values; this affects a bounded portion of resource analysis, and frequency across real APKs was not measured. - Workaround status: unverified - Workaround basis: Requesting an already known locale can retrieve its table, but that does not recover accurate locale discovery; no complete safe filtering procedure was verified.
bsakaguchi 2026-10-15 18:29:15 +00:00
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Addressed on main in d4bd65c53.

Apktool locale discovery uses the shared locale parser and excludes non-locale configuration qualifiers such as car, night and v21.

Validation: 22 Apktool boundary cases passed, including configuration-only directories.

Verification scope: No real Apktool engine run was performed in this audit.

<!-- gh-comment-id:6158683775 --> @morluto commented on GitHub (Oct 14, 2026): Addressed on main in [d4bd65c53](https://github.com/morluto/rea/commit/d4bd65c53). Apktool locale discovery uses the shared locale parser and excludes non-locale configuration qualifiers such as car, night and v21. Validation: 22 Apktool boundary cases passed, including configuration-only directories. Verification scope: No real Apktool engine run was performed in this audit.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#880
No description provided.