LPM CLI

Swift Package Registry (SE-0292)

How LPM.dev Registry implements the SE-0292 Swift Package Registry spec — identity mapping, signing, certificates.

LPM.dev Registry implements the SE-0292 Swift Package Registry API. lpm install configures Swift Package Manager (SPM) when needed, then resolves LPM.dev Registry-hosted Swift packages natively — no manual fork-as-git-dep and no separate setup command for the normal install flow.

This page is the conceptual reference. For the user-facing setup walkthrough, see Using LPM CLI 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.swift manifest (GET /{scope}/{name}/{version}/Package.swift)
  • Login (POST /login)
  • Optional package signing via CMS

LPM.dev Registry implements all of the above plus signature verification metadata. SPM clients see a standard SE-0292 server.

Identity mapping

LPM.dev Registry packages are named @lpm.dev/owner.pkg-name. SE-0292 scopes can't contain ., so LPM.dev Registry maps:

LPM.dev Registry nameSE-0292 identity
@lpm.dev/owner.pkg-namelpmdev.owner_pkg-name
@lpm.dev/myorg.swift-utilslpmdev.myorg_swift-utils

Rules:

  • The LPM.dev Registry scope @lpm.dev/ becomes the SE-0292 scope lpmdev (no dots, no @).
  • The _ between owner and package name is the unambiguous separator. LPM.dev Registry 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-client and @lpm.dev/swift.server-async-http-client map to two distinct identifiers (lpmdev.swift-server_async-http-client vs lpmdev.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.

Automatic setup during install

lpm login
lpm install @lpm.dev/owner.swift-pkg

Before resolving the first Swift package in an unconfigured project, LPM CLI performs four setup steps. Scope, certificate, and signing-trust failures abort the install. Authentication setup warns when no token is available or SPM rejects the login, but LPM.dev Registry requests will return 401 until you sign in:

  1. Set the registry for the lpmdev scope.
    swift package-registry set --scope lpmdev https://lpm.dev/api/swift-registry
  2. Log in. Uses the LPM.dev Registry bearer token resolved through the standard LPM CLI session manager (LPM_TOKEN → keychain → OIDC → none).
  3. Install the signing certificate. Downloads the CMS signing cert (DER) from https://lpm.dev/api/swift-registry/certificate and 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.
  4. Configure the SPM signing trust policy. Writes ~/.swiftpm/configuration/registries.json with a default signing.onUnsigned = "warn" + signing.onUntrustedCertificate = "warn" policy and a scope-specific override pinning lpmdev to signing.onUntrustedCertificate = "silentAllow" — see Trust model for the rationale.

lpm swift-registry exposes this setup explicitly for repair and certificate rotation. Its --force flag re-downloads the certificate even when a valid file is present. A failed forced refresh is fatal rather than silently retaining the stale certificate.

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 Registry origin is the trust anchor for "this came from LPM.dev Registry" — 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.dev Registry'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 LPM.dev Registry, not the package author — the signature attests "this came from LPM.dev Registry," 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.0

Behind the scenes:

  1. LPM CLI resolves the version against the SE-0292 endpoint.
  2. Selects an eligible non-test target and updates Package.swift — appends the dependency into dependencies: and wires the product into that target's targets: array.
  3. Triggers swift package resolve so SPM downloads the source archive, verifies the signature, and writes Package.resolved.

One eligible target is selected automatically. With multiple eligible targets, a normal install opens the selector; -y / --yes skips the prompt and selects the first eligible target, matching the selector's default.

Use lpm install for Swift, not lpm add. lpm install is the Registry-resolved SPM dependency path; lpm add is the legacy source-delivery path.

Publishing a Swift package

lpm publish

Detected as a Swift package when the repository has Package.swift at its root. The publish pipeline does the standard pack, secret scan, quality pass, and upload, plus three Swift-specific steps:

  1. Generate the SE-0292 source archive. Wraps the pack output into a {pkgName}-{version}/ top-level directory and creates the .zip SPM expects. The wrapper is a hard SE-0292 requirement (SPM uses stripFirstLevel semantics on the archive).
  2. 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 Digest HTTP response header on download.
  3. Sign the package. Generates a CMS-1.0.0 detached signature (ECDSA P-256 / SHA-256) using the LPM.dev Registry 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 Digest HTTP response header on download
    • A comment block in the Package.swift manifest response

Verifying signatures

SPM verifies the CMS detached signature on every package archive at install time. LPM CLI configures the certificate and signing policy during the normal install flow. Use lpm swift-registry --force only when a documented certificate rotation or local corruption requires an explicit refresh.

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.dev Registry-published packages are always signed, so this typically only appears for packages outside LPM.dev Registry that SPM resolves alongside.
  • Cert missing or out of date — SPM emits a "signer is not trusted" error. Re-run lpm swift-registry (or lpm swift-registry --force if 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:

EndpointReturns
GET /{scope}/{name}List of versions (JSON)
GET /{scope}/{name}/{version}Release metadata (JSON) — signature, checksum, dependencies
GET /{scope}/{name}/{version}.zipSource archive with {name}-{version}/ top level, Digest: SHA-256={base64} header
GET /{scope}/{name}/{version}/Package.swiftManifest with signature in a comment block
POST /loginAuth flow configured automatically during lpm install
GET /certificatePublic cert in DER format

Implementation lives in the LPM.dev Registry origin server and registry worker. LPM CLI is a client of this API; it doesn't implement the server side itself.

Limitations

  • Automatic and explicit Swift Registry setup configure the lpmdev scope only. Custom-scope publishing (for example, acme for a private LPM.dev Registry tenant) is not surfaced in LPM CLI.
  • 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-http flag is honored only for http:// registry URLs (i.e., local dev against http://localhost). The hosted LPM.dev Registry endpoint is HTTPS-only.

See also