SE-0292: Package Registry Service
- Proposal: SE-0292
- Authors: Bryan Clark,
- Review Manager: Tom Doron
- Status: Implemented (Swift 5.7)
- Implementation: apple/swift-package-manager#3023
- Decision Notes: Rationale
- Review:
- Previous Revision:
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)][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}\zA 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}\zPackage 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][scp-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][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][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
1feec3d8d144814e99e694cd1d785928878d8d6892c4e59d12569e179252c535If 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.zipBy 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.zipThe 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.0If desired, this behavior could be changed in future tool versions.
Note:
git-archiveignores files with theexport-ignoreGit attribute. By default, this ignores hidden files and directories, including.gitand.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 machineRunning 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 registryAssociating 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 scopeRunning 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][SE-0219] for that package's URL.
$ swift package config set-mirror \
--original-url https:///github.com/mona/linkedlist \
--mirror-url https:///github.com/octocorp/swiftlinkedlistThis 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][RFC 6749], 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][xss] and [CRLF injection][http header injection] attacks.
To better understand the security implications of this proposal — and Swift dependency management more broadly — we employ the <abbr title="Spoofing, Tampering, Repudiation, Information disclosure, Denial of Service, Escalation of privilege"> [STRIDE] </abbr> 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: OKIntegrity 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)][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][secret scanning] 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][RFC 8288] 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][left-pad] 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][scp-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][SE-0305].
GET /github.com/mona/LinkedList/1.1.1.xcframework HTTP/1.1
Host: packages.github.com
Accept: application/vnd.swift.registry.v1+xcframeworkSwift Package Manager could then use XCFramework archives as [binary dependencies][SE-0272] 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][SE-0301] 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.[AWS CodeArtifact]: https://aws.amazon.com/codeartifact/ [BCP 13]: https://tools.ietf.org/html/rfc6838 "Media Type Specifications and Registration Procedures" [CDN]: https://en.wikipedia.org/wiki/Content_delivery_network "Content delivery network" [checksum database]: https://sum.golang.org "Go Module Mirror, Index, and Checksum Database" [CocoaPods]: https://cocoapods.org "A dependency manager for Swift and Objective-C Cocoa projects" [crates.io]: https://crates.io "crates.io: The Rust community’s crate registry" [Docker Hub]: https://hub.docker.com [GPG]: https://gnupg.org [homograph attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack [http header injection]: https://en.wikipedia.org/wiki/HTTP_header_injection [IANA link relations]: https://www.iana.org/assignments/link-relations/link-relations.xhtml "IANA Link Relation Types" [ICANN]: https://www.icann.org [JFrog Artifactory]: https://jfrog.com/artifactory/ [JSON-LD]: https://w3c.github.io/json-ld-syntax/ "JSON-LD 1.1: A JSON-based Serialization for Linked Data" [left-pad]: https://qz.com/646467/how-one-programmer-broke-the-internet-by-deleting-a-tiny-piece-of-code/ "How one programmer broke the internet by deleting a tiny piece of code" [Maven]: https://maven.apache.org [npm]: https://www.npmjs.com "The npm Registry" [offline cache]: https://yarnpkg.com/features/offline-cache "Offline Cache | Yarn - Package Manager" [PyPI]: https://pypi.org "PyPI: The Python Package Index" [reverse domain name notation]: https://en.wikipedia.org/wiki/Reverse_domain_name_notation [RFC 2119]: https://tools.ietf.org/html/rfc2119 "Key words for use in RFCs to Indicate Requirement Levels" [RFC 3230]: https://tools.ietf.org/html/rfc5843 "Instance Digests in HTTP" [RFC 3492]: https://tools.ietf.org/html/rfc3492 "Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)" [RFC 3986]: https://tools.ietf.org/html/rfc3986 "Uniform Resource Identifier (URI): Generic Syntax" [RFC 3987]: https://tools.ietf.org/html/rfc3987 "Internationalized Resource Identifiers (IRIs)" [RFC 5234]: https://tools.ietf.org/html/rfc5234 "Augmented BNF for Syntax Specifications: ABNF" [RFC 5843]: https://tools.ietf.org/html/rfc5843 "Additional Hash Algorithms for HTTP Instance Digests" [RFC 6249]: https://tools.ietf.org/html/rfc6249 "Metalink/HTTP: Mirrors and Hashes" [RFC 6570]: https://tools.ietf.org/html/rfc6570 "URI Template" [RFC 6749]: https://tools.ietf.org/html/rfc6749 "The OAuth 2.0 Authorization Framework" [RFC 7230]: https://tools.ietf.org/html/rfc7230 "Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing" [RFC 7231]: https://tools.ietf.org/html/rfc7231 "Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content" [RFC 7233]: https://tools.ietf.org/html/rfc7233 "Hypertext Transfer Protocol (HTTP/1.1): Range Requests" [RFC 7234]: https://tools.ietf.org/html/rfc7234 "Hypertext Transfer Protocol (HTTP/1.1): Caching" [RFC 7807]: https://tools.ietf.org/html/rfc7807 "Problem Details for HTTP APIs" [RFC 8288]: https://tools.ietf.org/html/rfc8288 "Web Linking" [RFC 8446]: https://tools.ietf.org/html/rfc8446 "The Transport Layer Security (TLS) Protocol Version 1.3" [RubyGems]: https://rubygems.org "RubyGems: The Ruby community’s gem hosting service" [Schema.org]: https://schema.org/ [scp-url]: https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols#the_ssh_protocol [SE-0219]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0219-package-manager-dependency-mirroring.md "Package Manager Dependency Mirroring" [SE-0272]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0272-swiftpm-binary-dependencies.md "Package Manager Binary Dependencies" [SE-0301]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0301-package-editing-commands.md "Package Editor Commands" [SE-0305]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0305-swiftpm-binary-target-improvements.md "Package Manager Binary Target Improvements" [secret scanning]: https://docs.github.com/en/github/administering-a-repository/about-secret-scanning [SemVer]: https://semver.org/ "Semantic Versioning" [SoftwareSourceCode]: https://schema.org/SoftwareSourceCode [STRIDE]: https://en.wikipedia.org/wiki/STRIDE(security) "STRIDE (security)" [thundering herd effect]: https://en.wikipedia.org/wiki/Thundering_herd_problem "Thundering herd problem" [TOFU]: https://en.wikipedia.org/wiki/Trust_on_first_use "Trust on First Use" [transparent log]: https://research.swtch.com/tlog [typosquatting]: https://en.wikipedia.org/wiki/Typosquatting [UTI]: https://en.wikipedia.org/wiki/Uniform_Type_Identifier [version-specific-manifest-selection]: https://github.com/apple/swift-package-manager/blob/main/Documentation/Usage.md#version-specific-manifest-selection "Swift Package Manager - Version-specific Manifest Selection" [version-specific-tag-selection]: https://github.com/apple/swift-package-manager/blob/main/Documentation/Usage.md#version-specific-tag-selection "Swift Package Manager - Version-specific Tag Selection" [XCFramework]: https://developer.apple.com/videos/play/wwdc2019/416/ "WWDC 2019 Session 416: Binary Frameworks in Swift" [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting [Zip bomb]: https://en.wikipedia.org/wiki/Zip_bomb "Zip bomb" [Zip Slip]: https://snyk.io/research/zip-slip-vulnerability "Zip Slip Vulnerability"