a11oy / artifacts /a11oy-uds /docs /SECURITY.md
betterwithage's picture
sync(space): full source mirror — resolve all GitHub<->Space drift (CTO)
a6a5d8e verified
|
Raw
History Blame
5.89 kB
# a11oy — Security & Provenance
## Threat surface
The shipped payload is **pure-functional TypeScript compiled to ESM
JavaScript**. It contains:
- No network code (no `fetch`, no `http`, no sockets).
- No filesystem code (no `fs`, no `child_process`).
- No `eval`, no dynamic `Function(...)` construction.
- No native bindings, no WASM modules.
- No third-party runtime dependencies (the `package.json` files declare
zero `dependencies` blocks).
The runtime is consumed by a host process that supplies inputs and persists
outputs. The host process — not this payload — owns I/O, authentication,
and authorization.
## Build provenance
Every tarball ships a `MANIFEST.json` at its root with:
| field | meaning |
|-------------------|-----------------------------------------------------------|
| `name` | `a11oy-uds` |
| `version` | semver from `artifacts/a11oy-uds/package.json` |
| `gitSha` | short SHA of the source commit |
| `builtAt` | ISO-8601 UTC build timestamp |
| `hashAlgorithm` | always `sha256` |
| `sourcePackaged` | `false` on release builds (compiled `dist/`, not `src/`) |
| `fileCount` | total file count across both packages |
| `totalBytes` | sum of `files[].size` |
| `files[].path` | path relative to `/opt/a11oy/` |
| `files[].size` | byte length |
| `files[].sha256` | SHA-256 hex digest of the file |
The manifest is sorted by `path` so a re-run on the same inputs produces
byte-identical output. The build script (`artifacts/a11oy-uds/scripts/build.sh`)
refuses to package `src/` instead of `dist/` unless the operator explicitly
sets `A11OY_UDS_ALLOW_SOURCE_FALLBACK=1` — this is forbidden for release
output and the `sourcePackaged` field would surface the violation.
## Signature chain (dev channel — this hand-off)
```
a11oy-uds-<version>.tar.zst ← Zarf package (zstd-compressed tar)
a11oy-uds-<version>.tar.zst.sha256 ← GNU coreutils sha256 sidecar
a11oy-uds-<version>.tar.zst.sig ← cosign blob signature (PEM-key)
a11oy-uds-dev.pub ← matching cosign public key
```
Verify:
```bash
sha256sum -c a11oy-uds-<version>.tar.zst.sha256
cosign verify-blob \
--key a11oy-uds-dev.pub \
--signature a11oy-uds-<version>.tar.zst.sig \
a11oy-uds-<version>.tar.zst
```
The public key is also committed to source at
`artifacts/a11oy-uds/release-keys/a11oy-uds-dev.pub` so a verifier can pin to
the in-source copy rather than the bundled one.
Trust root: in-band hand-off + matching public key. Suitable for the dev
channel and direct operator delivery. **Not** suitable for ungated
download-and-deploy scenarios — use the release channel instead.
## Signature chain (release channel — GHCR + cosign keyless OIDC)
Once published from a `v*.*.*` git tag, the same byte-identical tarball is
pushed to `ghcr.io/szl-holdings/a11oy-uds:<version>` and signed via cosign
keyless OIDC against the GitHub Actions workflow identity. Verify:
```bash
# Identity regex matches whichever szl-holdings repo currently hosts the
# `a11oy-uds-publish.yml` workflow file. The workflow may live in
# `szl-holdings/platform` (monorepo) or `szl-holdings/a11oy` (extracted);
# both produce the same byte-identical tarball and a valid Fulcio cert.
cosign verify \
--certificate-identity-regexp 'https://github.com/szl-holdings/.+/\.github/workflows/a11oy-uds-publish\.yml@.+' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/szl-holdings/a11oy-uds:<version>
```
Trust root: GitHub's OIDC issuer + the published workflow identity. Suitable
for any UDS / Zarf consumer with internet egress to the Sigstore Rekor +
Fulcio infrastructure.
## Determinism
The build is reproducible:
- Zarf produces a sorted tar with the package metadata canonicalised by the
`zarf` binary (no operator-controlled fields leak in).
- The non-zarf fallback (development only) uses
`tar --sort=name --owner=0 --group=0 --numeric-owner --mtime="${BUILD_TS}"`
followed by `zstd -19` so the output sha256 is fixed by the inputs and the
build timestamp alone.
This is what makes cross-channel digest comparison meaningful: the dev
hand-off tarball and the GHCR-published tarball have **the same sha256** when
built from the same git SHA at the same `builtAt`.
## Key rotation
The dev-channel cosign key is project-managed. To rotate:
1. Generate a new keypair:
`COSIGN_PASSWORD="" cosign generate-key-pair`
2. Update `artifacts/a11oy-uds/release-keys/a11oy-uds-dev.pub`.
3. Re-sign the existing tarball(s) and update the `.sig` sidecars.
4. Cut a new prerelease so consumers see the new key alongside the new
signature.
The release-channel keyless OIDC signature has no static key to rotate — its
identity is tied to the GitHub Actions workflow file path and the repo SHA.
## Vulnerability surface
Because the runtime has zero third-party runtime dependencies and no I/O,
the dependency-derived vulnerability surface is empty. The development
dependencies used to **build** the runtime (TypeScript, etc.) do not ship to
the operator — they are pruned out of the `dist/` staging step.
SBOMs for the package are generated by Zarf at build time and can be
extracted post-hoc:
```bash
zarf package inspect sbom-extract a11oy-uds-<version>.tar.zst --output ./sbom
```
The extracted SBOM enumerates every staged file with size + sha256 and
flags any non-zero dependency entries. For v0.1.0 the dependency entry count
is zero.