Spaces:
Running
Running
File size: 5,892 Bytes
a6a5d8e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | # 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.
|