- Proposal: SE-0292
- Authors: Bryan Clark, Whitney Imura, Mattt Zmuda
- Review Manager: Tom Doron
- Status: Implemented (Swift 5.7)
- Implementation: apple/swift-package-manager#3023
- Decision Notes: Rationale
- Review: 1 2 3 Amendment
- Previous Revision: 1 2 3
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
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.
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
.
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.
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.
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.
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.
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.
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.
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 theexport-ignore
Git attribute. By default, this ignores hidden files and directories, including.git
and.build
.
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.
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
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.
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.
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
)
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.
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.
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:
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
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.
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.
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.
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.
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.
Current packages won't be affected by this change, as they'll continue to download dependencies directly through Git.
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.
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.
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.
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.
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.
Defining a standard interface for package registries lays the groundwork for several useful features.
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.
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.
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
.
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.
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"
),
]
)
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"))
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"))
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.
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.