Skip to content

ref: Remove shell and OS utility dependencies from the app - #692

Draft
oioki wants to merge 4 commits into
mainfrom
alextarasov/sec-1090-launchpad
Draft

oioki wants to merge 4 commits into
mainfrom
alextarasov/sec-1090-launchpad

Conversation

@oioki

@oioki oioki commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Remove every dependency on a shell and on OS utilities that a distroless base image does not ship: zip, unzip, file(1), and the sh/bash launcher scripts that wrap the bundletool and apksigner jars.

This is the first step of moving launchpad onto a Docker Hardened Image (SEC-1090). It is independent of the base image change and safe to land on its own: everything here behaves identically on the current python:3.14-slim-bookworm image, and it unblocks the multi-stage DHI Dockerfile that follows.

The commits are ordered by how much discussion they are likely to need, so they can be reviewed — or dropped — separately:

  1. build: exec-form container healthchecks, and the IPA test reads the archive with zipfile. Mechanical.
  2. ref(android): run the jars with java -jar instead of through their shell launchers, and stop installing those launchers in scripts/deps. Same JVM, same jars, same flags. Note for local dev: after re-running scripts/deps, bundletool and apksigner are no longer on PATH as commands; only the jars are installed.
  3. ref(size): zip_directory() replaces zip -r -y for IPA generation, and file types for extensionless files are sniffed from magic bytes instead of file(1). Unrecognised files now report unknown rather than libmagic's free-text description; nothing downstream matched on that text.
  4. ref(size): the Apple download-size estimate no longer parses unzip -v output. This is the one worth a careful look — it touches a customer-visible number. Info-ZIP writes UT and ux extra fields into every header (52 bytes per entry across the local and central records) which counted toward the metadata size, so the constant is added back explicitly to keep reported download sizes byte-identical. The alternative is to drop the constant and accept a one-time shift of a few KB in reported sizes; that felt worse mid-rollout, when base and head builds would be produced by different images, but I am happy to change it.

Verified: the full unit and integration suite passes at each of the four commits, and the affected tests also pass inside the built Linux image (docker build --build-arg TEST_BUILD=true), which exercises the real java -jar paths.

Refs SEC-1090

@linear-code

linear-code Bot commented Sep 21, 2026

Copy link
Copy Markdown

SEC-1090

@sentry

sentry Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Size Analysis

1 component analyzed, 1 component processing

iOS Builds

Name Configuration Version Download Size Install Size
HackerNews (iOS)
com.emergetools.hackernews
Release 3.8 (1) 6.5 MB (N/A) 9.7 MB (N/A)

Android Builds

Name Configuration Version Download Size Uncompressed Size
Hacker News (Android)
com.emergetools.hackernews
Release 1.0.2 (13) Processing... (-) Processing... (-)

Configure launchpad-test-ios status check rules

@sentry

sentry Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

📲 Install Builds

iOS

🔗 App Name App ID Version Configuration
HackerNews com.emergetools.hackernews 3.8 (1) Release

Android

🔗 App Name App ID Version Configuration
Hacker News com.emergetools.hackernews 1.0.2 (13) Release

⚙️ launchpad-test-android Build Distribution Settings

oioki and others added 3 commits September 28, 2026 21:49
The container healthchecks used CMD-SHELL and the IPA integration test shelled
out to unzip. Neither exists in a distroless image. Use exec-form healthchecks
that probe the health file with python3, and read the generated IPA with
zipfile. Behaviour is unchanged on the current image.

Refs SEC-1090
Co-Authored-By: Claude <noreply@anthropic.com>
Both tools were launched through the shell wrappers that ship with them: a
generated /bin/sh shim for bundletool and the upstream /bin/bash launcher for
apksigner. A distroless image has no shell, so run the jars with java -jar
instead and stop installing the wrappers. apksigner keeps the -Xmx1024M its
launcher passed. keytool is resolved next to the java we picked.

Refs SEC-1090
Co-Authored-By: Claude <noreply@anthropic.com>
IPA generation ran `zip -r -y` and extensionless files were typed with file(1);
neither binary is present in a distroless image.

Add zip_directory(), which reproduces what Info-ZIP wrote for us: symlink
entries, unix permissions and explicit directory entries. Detect file types from
magic bytes instead, covering the categories the previous mapping recognised
(Mach-O, ELF and shebangs, Hermes bytecode, text, empty, symlink, directory).
Anything else is reported as unknown, where before libmagic's free-text
description was passed through; nothing downstream matched on that text.

Refs SEC-1090
Co-Authored-By: Claude <noreply@anthropic.com>
@oioki
oioki force-pushed the alextarasov/sec-1090-launchpad branch from b354e08 to c255464 Compare September 28, 2026 19:51
The Apple download size estimate zipped the bundle with `zip -q -r` and parsed
the totals out of `unzip -v` output. Neither binary exists in a distroless
image, so build the archive with zip_directory() and sum the compressed sizes
from the central directory.

Info-ZIP also writes UT and ux extra fields into every header, 52 bytes per
entry across the local and central records. Those bytes counted as metadata
before, so add them back explicitly: reported download sizes stay identical and
size comparisons spanning this change do not shift.

Refs SEC-1090
Co-Authored-By: Claude <noreply@anthropic.com>
@oioki
oioki force-pushed the alextarasov/sec-1090-launchpad branch from c255464 to ac2248d Compare September 28, 2026 20:38
metadata_size = total_zip_size - total_compressed
# Metadata is everything that isn't payload: headers, names, central directory, EOCD.
# Compressed bytes sit in both terms and cancel, so the compression level can't skew this.
metadata_size = total_zip_size - total_compressed + _INFOZIP_EXTRA_FIELD_BYTES_PER_ENTRY * len(infos)

@oioki oioki Sep 28, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This _INFOZIP_EXTRA_FIELD_BYTES_PER_ENTRY * len(infos) is a correction term to keep the stats intact after moving from zip binary to pythonic zipfile module. Maybe we could drop it and allow one-time drift for more correct reporting?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant