Skip to content

Latest commit

 

History

History
1267 lines (1042 loc) · 48.7 KB

0292-package-registry-service.md

File metadata and controls

1267 lines (1042 loc) · 48.7 KB

Package Registry Service

Introduction

Swift Package Manager downloads dependencies using Git. Our proposal defines a standard web service interface that it can also use to download dependencies from a package registry.

Swift-evolution thread: Swift Package Registry Service

Motivation

A package dependency is currently specified by a URL to its source repository. When Swift Package Manager builds a project for the first time, it clones the Git repository for each dependency and attempts to resolve the version requirements from the available tags.

Although Git is a capable version-control system, it's not well-suited to this kind of workflow for the following reasons:

  • Reproducibility: A version tag in the Git repository for a dependency can be reassigned to another commit at any time. This can cause a project to produce different build results depending on when it was built.
  • Availability: The Git repository for a dependency can be moved or deleted, which can cause subsequent builds to fail.
  • Efficiency: Cloning the Git repository for a dependency downloads all versions of a package when only one is used at a time.
  • Speed: Cloning a Git repository for a dependency can be slow if it has a large history. Also, cloning a Git repository is expensive for both the server and client, and may be significantly slower than downloading the same content using HTTP through a content delivery network (CDN).

Many language ecosystems have a package registry, including RubyGems for Ruby, PyPI for Python, npm for JavaScript, and crates.io for Rust. In fact, many Swift developers build apps today using CocoaPods and its index of libraries.

A package registry for Swift Package Manager could offer faster and more reliable dependency resolution than downloading dependencies using Git. It could also support other useful functionality, including package search, security audits, and local offline caches.

Proposed solution

This proposal defines a standard interface for package registry services and describes how Swift Package Manager integrates with them to download dependencies.

A user may configure a package registry for their project by specifying a URL to a conforming web service. When a registry is configured, Swift Package Manager resolves external dependencies in the project's package manifest (Package.swift) file that are declared with a scoped package identifier in the form scope.package-name. These package identifiers resolve potential name collisions across build targets.

For each external dependency declared in the package manifest, Swift Package Manager first sends a GET request to /{scope}/{name} to fetch a list of available releases from the configured registry. If a release is found that satisfies the declared version requirement (for example, .upToNextMinor(from: "1.1.0")), Swift Package Manager sends a GET request to /{scope}/{name}/{version}/Package.swift to fetch the manifest for that release. This process continues with the package manifests of each dependency, each of their respective dependencies, and so on. Once the dependency graph is resolved, Swift Package Manager downloads the source archive for each dependency by sending a GET request to /{scope}/{name}/{version}.zip.

Detailed design

Package registry service

A package registry service implements the following REST API endpoints for listing releases for a package, fetching information about a release, and downloading the source archive for a release:

Method Path Description
GET /{scope}/{name} List package releases
GET /{scope}/{name}/{version} Fetch metadata for a package release
GET /{scope}/{name}/{version}/Package.swift{?swift-version} Fetch manifest for a package release
GET /{scope}/{name}/{version}.zip Download source archive for a package release
GET /identifiers{?url} Lookup package identifiers registered for a URL

A formal specification for the package registry interface is provided alongside this proposal. In addition, an OpenAPI (v3) document and a reference implementation written in Swift are provided for the convenience of developers interested in building their own package registry.

Changes to Swift Package Manager

Package identity

Currently, the identity of a package is computed from the last path component of its effective URL (which can be changed with dependency mirroring). However, this approach can lead to a conflation of distinct packages with similar names and the duplication of the same package under different names.

We propose using a scoped identifier in the form scope.package-name to identify package dependencies.

A scope provides a namespace for related packages within a package registry. A package scope consists of alphanumeric characters and hyphens. Hyphens may not occur at the beginning or end, nor consecutively within a scope. The maximum length of a package scope is 39 characters. A valid package scope matches the following regular expression pattern:

\A[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){0,38}\z

