Skip to content

Package format

A mini app ships as a single .sapp file. It is a ZIP archive with a signature block inserted before the central directory, and it borrows its structure from formats that have already survived adversarial contact: Android’s APK Signature Scheme, Chrome’s CRX3, and fs-verity.

┌─────────────────────────────────────────────┐
│ ZIP entries │
│ manifest.json ← covered by sig │
│ index.html │
│ assets/… │
│ sbom.cdx.json │
├─────────────────────────────────────────────┤
│ Sollar signature block │
│ • signer chain (lineage) │
│ • Ed25519 signature │
│ • ECDSA P-256 signature │
│ • Merkle root over content │
│ • countersignature (store or org CA) │
├─────────────────────────────────────────────┤
│ ZIP central directory │
│ End of central directory │
└─────────────────────────────────────────────┘

Placing the signature block before the central directory is the APK v2 arrangement. It means the signature covers the archive’s bytes rather than its parsed entries, which closes the class of attack where a verifier and an extractor disagree about what the archive contains.

ProblemSolutionPrecedent
Verifier and extractor parse differentlySign the byte range, not the entry listAPK Signature Scheme v2
Signing key must be replaced without breaking installsSigner lineage: the old key signs the new key’s authorityAPK Signature Scheme v3
Whole-file hash means reading the whole file before first paintMerkle tree — verify each block as it is readAPK v4 / fs-verity
One algorithm breaks and everything installed is unverifiableTwo independent signatures, both requiredCRX3
An attacker publishes under someone else’s identifierThe app id is derived from the public keyCRX3
A dependency turns out to be vulnerable and nobody knows who shipped itCycloneDX SBOM inside the package

Two signatures, both required, over the same content:

  • Ed25519 — fast, small, no parameter choices to get wrong.
  • ECDSA P-256 — broad hardware-backed keystore support on both platforms.

A package that verifies under only one is refused. This is CRX3’s reasoning: the cost of carrying a second algorithm is a few hundred bytes, and the benefit is that a break in either curve does not strand every installed app.

Terminal window
sollar build # produces dist/com.acme.erp-2.4.1.sapp
sollar sign --key <keyref> # adds the signature block

sollar sign --keyless uses Sigstore: an ephemeral key, an OIDC identity, and a transparency-log entry. Nothing to store, nothing to leak, and a public record of who signed what. Available for both channels; recommended for CI.

The developer signature proves authorship. It is not enough to install.

ChannelCountersigned by
storeThe Sollar store key, after review
tenant-privateThe organisation’s CA

The client refuses a package with no countersignature from one of the two. Tenant-private apps skip Sollar’s review; they do not skip the cryptography.

The signer lineage is a chain: each new key is authorised by a signature from the previous one. A client that has installed version 2.4.1 signed by key A will accept 2.5.0 signed by key B, because B’s authority is proved by A within the package.

Losing a signing key with no lineage entry means the app cannot be updated by anyone, including you. Rotate deliberately, and keep the lineage.

Full and delta, both supported.

  • Delta — a bsdiff patch against a specific prior version. The client falls back to a full download when it holds a version with no patch path.
  • Merkle verification — the client verifies blocks as it reads them, so a mini app starts before the whole package is hashed.
  • Monotonic versions — the client accepts only a higher version than the one installed. This is what makes rollback work as a new version, not a reversal; see Distribution.
LimitNote
Package size8 MBThe main package; enforced at submission
Total with subpackages24 MBLazily loaded parts
Single file4 MB
Manifest256 KB
actions[]64 per appAn agent given hundreds of tools chooses badly
network.connect32 hostsA list longer than this is not an allowlist

Size limits exist because a mini app that takes as long as an app-store download has lost the only advantage it had.

sbom.cdx.json, CycloneDX 1.7, generated at build:

Terminal window
sollar build --sbom

Required for the store channel, recommended for tenant-private. The submission gate scans it against known-vulnerability data, and a high-severity match in a shipped dependency blocks the release.

The SBOM is what makes the question “which mini apps ship the vulnerable version of this library” answerable in minutes rather than by asking every developer.

No remote code. The Content Security Policy floor is script-src 'self' with no remote host and no 'unsafe-eval' — every executable byte arrives inside the verified package.

This is a security property and a legal one. Google Play’s Device and Network Abuse policy exempts from its downloaded-code prohibition anything running “in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser)”. A mini app pulling a script from a CDN is downloading executable code from outside the verified package, which is exactly what the exemption does not cover. See Google Play.

A third-party resource that genuinely must be remote requires Subresource Integrity with a pinned hash.