Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time
752 lines (621 sloc) 32.4 KB
# Sandstorm - Personal Cloud Sandbox
# Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
# All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
# This file contains schemas relevant to the Sandstorm package format. See also the `spk` tool.
$import "/capnp/c++.capnp".namespace("sandstorm::spk");
using Util = import "util.capnp";
using Powerbox = import "powerbox.capnp";
using Grain = import "grain.capnp";
using ApiSession = import "api-session.capnp".ApiSession;
using Identity = import "identity.capnp";
using WebSession = import "web-session.capnp".WebSession;
struct PackageDefinition {
id @0 :Text;
# The app's ID string. This is actually an encoding of the app's public key generated by the spk
# tool, and looks something like "h37dm17aa89yrd8zuqpdn36p6zntumtv08fjpu8a8zrte7q1cn60".
# Normally, `spk init` will fill this in for you. You can use `spk keygen` to generate a new ID
# if needed. The private key corresponding to each ID is stored in a keyring outside your project
# directory; see `spk help` for more on this.
# Note that you can specify an alternative ID to `spk pack` with the `-i` flag. This makes sense
# when you are doing an unofficial build of an app and don't want to use (or don't have access
# to) the app's real private key.
manifest @1 :Manifest;
# Manifest to write as the package's `sandstorm_manifest`. If null, then `sandstorm-manifest`
# should appear in the file list.
sourceMap @2 :SourceMap;
# Indicates where to search for file to include in the package.
fileList @3 :Text;
# Name of a file which itself contains a list of files, one per line, that should be included
# in the package. Each file should be specified according to its location in the package; the
# source file will be found by mapping this through `sourceMap`. Each name should be canonical
# (no ".", "..", or consecutive slashes) and should NOT start with '/'.
# The file list is automatically generated by `spk dev` based on watching what files are opened
# by the actual running server. On subsequent runs, new files will be added, but files will never
# be removed from the list. To reset the list, simply delete it and run `spk dev` again.
alwaysInclude @4 :List(Text);
# Files and directories that should always be included in the package whether or not they are
# in the file named by `fileList`. If you name a directory here, its entire contents will be
# included recursively (this is not the case in `fileList`). Use this list to name files that
# wouldn't automatically be included, because for whatever reason the server does not actually
# open them when running in dev mode. This could include runtime dependencies that are too
# difficult to test fully, or perhaps a readme file or copyright notice that you want people to
# see if they unpack your package manually.
bridgeConfig @5 :BridgeConfig;
# Configuration variables for apps that use sandstorm-http-bridge.
struct Manifest {
# This manifest file defines an application. The file `sandstorm-manifest` at the root of the
# application's `.spk` package contains a serialized (binary) instance of `Manifest`.
# TODO(soon): Maybe this should be renamed. A "manifest" is a list of contents, but this
# structure doesn't contain a list at all; it contains information on how to use the contents.
const sizeLimitInWords :UInt64 = 1048576;
# The maximum size of the Manifest is 8MB (1M words). This limit is enforced in many places.
appTitle @7 :Util.LocalizedText;
# The name of this app as it should be displayed to the user.
appVersion @4 :UInt32;
# Among app packages with the same app ID (i.e. the same `publicKey`), `version` is used to
# decide which packages represent newer vs. older versions of the app. The sole purpose of this
# number is to decide whether one package is newer than another; it is not normally displayed to
# the user. This number need not have anything to do with the "marketing version" of your app.
minUpgradableAppVersion @5 :UInt32;
# The minimum version of the app which can be safely replaced by this app package without data
# loss. This might be non-zero if the app's data store format changed drastically in the past
# and the app is no longer able to read the old format.
appMarketingVersion @6 :Util.LocalizedText;
# Human-readable presentation of the app version, e.g. "2.9.17". This will be displayed to the
# user to distinguish versions. It _should_ match the way you identify versions of your app to
# users in documentation and marketing.
minApiVersion @0 :UInt32;
maxApiVersion @1 :UInt32;
# Min and max API versions against which this app is known to work. `minApiVersion` primarily
# exists to warn the user if their instance is too old. If the sandstorm instance is newer than
# `maxApiVersion`, it may engage backwards-compatibility hacks and hide features introduced in
# newer versions.
metadata @8 :Metadata;
# Stuff that's not important to actually executing the app, but important to how the app is
# presented to the user in the Sandstorm UI and app marketplace.
struct Command {
# Description of a command to execute.
# Note that commands specified this way are NOT interpreted by a shell. If you want shell
# expansion, you must include a shell binary in your app and invoke it to interpret the
# command.
argv @1 :List(Text);
# Argument list, with the program name as argv[0].
environ @2 :List(Util.KeyValue);
# Environment variables to set. The environment will be completely empty other than what you
# define here.
deprecatedExecutablePath @0 :Text;
# (Obsolete) If specified, will be inserted at the beginning of argv. This is now redundant
# because you should just specify the program as argv[0]. To be clear, this does not and did
# never provide a way to make argv[0] contain something other than the executable name, as
# you can technically do with the `exec` system call.
struct Action {
input :union {
none @0 :Void;
# This action creates a new grain with no input.
capability @1 :List(Powerbox.PowerboxDescriptor);
# This action creates a new grain from a powerbox offer. When a capability matching the query
# is offered to the user (e.g. by another application calling SessionContext.offer()), this
# action will be listed as one of the things the user can do with it.
# On startup, the platform will call create the first session with
# `UiView.newOfferSession()`.
command @2 :Command;
# Command to execute (in a newly-allocated grain) to run this action.
title @3 :Util.LocalizedText;
# (Obsolete) Title of this action, to display in the action selector. This should no longer
# be used for new apps.
nounPhrase @5 : Util.LocalizedText;
# When this action is run, what kind of thing is created? E.g. Etherpad creates a "document".
# Displayed as "New <nounPhrase>" in the "create new grain" UI.
description @4 :Util.LocalizedText;
# Description of this action, suitable for help text.
actions @2 :List(Action);
# Actions which this grain offers.
continueCommand @3 :Command;
# Command to run to restart an already-created grain.
struct SourceMap {
# Defines where to find files that need to be included in a package. This is usually combined
# with a list of files that the package is expected to contain in order to compile a package.
# The list of files may come from using "spk dev" to
searchPath @0 :List(Mapping);
# List of directories to map into the package.
struct Mapping {
# Describes a directory to be mapped into the package.
packagePath @0 :Text;
# Path where this directory should be mapped into the package. Must be a canonical file name
# (no "." nor "..") and must not start with '/'. Omit to map to the package root directory.
sourcePath @1 :Text;
# Path on the local system where this directory may be found. Relative paths are interpreted
# relative to the location of the package definition file.
hidePaths @2 :List(Text);
# Names of files or subdirectories within the directory which should be hidden when mapping
# this path into the spk. Use only canonical paths here -- i.e. do not use ".", "..", or
# multiple consecutive slashes. Do not use a leading slash.
struct BridgeConfig {
# Configuration variables specific to apps that are using sandstorm-http-bridge. This includes
# things that need to be communicated to the bridge process before the app starts up, such as
# permissions.
viewInfo @0 :Grain.UiView.ViewInfo;
# What to return from the UiView's getViewInfo(). This structure defines, among other things, the
# list of shareable permissions and roles that apply to this app. See grain.capnp for more details.
# When a request comes in from the user, sandstorm-http-bridge will set the
# X-Sandstorm-Permissions header to a comma-delimited list of permission names corresponding to
# the user's permissions.
apiPath @1 :Text;
# Specifies a path which will be prefixed to all API requests -- that is, requests coming in
# through the API endpoint as described in:
# Note that this form of HTTP APIs is old and will eventually be deprecated. In the fantastic
# future, available APIs should be defined by `powerboxApis`, below. However, as of this writing,
# `powerboxApis` cannot do everything that old-style HTTP APIs do. Why yes, I did once work at
# Google, how could you tell?
# apiPath must end with "/".
# WARNING: Specifying this does NOT prevent access to other paths. Anyone holding an API token
# can convert it into a sharing token, which provides full access to the grain's web interface.
# The purpose of apiPath is only to allow you to design your API URL schema independently of
# your UI's URL schema, which is often convenient. To actually restrict what an API token
# holder is allowed to do, you MUST use permissions and enforce them for both API and UI
# requests. See:
saveIdentityCaps @2 :Bool;
# If true, the first time a new user accesses the grain, the bridge will save the user's Identity
# capability so that it can be fetched later using `SandstormHttpBridge.getSavedIdentity`. You
# will probably want to enable this if your app supports notifications.
expectAppHooks @4 :Bool;
# Set this to true if you are using sandstorm-http-bridge and want to do any of
# the following:
# - Provide an implementation of getViewInfo() that generates its result dynamically,
# rather than statically defining the result via `viewInfo` above.
# - Support exporting persistent capabilities other than the HTTP APIs specified by
# PowerboxApi below.
# If this is true, the bridge will expect the application to establish a capnproto
# connection to the bridge via `/tmp/sandstorm-api`, and it will expect the app's
# bootstrap interface on this connection to implement `AppHooks`, defined in
# sandstorm-http-bridge.capnp. The methods described there can be used to
# implement the above functionality.
powerboxApis @3 :List(PowerboxApi);
struct PowerboxApi {
# Defines an HTTP API which this application exports, to which other apps can request access
# via the powerbox.
# Use this to define APIs to which other applications can request access.
# Note that this metadata is consulted at the time of a powerbox request. Once the request is
# complete and a connection has been formed, future changes to the app's manifest will not
# affect the already-made connection.
name @0 :Text;
# Symbolic name for this API. This is passed back to the app as the value of the
# `X-Sandstorm-Api` header, in all requests made to this API. Note that removing an API
# definition will NOT revoke existing connections formed through the powerbox. So, the app
# should be prepared to handle all API names that it has ever defined (or, perhaps, return
# an appropriate error when it receives a request for an API that no longer exists).
displayInfo @1 :Powerbox.PowerboxDisplayInfo;
# Information for display to the user when representing this API.
# displayInfo.title in particular will be used when displaying a chooser to the user to choose
# among the app's available APIs.
path @2 :Text;
# Specifies the path which will be prefixed to all requests to this API.
# Like `apiPath` (above), this must end with "/". It is perfectly reasonable for the path to be
# just "/", if you consider your app's entire HTTP surface to be an API. However, note that
# unlike with `apiPath`, restricting a powerbox API to a sub-path actually does prevent
# consumers of the API from accessing other paths. You may wish to use this to your advantage.
tag @3 :ApiSession.PowerboxTag;
# Tag defining this API for powerbox matching purposes.
permissions @4 :Identity.PermissionSet;
# The permissions represented by this API. A user interacting with the powerbox will not have
# the option of choosing this API unless they possess at least these permissions. Meanwhile,
# when a request is made to this API, the `Sandstorm-Permissions` header will always contain
# exactly these permissions, even if the user who made the powerbox connection has greater
# permissions.
# If not specified, the `X-Sandstorm-Permissions` header will contain the exact list of
# permissions that the user had at the time that they formed the connection. Note that if any
# of these permissions are later revoked from the user, then the API connection will be revoked
# in whole.
# TODO(someday): Allow non-singleton APIs, for grains that contain many objects. Requires app
# to implement an embeddable picker UI.
# TODO(someday): Allow implementing Cap'n Proto APIs via JSON conversion.
struct Metadata {
# Data which is not needed specifically to execute the app, but is useful for purposes like
# marketing and display.
# Technically, appMarketingVersion and appTitle belong in this category, but they were defined
# before Metadata became a thing.
# NOTE: Any changes here which add new blobs may require updating the front-end so that it
# correctly extracts those blobs into separate assets on install.
icons :group {
# Various icons to represent the app in various contexts.
# Each context is associated with a List(Icon). This list contains versions of the same image
# in varying sizes and formats. When the icon is used, the optimal icon image will be chosen
# from the list based on the context where it is to be displayed, possibly taking into account
# parameters like size, display pixel density, and browser image format support.
appGrid @0 :Icon;
# The icon shown on the app grid, in the "new grain" flow.
# Size: 128x128
# Data size limit: 64kB
grain @1 :Icon;
# The icon shown to represent an individual grain, both in the grain table and on the navbar.
# If omitted, the appGrid icon will be used.
# Size: 24x24
# Data size limit: 4kB
market @2 :Icon;
# The icon shown in the app market grid. If omitted, the appGrid icon will be used.
# Size: 150x150
# Data size limit: 64kB
marketBig @18 :Icon;
# The image shown in the app market when visiting the app's page directly, or when featuring
# a particular app with a bigger display. If omitted, the regular market icon will be used
# (raster images may look bad).
# Size: 300x300
# Data size limit: 256kB
struct Icon {
# Represents one icon image.
union {
unknown @0 :Void;
# Unknown file format.
svg @1 :Text;
# Scalable Vector Graphics image. This format is *highly* preferred whenever possible.
# The uncompressed SVG can be up to 4x the documented size limit, to account for the fact
# that it will be compressed when served.
png :group {
# PNG image. You may specify one or both DPI levels.
dpi1x @2 :Data;
# Normal-resolution PNG. The image's resolution should exactly match the documented
# viewport size.
dpi2x @3 :Data;
# Double-resolution PNG for high-dpi displays. Any documented data size limit is also
# doubled for this PNG. (The size limit is only doubled, not quadrupled, because the size
# of a compressed image should be a function of total interior edge length, not area.)
website @3 :Text;
# URL of the app's main web site.
codeUrl @4 :Text;
# URL of the app's source code repository, e.g. a Github URL.
# This field is required if the app's license requires redistributing code (such as the GPL),
# but is optional otherwise.
license :group {
# How is this app licensed?
# Example usage for open source licenses:
# license = (openSource = apache2, notices = (defaultText = embed "notices.txt")),
# Example usage for proprietary licenses:
# license = (proprietary = (defaultText = embed "license.txt"),
# notices = (defaultText = embed "notices.txt")),
union {
none @5 :Void;
# No license. Default copyright rules apply; e.g. redistribution is prohibited. See:
# "None" does NOT mean "public domain". See `publicDomain` below.
openSource @6 :OpenSourceLicense;
# Indicates an OSI-approved open source license.
# If you choose such a license, the license title will be displayed with your app on the app
# market, and users who specify they want to see only open source apps will see your app.
proprietary @7 :Util.LocalizedText;
# Full text of a non-OSI-approved license.
# Proprietary licenses usually not only restrict copying but also place limitations on *use*
# of the app. In other words, while open source licenses grant users additional freedoms
# compared to default copyright rules, proprietary licenses impose additional restrictions.
# Because of this, the user must explicitly agree to the license. Sandstorm will display the
# license to the user and ask them to agree before they can start using the app.
# If your license does not require such approval -- because it does not add any restrictions
# beyond default copyright protections -- consider whether it would make sense to use `none`
# instead; this will avoid prompting the user.
publicDomain @8 :Util.LocalizedText;
# Indicates that the app is placed in the public domain; you place absolutely no restrictions
# on its use or distribution. The text is your public domain dedication statement. Please
# note that public domain is not recognized in all jurisdictions, therefore using public
# domain is widely considered risky. The Open Source Initiative recommends using a permissive
# license like MIT's rather than public domain. provides resources to help you
# use public domain; it is highly recommended that you read it before using this.
notices @9 :Util.LocalizedText;
# Contains any third-party copyright notices that the app is required to display, for example
# due to use of third-party open source libraries.
categories @10 :List(Category);
# List of categories/genres to which this app belongs, sorted with best fit first. See the
# `Category` enum below.
# You can list multiple categories, but note that as with all things the app market moderators
# may ask you to make changes, e.g. if you list categories that don't fit or seem spammy.
author :group {
# Fields relating to the author of this app.
# The "author" might be a human, but could also be a company, or a pseudo-identity created to
# represent the app itself.
# It is extremely important to users that they be able to verify the author's identity in a way
# that is not susceptible to spoofing or forgery. Therefore, we *only* identify the author by
# PGP key. Various PGP infrastructure exists which can be used to determine the author's
# identity based on their PGP key. For example, has done a really good job of
# connecting PGP keys to other Internet identities in a verifiable way.
upstreamAuthor @19 :Text;
# Name of the original primary author of this app, if it is different from the person who
# produced the Sandstorm package. Setting this implies that the author connected to the PGP
# signature only "ported" the app to Sandstorm.
contactEmail @11 :Text;
# Email address to contact for any issues with this app. This includes end-user support
# requests as well as app store administrator requests, so it is very important that this be a
# valid address with someone paying attention to it.
pgpSignature @12 :Data;
# PGP signature attesting responsibility for the app ID. This is a binary-format detached
# signature of the following ASCII message (not including the quotes, no newlines, and
# replacing <app-id> with the standard base-32 text format of the app's ID):
# "I am the author of the app with the following ID: <app-id>"
# You can create a signature file using `gpg` like so:
# echo -n "I am the author of the app with the following ID: <app-id>" |
# gpg --sign > pgp-signature
# To learn how to set up gpg, visit Keybase ( -- they have excellent
# documentation and tools. Moreover, if you create a Keybase account for your key and follow
# Keybase's instructions to link it to other social accounts (like your Github account), then
# the Sandstorm app install flow and app market will present this information to the user as
# "verified".
pgpKeyring @13 :Data;
# A keyring in GPG keyring format containing all public keys needed to verify PGP signatures in
# this manifest (as of this writing, there is only one: `author.pgpSignature`).
# To generate a keyring containing just your public key, do:
# gpg --export <key-id> > keyring
# Where `<key-id>` is a PGP key ID or email address associated with the key.
description @14 :Util.LocalizedText;
# The app's description in Github-flavored Markdown format, to be displayed e.g.
# in an app store. Note that the Markdown is not permitted to cotnain HTML nor image tags (but
# you can include a list of screenshots separately).
shortDescription @15 :Util.LocalizedText;
# A very short (one-to-three words) description of what the app does. For example,
# "Document editor", or "Notetaking", or "Email client". This will be displayed under the app
# title in the grid view in the app market.
screenshots @16 :List(Screenshot);
# Screenshots to use for marketing purposes.
struct Screenshot {
width @0 :UInt32;
height @1 :UInt32;
# Width and height of the screenshot in "device-independent pixels". The actual width and
# height of the image is in the image data, but this width and height is used to decide how
# much to scale the image for it to "look right". Typically, a screenshot taken on a high-DPI
# display should specify this width and height as half of the actual image width and height.
# The market is under no obligation to display images with any particular size; these are just
# hints.
union {
unknown @2 :Void;
# Unknown file format.
png @3 :Data;
# PNG-encoded image data. Usually preferred for screenshots.
jpeg @4 :Data;
# JPEG-encoded image data. Preferred for screenshots that contain photographs or the like.
changeLog @17 :Util.LocalizedText;
# Documents the history of changes in Github-flavored markdown format (with the same restrictions
# as govern `description`). We recommend formatting this with an H1 heading for each version
# followed by a bullet list of changes.
struct OsiLicenseInfo {
id @0 :Text;
# The file name of the license at, i.e. such that the URL can be constructed as:
title @1 :Text;
# Display title for app market. E.g. "Apache License 2.0".
requireSource @2 :Bool = false;
# Whether or not you are required to provide a `codeUrl` when specifying this license.
annotation osiInfo @0x9476412d0315d869 (enumerant) :OsiLicenseInfo;
# Annotation applied to each item in the OpenSourceLicense enum.
enum OpenSourceLicense {
# Identities an OSI-approved Open Source license. Apps which claim to be "open source" must use
# one of these licenses.
invalid @0; # Sentinel value; do not choose this.
# Recommended licenses, especially for new code. These four licenses cover the spectrum of open
# source license mechanics and are widely recognized and understood.
mit @1 $osiInfo(id = "MIT" , title = "MIT License");
apache2 @2 $osiInfo(id = "Apache-2.0", title = "Apache License v2");
gpl3 @3 $osiInfo(id = "GPL-3.0" , title = "GNU GPL v3", requireSource = true);
agpl3 @4 $osiInfo(id = "AGPL-3.0" , title = "GNU AGPL v3", requireSource = true);
# Other popular general-purpose licenses.
bsd3Clause @5 $osiInfo(id = "BSD-3-Clause", title = "BSD 3-Clause");
bsd2Clause @6 $osiInfo(id = "BSD-2-Clause", title = "BSD 2-Clause");
gpl2 @7 $osiInfo(id = "GPL-2.0" , title = "GNU GPL v2", requireSource = true);
lgpl2 @8 $osiInfo(id = "LGPL-2.1" , title = "GNU LGPL v2.1", requireSource = true);
lgpl3 @9 $osiInfo(id = "LGPL-3.0" , title = "GNU LGPL v3", requireSource = true);
isc @10 $osiInfo(id = "ISC" , title = "ISC License");
# Popular licenses associated with specific languages.
artistic2 @11 $osiInfo(id = "Artistic-2.0", title = "Artistic License v2");
python2 @12 $osiInfo(id = "Python-2.0" , title = "Python License v2");
php3 @13 $osiInfo(id = "PHP-3.0" , title = "PHP License v3");
# Popular licenses associated with specific projects or companies.
mpl2 @14 $osiInfo(id = "MPL-2.0" , title = "Mozilla Public License v2", requireSource = true);
cddl @15 $osiInfo(id = "CDDL-1.0", title = "CDDL", requireSource = true);
epl @16 $osiInfo(id = "EPL-1.0" , title = "Eclipse Public License", requireSource = true);
cpal @17 $osiInfo(id = "CPAL-1.0" , title = "Common Public Attribution License", requireSource = true);
zlib @18 $osiInfo(id = "Zlib" , title = "Zlib/libpng License");
# Is your preferred license not on the list? We are happy to add any OSI-approved license; that
# is, anything on this page:
# Feel free to send a pull request adding yours.
struct AppId {
id0 @0 :UInt64;
id1 @1 :UInt64;
id2 @2 :UInt64;
id3 @3 :UInt64;
struct PackageId {
id0 @0 :UInt64;
id1 @1 :UInt64;
struct VerifiedInfo {
# `spk verify --capnp` writes this to stdout. Also, `spk verify --details` writes a JSON version
# of this, with large `Data` and `Text` fields removed and `LocalizedText` simplified to their
# `defaultText`.
appId @0 :AppId;
packageId @1 :PackageId;
# App and package ID computed from package file signature and hash.
title @2 :Util.LocalizedText;
version @3 :UInt32;
marketingVersion @4 :Util.LocalizedText;
authorPgpKeyFingerprint @5 :Text;
metadata @6 :Metadata;
# Stuff extracted directly from manifest.
struct CategoryInfo {
title @0 :Text;
annotation categoryInfo @0x8d51dd236606d205 (enumerant) :CategoryInfo;
enum Category {
# ------------------------------------
# "Meta": communication & coordination
productivity @1 $categoryInfo(title = "Productivity");
# Apps which manage productivity -- i.e. the "meta" apps you use to "get organized", NOT the apps
# you use to actually produce content.
# Examples: Note-taking, to-dos, calendars, kanban boards, project management, team management.
# NON-examples: Document editors (-> office), e-mail (-> communications).
communications @2 $categoryInfo(title = "Communications");
# Email, chat, conferencing, etc. Things that you use primarily to communicate, not to organize.
social @3 $categoryInfo(title = "Social");
# Social networking. Overlaps with communication, but focuses on organizing a network of people
# and surfacing content and interactions from your network that aren't explicitly addressed
# to you.
# ------------------------------------
# Content creation
webPublishing @4 $categoryInfo(title = "Web Publishing");
# Tools for publishing web sites and blogs.
office @5 $categoryInfo(title = "Office");
# Tools for the office: editors for documents, spreadsheets, presentations, etc.
developerTools @6 $categoryInfo(title = "DevTools");
# Tools for software engineering: source control, test automation, compilers, IDEs, etc.
science @7 $categoryInfo(title = "Science");
# Tools for scientific / academic pursuits: data gathering, data processing, paper publishing,
# etc.
graphics @10 $categoryInfo(title = "Graphics");
# Tools for creating graphics / visual art.
# ------------------------------------
# Content consumption
media @8 $categoryInfo(title = "Media");
# Content *consumption*: Apps that aren't used to create content, but are used to display and
# consume it. Music players, photo galleries, video, feed readers, etc.
games @9 $categoryInfo(title = "Games");
# Games.
# ------------------------------------
other @0 $categoryInfo(title = "Other");
# Use if nothing else fits -- but consider sending us a pull request to add a better category!
# ==============================================================================
# Below this point is not interesting to app developers.
# TODO(cleanup): Maybe move elsewhere?
struct KeyFile {
# A public/private key pair, as generated by libsodium's crypto_sign_keypair.
# The keyring maintained by the spk tool contains a sequence of these.
# TODO(someday): Integrate with desktop environment's keychain for more secure storage.
publicKey @0 :Data;
privateKey @1 :Data;
const magicNumber :Data = "\x8f\xc6\xcd\xef\x45\x1a\xea\x96";
# A sandstorm package is a file composed of two messages: a `Signature` and an `Archive`.
# Additionally, the whole file is XZ-compressed on top of that, and the XZ data is prefixed with
# `magicNumber`. (If a future version of the package format breaks compatibility, the magic number
# will change.)
struct Signature {
# Contains a cryptographic signature of the `Archive` part of the package, along with the public
# key used to verify that signature. The public key itself is the application ID, thus all
# packages signed with the same key will be considered to be different versions of the same app.
publicKey @0 :Data;
# A libsodium crypto_sign public key.
# libsodium signing public keys are 32 bytes. The application's ID is simply a textual
# representation of this key.
signature @1 :Data;
# libsodium crypto_sign signature of the crypto_hash of the `Archive` part of the package
# (i.e. the package file minus the header).
struct Archive {
# A tree of files. Used to represent the package contents.
files @0 :List(File);
struct File {
name @0 :Text;
# Name of the file.
# Must not contain forward slashes nor NUL characters. Must not be "." nor "..". Must not
# be the same as any other file in the directory.
lastModificationTimeNs @5 :Int64;
# Modification timestamp to apply to the file after unpack. Measured in nanoseconds.
union {
regular @1 :Data;
# Content of a regular file.
executable @2 :Data;
# Content of an executable.
symlink @3 :Text;
# Symbolic link path. The link will be interpreted in the context of the sandbox, where the
# archive itself mounted as the root directory.
directory @4 :List(File);
# A subdirectory containing a list of files.