A package's name uniquely identifies a package in a scope. A package name consists of alphanumeric characters, underscores, and hyphens. Hyphens and underscores may not occur at the beginning or end, nor consecutively within a name. The maximum length of a package name is 100 characters. A valid package scope matches the following regular expression pattern:

\A[a-zA-Z0-9](?:[a-zA-Z0-9]|[-_](?=[a-zA-Z0-9])){0,99}\z

Package scopes and names are compared using locale-independent case folding.

New PackageDescription API

The Package.Dependency type adds the following static method:

extension Package.Dependency {
    /// Adds a dependency on a package with the specified identifier
    /// that uses the provided version requirement.
    public static func package(
        id: String,
        _ requirement: Package.Dependency.VersionBasedRequirement
    ) -> Package.Dependency
}

These methods may be called in the dependencies field of a package manifest to declare one or more dependencies by their respective package identifier.

dependencies: [
   .package(id: "mona.LinkedList", .upToNextMinor(from: "1.1.0")),
   .package(id: "mona.RegEx", .exact("2.0.0"))
]

A package dependency declared with an identifier using this method may only specify a version-based requirement. Package.Dependency.VersionBasedRequirement is a new type that provides the same interface as Package.Dependency.Requirement for version-based requirements, but excluding branch-based and commit-based requirements.

Package name collision resolution

Consider a dependency graph that includes both a package declared with the identifier mona.LinkedList and an equivalent package declared with the URL https://github.com/mona/LinkedList.

When Swift Package Manager fetches a list of releases for the identified package (GET /mona/LinkedList), the response includes a Link header field with URLs to that project's source repository that are known to the registry.

Link: <https://github.com/mona/LinkedList>; rel="canonical",
      <ssh://git@github.com:mona/LinkedList.git>; rel="alternate"

Swift Package Manager uses this information to reconcile the URL-based dependency declaration with the package identifier mona.LinkedList. Link relation URLs may also be normalized to mitigate insignificant variations. For example, a package with an "scp-style" URL like git@github.com:mona/LinkedList.git is determined to be equivalent to a URL with an explicit scheme like ssh:///git@github.com/mona/LinkedList. Swift Package Manager may additionally consult the registry to associate a URL-based package declaration with a package identifier by sending a GET /identifiers{?url} request with that package's URL.

A package identifier serves as the package name in target-based dependency declarations — that is, the package parameter in .product(name:package) method calls.

    targets: [
        .target(name: "MyLibrary",
                dependencies: [
                  .product(name: "LinkedList",
-                          package: "LinkedList")
+                          package: "mona.LinkedList")
                ]
    ]

Any path-based dependency declaration or URL-based declaration without an associated package identifier will continue to synthesize its identity from the last path component of its location.

Dependency graph resolution

In its PackageGraph module, Swift Package Manager defines the PackageContainer protocol as the top-level unit of package resolution. Conforming types are responsible for determining the available tags for a package and its contents at a particular revision. A PackageContainerProvider protocol adds a level of indirection for resolving package containers.

There are currently two concrete implementations of PackageContainer: LocalPackageContainer and RepositoryPackageContainer. This proposal adds a new RegistryPackageContainer type that adopts PackageContainer and performs equivalent operations with HTTP requests to a registry service. These client-server interactions are facilitated by a new RegistryManager type. When requesting resources from a registry, Swift Package Manager will employ techniques like exponential backoff, circuit breakers, and client-side validation to safeguard against adverse network conditions and malicious server responses.

The following table lists the tasks performed by Swift Package Manager during dependency resolution alongside the Git operations used and their corresponding package registry API calls.

Task Git operation Registry request
Fetch the contents of a package git clone && git checkout GET /{scope}/{name}/{version}.zip
List the available tags for a package git tag GET /{scope}/{name}
Fetch a package manifest git clone GET /{scope}/{name}/{version}/Package.swift

Package registries support version-specific manifest selection by providing a list of versioned manifest files for a package (for example, Package@swift-5.3.swift) in its response to GET /{scope}/{name}/{version}/Package.swift. However, package registries don't support version-specific tag selection.

