Swift Package Registry (SE-0292)
How LPM implements the SE-0292 Swift Package Registry spec — identity mapping, signing, certificates.
LPM implements the SE-0292 Swift Package Registry API on its registry side. Once you've run lpm swift-registry once, Swift Package Manager (SPM) on your machine can resolve LPM-hosted Swift packages natively — no manual fork-as-git-dep, no shim.
This page is the conceptual reference. For the user-facing setup walkthrough, see Using LPM with Swift.
What SE-0292 specifies
SE-0292 defines a REST API for Swift package registries that SPM can talk to in lieu of a git URL. It covers:
- Listing versions for a package (
GET /{scope}/{name}) - Fetching a version's source archive (
GET /{scope}/{name}/{version}.zip) - Fetching a version's
Package.swiftmanifest (GET /{scope}/{name}/{version}/Package.swift) - Login (
POST /login) - Optional package signing via CMS
LPM implements all of the above plus signature verification metadata. SPM clients see a standard SE-0292 server.
Identity mapping
LPM packages are named @lpm.dev/owner.pkg-name. SE-0292 scopes can't contain ., so LPM maps:
| LPM name | SE-0292 identity |
|---|---|
@lpm.dev/owner.pkg-name | lpmdev.owner_pkg-name |
@lpm.dev/myorg.swift-utils | lpmdev.myorg_swift-utils |
Rules:
- The LPM scope
@lpm.dev/becomes the SE-0292 scopelpmdev(no dots, no@). - The
_between owner and package name is the unambiguous separator. LPM forbids_in both owner and package name (enforced at the storage layer), so the boundary is always clear even when either half contains hyphens.@lpm.dev/swift-server.async-http-clientand@lpm.dev/swift.server-async-http-clientmap to two distinct identifiers (lpmdev.swift-server_async-http-clientvslpmdev.swift_server-async-http-client) — no ambiguity.
Identity translation is automatic — lpm install @lpm.dev/owner.swift-pkg writes the SE-0292 identity into Package.swift. You don't deal with the mapping by hand.
Setup: lpm swift-registry
lpm swift-registry # idempotent
lpm swift-registry --force # re-download the signing certificate (cert rotation)Four steps under the hood. Every step is fail-loud — if any one of them returns a non-success exit, the command aborts instead of completing with broken signing:
- Set the registry for the
lpmdevscope.swift package-registry set --scope lpmdev https://lpm.dev/api/swift-registry - Log in. Uses the LPM bearer token resolved through the standard session manager (
LPM_TOKEN→ keychain → OIDC → none). - Install the signing certificate. Downloads the CMS signing cert (DER) from
https://lpm.dev/api/swift-registry/certificateand writes it to SPM's trust store at~/.swiftpm/security/trusted-root-certs/lpm.der. The DER body is size-checked before being written; an empty / truncated / HTML-error response aborts instead of leaving a broken cert on disk. - Configure the SPM signing trust policy. Writes
~/.swiftpm/configuration/registries.jsonwith a defaultsigning.onUnsigned = "warn"+signing.onUntrustedCertificate = "warn"policy and a scope-specific override pinninglpmdevtosigning.onUntrustedCertificate = "silentAllow"— see Trust model for the rationale.
--force re-downloads the cert even when a valid one is on disk. A failed re-download with --force is fatal even if a stale cert is still on disk — silently keeping the stale cert would defeat the explicit rotation the user asked for.
When lpm install first encounters a Swift package in a project that hasn't been set up, it runs the same four-step path (in ensure_configured mode) before resolving the dep. Same fail-loud contract applies, so a one-shot lpm install @lpm.dev/owner.swift-pkg cannot finish "successfully" with signing broken.
Trust model
What the signature buys you, and what it doesn't — be precise about both:
Current posture: detached package integrity + HTTPS transport authenticity.
- Detached package integrity. SPM verifies the CMS signature against the source archive bytes on every install. A tampered tarball fails the integrity check before any code runs.
- Transport authenticity via HTTPS. The lpm.dev origin is the trust anchor for "this came from LPM" — the system CA chain authenticates the connection that carried both the signing cert and the tarballs themselves.
Not current posture: trusted signer identity verification.
LPM's signing cert is self-signed, non-CA, and code-signing-only. SPM's root trust store rejects it (root certs must have basicConstraints CA=true). The silentAllow scope override in step 4 tells SPM to accept the signature without a chain to a system trust root for the lpmdev scope. The cryptographic CMS check still runs; what's bypassed is the "is this signer in my trust chain?" gate.
The signer today is the LPM registry, not the package author — the signature attests "this came from LPM," not "this came from author X." Per-author attribution (Sigstore, transparency log, whatever shape it takes) is a deliberately separate, future track. This page will be updated when that lands; until then, treat any "signed by author X" mental model as not implemented.
Installing a Swift package
lpm install @lpm.dev/owner.swift-pkg
lpm install @lpm.dev/owner.swift-pkg@1.2.0Behind the scenes:
- LPM resolves the version against the SE-0292 endpoint.
- Updates
Package.swift— appends the dep intodependencies:, wires it into the requested target'stargets:array. - Triggers
swift package resolveso SPM downloads the source archive, verifies the signature, and writesPackage.resolved.
Use
lpm installfor Swift, notlpm add.lpm addis source-delivery (copies bytes into your project);lpm installis the registry-resolved-dep path.lpm addstill works for Swift today (with--target) but it's the legacy path for that ecosystem.
Publishing a Swift package
lpm publishDetected as a Swift package when lpm.config.json declares ecosystem: "swift" and the repo has a Package.swift at the root. The publish pipeline does the standard pack + secret scan + quality + upload, plus three Swift-specific steps:
- Generate the SE-0292 source archive. Wraps the pack output into a
{pkgName}-{version}/top-level directory and creates the.zipSPM expects. The wrapper is a hard SE-0292 requirement (SPM usesstripFirstLevelsemantics on the archive). - Compute and record the SHA-256 checksum. SPM verifies this on download. The hex is stored in the release metadata; the base64 form is sent in the
DigestHTTP response header on download. - Sign the package. Generates a CMS-1.0.0 detached signature (ECDSA P-256 / SHA-256) using the LPM signing key. The signature is base64-encoded into release metadata and surfaced in three places per release:
- The release metadata JSON (base64 CMS string)
- The
DigestHTTP response header on download - A comment block in the
Package.swiftmanifest response
Verifying signatures
SPM verifies the CMS detached signature on every package archive at install time. With lpm swift-registry configured (or --force after a documented cert rotation), the verifier path runs cleanly without prompting.
Two failure shapes a user can run into:
onUnsigned: warn— a Swift package without a signature surfaces as a warning, not a hard error. LPM-published packages are always signed, so this typically only appears for non-LPM packages SPM resolves alongside.- Cert missing or out of date — SPM emits a "signer is not trusted" error. Re-run
lpm swift-registry(orlpm swift-registry --forceif you suspect a stale cert) to refresh both the cert file and the registries.json scope override.
If the cert rotates server-side, re-run lpm swift-registry --force. A failed --force re-download is fatal — lpm will not silently keep using the stale cert.
Wire format details
For the curious — the things SPM expects from the server side:
| Endpoint | Returns |
|---|---|
GET /{scope}/{name} | List of versions (JSON) |
GET /{scope}/{name}/{version} | Release metadata (JSON) — signature, checksum, dependencies |
GET /{scope}/{name}/{version}.zip | Source archive with {name}-{version}/ top level, Digest: SHA-256={base64} header |
GET /{scope}/{name}/{version}/Package.swift | Manifest with signature in a comment block |
POST /login | Auth flow (handled by lpm swift-registry automatically) |
GET /certificate | Public cert in DER format |
Implementation lives in the LPM origin server and registry worker. The Rust CLI is a client of this API; it doesn't implement the server side itself.
Limitations
- Today
lpm swift-registryconfigures thelpmdevscope only. Custom-scope publishing (e.g.,acmefor a private LPM tenant) isn't surfaced in the CLI yet. - Cert rotation is manual — no automatic refresh on schedule. The signing cert is long-lived; rotation events will be communicated via release notes.
- The SE-0292
--allow-insecure-httpflag is honored only forhttp://registry URLs (i.e., local dev againsthttp://localhost). The hosted lpm.dev endpoint is HTTPS-only.
See also
- Using LPM with Swift — setup walkthrough + publish flow
lpm swift-registry— CLI command referencelpm install— the right command for SPM deps- SE-0292 spec — the upstream protocol