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.