* fix(artifacts): describe ZIP non-entry coverage accurately * fix(process): report no remaining time after a provider deadline fires Node timers can fire while performance.now() is still a fraction of a millisecond short of the monotonic deadline. ProviderStartupDeadline then reported interruption "timeout" and an aborted signal while remainingMs() returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0 (Hopper and Ghidra startup) could run past an elapsed deadline, and the real-timer test "aborts a bounded wait when its real monotonic deadline expires" failed intermittently in CI (expected 1 to be 0). Once the deadline's own timer has classified a timeout, report zero remaining time. A deterministic regression test fires the timer with the monotonic clock 0.4 ms short of the deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style: format comparison and IDA sources --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
2.7 KiB
TypeScript
64 lines
2.7 KiB
TypeScript
/** ZIP-backed application package families that share the hardened archive reader. */
|
|
export const ZIP_PACKAGE_FORMATS = [
|
|
"zip",
|
|
"ipa",
|
|
"apk",
|
|
"msix",
|
|
"appx",
|
|
"hap",
|
|
"hsp",
|
|
"app-pack",
|
|
] as const;
|
|
export type ZipPackageFormat = (typeof ZIP_PACKAGE_FORMATS)[number];
|
|
|
|
/** All ZIP package families share archive ownership and inventory coverage. */
|
|
export const isZipPackageFormat = (
|
|
format: string,
|
|
): format is ZipPackageFormat =>
|
|
ZIP_PACKAGE_FORMATS.some((candidate) => candidate === format);
|
|
|
|
/** Recognize local-header, empty-archive, or split-archive ZIP signatures, not archive validity. */
|
|
export const hasZipSignature = (bytes: Uint8Array): boolean =>
|
|
bytes.length >= 4 &&
|
|
bytes[0] === 0x50 &&
|
|
bytes[1] === 0x4b &&
|
|
((bytes[2] === 0x03 && bytes[3] === 0x04) ||
|
|
(bytes[2] === 0x05 && bytes[3] === 0x06) ||
|
|
(bytes[2] === 0x07 && bytes[3] === 0x08));
|
|
|
|
/**
|
|
* Classify a ZIP-backed package by its complete lower-cased path suffix.
|
|
*
|
|
* This is deliberately extension-only. Callers must separately verify ZIP
|
|
* magic before trusting the classification for a root input. HarmonyOS `.har`
|
|
* is intentionally absent: the suffix also names HTTP Archive JSON, so a
|
|
* Harmony shared archive is only established from verified ZIP bytes. The
|
|
* HarmonyOS App Pack suffix `.app` is also absent here: it applies only to
|
|
* root inputs (see `classifyAndHashRoot`), because a nested ZIP entry ending
|
|
* in `.app` inside another package, such as an IPA, is not an App Pack.
|
|
*/
|
|
export const zipPackageFormatForPath = (
|
|
path: string,
|
|
): ZipPackageFormat | undefined => {
|
|
const lower = path.toLowerCase();
|
|
if (lower.endsWith(".zip")) return "zip";
|
|
if (lower.endsWith(".ipa")) return "ipa";
|
|
// An Android App Bundle is a ZIP archive, not a standalone APK.
|
|
if (lower.endsWith(".apk")) return "apk";
|
|
if (lower.endsWith(".msix") || lower.endsWith(".msixbundle")) return "msix";
|
|
if (lower.endsWith(".appx") || lower.endsWith(".appxbundle")) return "appx";
|
|
if (lower.endsWith(".hap")) return "hap";
|
|
if (lower.endsWith(".hsp")) return "hsp";
|
|
return undefined;
|
|
};
|
|
|
|
/** Inventory lists entries, not all bytes surrounding the central directory. */
|
|
export const ZIP_NON_ENTRY_LIMITATION =
|
|
"ZIP inventory lists central-directory entries only. Non-entry bytes, including an APK Signing Block before the central directory or a ZIP comment after it, are not occurrences.";
|
|
|
|
/** Recognize the same inventory scope in current and previously retained Evidence. */
|
|
export const isZipInventoryScopeLimitation = (limitation: string): boolean =>
|
|
limitation === ZIP_NON_ENTRY_LIMITATION ||
|
|
limitation ===
|
|
"ZIP inventory lists central-directory entries only. Bytes after the central directory, including an APK Signing Block or a ZIP comment, are not occurrences.";
|