Changes to Package.resolved

Swift package registry releases are archived as Zip files.

When an external package dependency is downloaded through a registry, Swift Package Manager compares the integrity checksum provided by the server against any existing checksum for that release in the Package.resolved file as well as the integrity checksum reported by the compute-checksum subcommand:

$ swift package compute-checksum LinkedList-1.2.0.zip
1feec3d8d144814e99e694cd1d785928878d8d6892c4e59d12569e179252c535

If no prior checksum exists, it's saved to Package.resolved.

{
    "object": {
        "pins": [
            {
                "package": "mona.LinkedList",
                "state": {
                    "checksum": "ed008d5af44c1d0ea0e3668033cae9b695235f18b1a99240b7cf0f3d9559a30d",
                    "version": "1.2.0"
                }
            }
        ]
    },
    "version": 1
}

Suppose the checksum reported by the server is different from the existing checksum (or the checksum of the downloaded artifact is different from either of them). In that case, a package's contents may have changed at some point. Swift Package Manager will refuse to download dependencies if there's a mismatch in integrity checksums.

$ swift build
error: checksum of downloaded source archive of dependency 'mona.LinkedList' (c2b934fe66e55747d912f1cfd03150883c4f037370c40ca2ad4203805db79457) does not match checksum specified by the manifest (ed008d5af44c1d0ea0e3668033cae9b695235f18b1a99240b7cf0f3d9559a30d)

Once the correct checksum is determined, the user can update Package.resolved with the correct value and try again.

Archive-source subcommand

An anecdotal look at other package managers suggests that a checksum mismatch is more likely to be a disagreement in how to create the archive and/or calculate the checksum than, say, a forged or corrupted package.

This proposal adds a new swift package archive-source subcommand to provide a standard way to create source archives for package releases.

SYNOPSIS
	swift package archive-source [--output=<file>]

OPTIONS
	-o <file>, --output=<file>
		Write the archive to <file>.
		If unspecified, the package is written to `\(PackageName).zip`.

Run the swift package archive-source subcommand in the root directory of a package to generate a source archive for the current working tree. For example:

$ tree -a -L 1
LinkedList
├── .git
├── Package.swift
├── README.md
├── Sources
└── Tests

$ head -n 5 Package.swift
// swift-tools-version:5.3
import PackageDescription

let package = Package(
name: "LinkedList",

$ swift package archive-source
Created LinkedList.zip

By default, generated archive's filename is the name of the package with a .zip extension (for example, "LinkedList.zip"). You can override this behavior with the --output option:

$ git checkout 1.2.0
$ swift package archive-source --output="LinkedList-1.2.0.zip"
# Created LinkedList-1.2.0.zip

The archive-source subcommand has the equivalent behavior of [git-archive(1)] using the zip format at its default compression level, with entries prefixed by the basename of the generated archive's filename. Therefore, the following command produces equivalent output to the previous example:

$ git archive --format zip \
              --prefix LinkedList-1.2.0
              --output LinkedList-1.2.0.zip \
              1.2.0

If desired, this behavior could be changed in future tool versions.

Note: git-archive ignores files with the export-ignore Git attribute. By default, this ignores hidden files and directories, including.git and .build.

Registry configuration subcommands

This proposal adds a new swift package-registry subcommand for managing the registry used for all packages and/or packages in a particular scope.

Custom registries can serve a variety of purposes:

  • Private dependencies: Users may configure a custom registry for a particular scope to incorporate private packages with those fetched from a public registry.
  • Geographic colocation: Developers working under adverse networking conditions can host a mirror of official package sources on a nearby network.
  • Policy enforcement: A corporate network can enforce quality or licensing standards, so that only approved packages are available through a custom registry.
  • Auditing: A custom registry may analyze or meter access to packages for the purposes of ranking popularity or charging licensing fees.

Setting a custom registry

SYNOPSIS
	swift package-registry set <url> [options]
OPTIONS:
  --global    Apply settings to all projects for this user
  --scope     Associate the registry with a given scope
  --login     Specify a user name for the remote machine
  --password  Supply a password for the remote machine

Running the package-registry set subcommand in the root directory of a package creates or updates the .swiftpm/configuration/registries.json file with a new top-level registries key that's associated with an object containing the specified registry URLs. The default, unscoped registry is associated with the key [default]. Any scoped registries are keyed by their case-folded name.

For example, a build server that doesn't allow external network connections may configure a registry URL to resolve dependencies using an internal registry service.

$ swift package-registry set https://internal.example.com/
$ cat .swiftpm/configuration/registries.json
{
  "registries": {
    "[default]": {
      "url": "https://internal.example.com"
    }
  },
  "version": 1
}

If no registry is configured, Swift Package Manager commands like swift package resolve and swift package update fail with an error.

$ swift package resolve
error: cannot resolve dependency 'mona.LinkedList' without a configured registry

Associating a registry with a scope

The user can associate a package scope with a custom registry by passing the --scope option.

For example, a user might resolve all packages with the package scope example (such as example.PriorityQueue) to a private registry.

$ swift package-registry set https://internal.example.com/ --scope example
$ cat .swiftpm/configuration/registries.json
{
  "registries": {
    "example": {
      "url": "https://internal.example.com"
    }
  },
  "version": 1
}

When a custom registry is associated with a package scope, package dependencies with that scope are resolved through the provided URL. A custom registry may be associated with one or more scopes, but a scope may be associated with only a single registry at a time. Scoped custom registries override any unscoped custom registry.

Unsetting a custom registry

This proposal also adds a new swift package-registry unset subcommand to complement the package-registry set subcommand.

SYNOPSIS
	swift package-registry unset [options]
OPTIONS:
  --global    Apply settings to all projects for this user
  --scope     Removes the registry's association to a given scope

Running the package-registry unset subcommand in the root directory of a package updates the .swiftpm/configuration/registries.json file to remove the default entry in the top-level registries key, if present. If a --scope option is passed, only the entry for the specified scope is removed, if present.

Global registry configuration

The user can pass the --global option to the set or unset subcommands to update the user-level configuration file located at ~/.swiftpm/configuration/registries.json.

Any default or scoped registries configured locally in a project directory override any values configured globally for the user. For example, consider the following global and local registry configuration files:

// Global configuration (~/.swiftpm/configuration/registries.json)
{
  "registries": {
    "[default]": {
      "url": "https://global.example.com"
    },
    "foo": {
      "url": "https://global.example.com"
    },
  },
  "version": 1
}

// Local configuration (.swiftpm/configuration/registries.json)
{
  "registries": {
    "foo": {
      "url": "https://local.example.com"
    }
  },
  "version": 1
}

Running the swift package resolve command with these configuration files resolves packages with the foo scope using the registry located at "https://local.example.com", and all other packages using the registry located at "https://global.example.com".

In summary, the behavior of swift package resolve and related commands depends on the following factors, in descending order of precedence:

  • The package manifest in the current directory (./Package.swift)
  • Any existing lock file (./Package.resolved)
  • Any local configuration (./.swiftpm/configuration/registries.json)
  • Any global configuration file (~/.swiftpm/configuration/registries.json)

Specifying credentials for a custom registry

Some servers may require a username and password. The user can provide credentials when setting a custom registry by passing the --login and --password options.

When credentials are provided, the corresponding object in the registries.json file includes a login key with the passed value. If the project's .netrc file has an existing entry for a given machine and login, it's updated with the new password; otherwise, a new entry is added. If no .netrc file exists, a new one is created and populated with the new entry.

$ swift package-registry set https://internal.example.com/ \
    --login jappleseed --password alpine

$ cat .netrc
machine internal.example.com
login jappleseed
password alpine

$ cat .swiftpm/configuration/registries.json

{
  "registries": {
    "[default]": {
      "url": "https://internal.example.com"
      "login": "jappleseed"
    }
  },
  "version": 1
}

If the user passes the --login and --password options to the set subcommand along with the --global option, the user-level .netrc file is updated instead. When Swift Package Manager connects to a custom registry, it first consults the project's .netrc file, if one exists. If no entry is found for the custom registry, Swift Package Manager then consults the user-level .netrc file, if one exists.

If the provided credentials are missing or invalid, Swift Package Manager commands like swift package resolve and swift package update fail with an error.

Changes to config subcommand

Set-mirror option for package identifiers

A user can currently specify an alternate location for a package by setting a dependency mirror for that package's URL.

$ swift package config set-mirror \
    --original-url https:///github.com/mona/linkedlist \
    --mirror-url https:///github.com/octocorp/swiftlinkedlist

This proposal updates the swift package config set-mirror subcommand to accept a --package-identifier option in place of an --original-url. Running this subcommand with a --package-identifier option creates or updates the .swiftpm/configuration/mirrors.json file, modifying the array associated with the top-level object key to add a new entry or update an existing entry for the specified package identifier, that assigns its alternate location.

{
  "object": [
    {
      "mirror": "https://github.com/OctoCorp/SwiftLinkedList.git",
      "original": "mona.LinkedList"
    }
  ],
  "version": 1
}

When a mirror URL is set for a package identifier, Swift Package Manager resolves any dependencies with that identifier through Git using the provided URL.

Security

Adding external dependencies to a project increases the attack surface area of your software. However, much of the associated risk can be mitigated, and a package registry can offer stronger guarantees for safety and security compared to downloading dependencies using Git.

Core security measures, such as the use of HTTPS and integrity checksums, are required by the registry service specification. Additional decisions about security are delegated to the registries themselves. For example, registries are encouraged to adopt a scoped, revocable authorization framework like OAuth 2.0, but this isn't a strict requirement. Package maintainers and consumers should consider a registry's security posture alongside its other features when deciding where to host and fetch packages.

Our proposal's package identity scheme is designed to prevent or mitigate vulnerabilities common to packaging systems and networked applications:

  • Package scopes are restricted to a limited set of characters, preventing homograph attacks. For example, "А" (U+0410 CYRILLIC CAPITAL LETTER A) is an invalid scope character and cannot be confused for "A" (U+0041 LATIN CAPITAL LETTER A).
  • Package scopes disallow leading, trailing, or consecutive hyphens (-), and disallows underscores (_) entirely, which mitigates look-alike package scopes (for example, "llvm--swift" and "llvm_swift" are both invalid and cannot be confused for "llvm-swift").
  • Package scopes disallow dots (.), which prevents potential confusion with domain variants of scopes (for example, "apple.com" is invalid and cannot be confused for "apple").
  • Packages are registered within a scope, which mitigates typosquatting. Package registries may further restrict the assignment of new scopes that are intentionally misleading (for example, "G00gle", which looks like "Google").
  • Package names disallow punctuation and whitespace characters used in cross-site scripting and CRLF injection attacks.

To better understand the security implications of this proposal — and Swift dependency management more broadly — we employ the STRIDE mnemonic below:

Spoofing

An attacker could interpose a proxy between the client and the package registry to intercept credentials for that host and use them to impersonate the user in subsequent requests.

The impact of this attack is potentially high, depending on the scope and level of privilege associated with these credentials. However, the use of secure connections over HTTPS goes a long way to mitigate the overall risk.

Swift Package Manager could further mitigate this risk by taking the following measures:

  • Enforcing HTTPS for all URLs
  • Resolving URLs using DNS over HTTPS (DoH)
  • Requiring URLs with Internationalized Domain Names (IDNs) to be represented as Punycode

Tampering

An attacker could interpose a proxy between the client and the package registry to construct and send Zip files containing malicious code.

Although the impact of such an attack is potentially high, the risk is largely mitigated by the use of cryptographic checksums to verify the integrity of downloaded source archives.

$ echo "$(swift package compute-checksum LinkedList-1.2.0.zip) *LinkedList-1.2.0.zip" | \
    shasum -a 256 -c -
LinkedList-1.2.0.zip: OK

Integrity checks alone can't guarantee that a package isn't a forgery; an attacker could compromise the website of the host and provide a valid checksum for a malicious package.

Package.resolved provides a Trust on first use (TOFU) security model that can offer strong guarantees about the integrity of dependencies over time. A registry can further improve on this model by implementing a transparent log, checksum database, or another comparable, tamper-proof system for authenticating package contents.

Distribution of packages through Zip files introduces new potential attack vectors. For example, an attacker could maliciously tamper with a generated source archive in an attempt to exploit a known vulnerability like Zip Slip, or a common software weakness like susceptibility to a Zip bomb. Swift Package Manager should take care to identify and protect against these kinds of attacks in its implementation of source archive decompression.

Repudiation

A compromised host could serve a malicious package with a valid checksum and be unable to deny its involvement in constructing the forgery.

This threat is unique and specific to binary and source artifacts; Git repositories can have their histories audited, and individual commits may be cryptographically signed by authors. Unless you can establish a direct connection between an artifact and a commit in a source tree, there's no way to determine the provenance of that artifact.

Source archives generated by [git-archive(1)] include the checksum of the HEAD commit as a comment. If the history of a project is available and the commit used to generate the source archive is signed with GPG, the cryptographic signature may be used to verify the authenticity.

$ git rev-parse HEAD
b7c37c81f164e5dce0f64e3d75c79a48fb1fe00b3

$ swift package archive-source -o LinkedList-1.2.0.zip
Generated LinkedList-1.2.0.zip

$ zipnote LinkedList-1.2.0.zip | grep "@ (zip file comment below this line)" -A 1 | tail -n 1
b7c37c81f164e5dce0f64e3d75c79a48fb1fe00b3

$ git verify-commit b7c37c81f164e5dce0f64e3d75c79a48fb1fe00b3
gpg: Signature made Tue Dec 16 00:00:00 2020 PST
gpg:                using RSA key BFAA7114B920808AA4365C203C5C1CF
gpg: Good signature from "Mona Lisa Octocat <mona@noreply.github.com>" [ultimate]

Otherwise, a checksum database and the use of digital signatures can both provide similar non-repudiation guarantees.

Information disclosure

A user may inadvertently expose credentials by checking in their project's configuration files. An attacker could scrape public code repositories for configuration files and attempt to reuse credentials to impersonate the user.

The risk of leaking credentials can be mitigated by storing them in a .netrc file located outside the project directory (typically in the user's home directory). However, a user may run swift package subcommands with the --netrc-file option to configure the location of their project's .netrc file. To mitigate the risk of a user inadvertently adding a local .netrc file to version control, Swift Package Manager could add an entry to the .gitignore file template for new projects created with swift package init.

Code hosting providers can also help minimize this risk by detecting secrets that are committed to public repositories.

Credentials may also be unintentionally disclosed by Swift Package Manager or other tools in logging statements. Care should be taken to redact usernames and passwords when displaying feedback to the user.

Denial of service

An attacker could scrape public code repositories for .swiftpm/configuration/registries.json files that declare one or more custom registries and launch a denial-of-service attack in an attempt to reduce the availability of those resources.

{
  "registries": {
      "[default]": {
        "url": "https://private.example.com"
      }
  },
  "version": 1
}

The likelihood of this attack is generally low but could be used in a targeted way against resources known to be important or expensive to distribute.

This kind of attack can be mitigated on an individual basis by adding .swiftpm/configuration to a project's .gitignore file.

Escalation of privilege

Even authentic packages from trusted creators can contain malicious code.

Code analysis tools can help to some degree, as can system permissions and other OS-level security features. However, developers are ultimately responsible for the code they ship to users.

Impact on existing packages

Current packages won't be affected by this change, as they'll continue to download dependencies directly through Git.

Alternatives considered

Use of alternative naming schemes

Some package systems, including RubyGems, PyPI, and CocoaPods identify packages with bare names in a flat namespace (for example, rails, pandas, or Alamofire). Other systems, including Maven, use reverse domain name notation to identify software components (for example, com.squareup.okhttp3).

We considered these and other schemes for identifying packages, but they were rejected in favor of the scoped package identity described in this proposal.

Use of tar or other archive formats

Swift Package Manager currently uses Zip archives for binary dependencies, which is reason enough to use it again here.

Zip files are also a convenient format for package registries, because they support the access of individual files within an archive. This allows a registry to satisfy the package manifest endpoint (GET /{scope}/{name}/{version}/Package.swift) without storing anything separately from the archive used for the package archive endpoint (GET /{scope}/{name}/{version}.zip).

We briefly considered tar as an archive format but concluded that its behavior of preserving symbolic links and executable bits served no useful purpose in the context of package management, and instead raised concerns about portability and security.

Inclusion of alternative source locations in package releases payload

To maintain compatibility with existing, URL-based dependency declarations Swift Package Manager needs to reconcile source locations with their respective identifiers. For example, the declarations .package(url: "https://github.com/mona/LinkedList", .exact("1.1.0")) and .package(id: "mona.LinkedList", .exact("1.1.0")), must be deemed equivalent to resolve a dependency graph that contains both of them.

We considered including alternative source locations in the response body, but rejected that in favor of using link relations.

Web linking provides a standard way to describe the relationships between resources. Standard canonical and alternative IANA link relations convey precise semantics for the relationship between a package and its source repositories that are broadly useful beyond any individual client.

Addition of an unarchive-source subcommand

This proposal adds an archive-source subcommand as a standard way for developers and registries to create source archives for packages. Having a canonical tool for creating source archives avoids any confusion when attempting to verify the integrity of Zip files sent from a registry with the source code for that package.

We considered including a complementary unarchive-source subcommand but ultimately decided against it, the reason being that unarchiving a Zip archive is unambiguous and well-supported on most platforms.

Use of digital signatures

SE-0272 includes a discussion about the use of digital signatures for binary dependencies, concluding that they were unsuitable because of complexity around transitive dependencies. However, it's unclear what specific objections were raised in this proposal. We didn't see any inherent tension with the example provided, and no further explanation was given.

Without understanding the context of this decision, we decided it was best to abide by their determination and instead consider adding this functionality in a future proposal. For the reasons outlined in the preceding Security section, we believe that digital signatures may offer additional guarantees of authenticity and non-repudiation beyond what's possible with checksums alone.

Future directions

Defining a standard interface for package registries lays the groundwork for several useful features.

Package publishing

A package registry is responsible for determining which package releases are made available to a consumer. This proposal sets no policies for how package releases are published to a registry. Nor does it specify how package scopes are registered or verified.

Many package managers — including the ones mentioned above — and artifact repository services, such as Docker Hub, JFrog Artifactory, and AWS CodeArtifact follow what we describe as a "push" model of publication: When a package owner wants to releases a new version of their software, they produce a build locally and push the resulting artifact to a server. This model has the benefit of operational simplicity and flexibility. For example, maintainers have an opportunity to digitally sign artifacts before uploading them to the server.

Alternatively, a system might incorporate build automation techniques like continuous integration (CI) and continuous delivery (CD) into what we describe as a "pull" model: When a package owner wants to release a new version of their software, their sole responsibility is to notify the package registry; the server does all the work of downloading the source code and packaging it up for distribution. This model can provide strong guarantees about reproducibility, quality assurance, and software traceability.

We intend to work with industry stakeholders to develop standards for publishing Swift packages in an extension to the registry specification.

Package removal

Removing a package from a registry can break other packages that depend on it, as demonstrated by the "left-pad" incident in March 2016. We believe package registries can and should provide strong durability guarantees to ensure the health of the ecosystem.

At the same time, there are valid reasons why a package release may be removed:

  • The package maintainer publishing a release by mistake
  • A security researcher disclosing a vulnerability for a release
  • The registry being compelled by law enforcement to remove a release

It's unclear whether and to what extent package deletion policies should be informed by the registry specification itself. For now, a registry is free to exercise its own discretion about how to respond to out-of-band removal requests.

We plan to consider these questions as part of the future extension to the specification described in the previous section.

Package dependency URL normalization

As described in "Package name collision resolution" Swift Package Manager cannot build a project if two or more packages in the project are located by URLs with the same (case-insensitive) last path component. Swift Package Manager may improve support URL-based dependencies by normalizing package URLs to mitigate insignificant variations. For example, a package with an "scp-style" URL like git@github.com:mona/LinkedList.git may be determined to be equivalent to a package with an HTTPS scheme like https:///github.com/mona/LinkedList.

Local offline cache

Swift Package Manager could implement an offline cache that would allow it to work without network access. While this is technically possible today, a package registry makes for a simpler and more secure implementation than would otherwise be possible with Git repositories alone.

Binary framework distribution

The registry specification could be amended to support the distribution of XCFramework bundles or artifact archives.

GET /github.com/mona/LinkedList/1.1.1.xcframework HTTP/1.1
Host: packages.github.com
Accept: application/vnd.swift.registry.v1+xcframework

Swift Package Manager could then use XCFramework archives as binary dependencies or as part of a future binary artifact distribution mechanism.

let package = Package(
    name: "SomePackage",
    /* ... */
    targets: [
        .binaryTarget(
            name: "LinkedList",
            url: "https://packages.github.com/github.com/mona/LinkedList/1.1.1.xcframework",
            checksum: "ed04a550c2c7537f2a02ab44dd329f9e74f9f4d3e773eb883132e0aa51438b37"
        ),
    ]
)

Updates to package editor commands

Package editor commands could be extended to add dependencies using scoped identifiers in addition to URLs.

$ swift package add-dependency mona.LinkedList
# Installed LinkedList 1.2.0
+    .package(id: "mona.LinkedList", .exact("1.2.0"))

Package manifest dependency migration

Swift Package Manager could add tooling to help package maintainers adopt registry-supported identifiers in their projects.

$ swift package-registry migrate
-    .package(url: "https://github.com/mona/LinkedList", .exact("1.2.0"))
+    .package(id: "mona.LinkedList", .exact("1.2.0"))

Security audits

The response for listing package releases could be updated to include information about security advisories.

{
    "releases": { /* ... */ },
    "advisories": [{
        "cve": "CVE-20XX-12345",
        "cwe": "CWE-400",
        "package": "mona.LinkedList",
        "vulnerable_versions": "<=1.0.0",
        "patched_versions": ">1.0.0",
        "severity": "moderate",
        "recommendation": "Update to version 1.0.1 or later.",
        /* additional fields */
    }]
}

Swift Package Manager could communicate this information to users when installing or updating dependencies or as part of a new swift package audit subcommand.

$ swift package audit
┌───────────────┬────────────────────────────────────────────────┐
│ High          │ Regular Expression Denial of Service           │
├───────────────┼────────────────────────────────────────────────┤
│ Package       │ mona.RegEx                                     │
├───────────────┼────────────────────────────────────────────────┤
│ Dependency of │ PatternMatcher                                 │
├───────────────┼────────────────────────────────────────────────┤
│ Path          │ SomePackage > PatternMatcher > RegEx           │
├───────────────┼────────────────────────────────────────────────┤
│ More info     │ https://example.com/advisories/526             │
└───────────────┴────────────────────────────────────────────────┘

Found 3 vulnerabilities (1 low, 1 moderate, 1 high) in 8 scanned packages.
  Run `swift package audit fix` to fix 3 of them.

Package search

The package registry API could be extended to add a search endpoint to allow users to search for packages by name, keywords, or other criteria. This endpoint could be used by clients like Swift Package Manager.

$ swift package search LinkedList
LinkedList (github.com/mona/LinkedList) - One thing links to another.

$ swift package search --author "Mona Lisa Octocat"
LinkedList (github.com/mona/LinkedList) - One thing links to another.
RegEx (github.com/mona/RegEx) - Expressions on the reg.