-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Expand file tree
/
Copy pathsecurity-smoke.sh
More file actions
executable file
·483 lines (440 loc) · 21.7 KB
/
Copy pathsecurity-smoke.sh
File metadata and controls
executable file
·483 lines (440 loc) · 21.7 KB
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
#!/usr/bin/env bash
# Cheap security policy checks for the elizaOS Live overlay.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
STRICT="${ELIZAOS_SECURITY_STRICT:-0}"
cd "${ROOT}"
failures=0
warnings=0
fail() {
echo "FAIL: $*" >&2
failures=$((failures + 1))
}
warn() {
echo "WARN: $*" >&2
warnings=$((warnings + 1))
}
strict_or_warn() {
if [ "${STRICT}" = "1" ]; then
fail "$@"
else
warn "$@"
fi
}
require_file() {
if [ ! -f "$1" ]; then
fail "missing required file: $1"
fi
}
require_grep() {
pattern="$1"
path="$2"
message="$3"
if ! grep -Eq "${pattern}" "${path}"; then
fail "${message}: ${path}"
fi
}
require_fixed() {
needle="$1"
path="$2"
message="$3"
if ! grep -Fq "${needle}" "${path}"; then
fail "${message}: ${path}"
fi
}
for path in \
tails/config/chroot_local-includes/usr/local/lib/elizaos/capability-runner \
tails/config/chroot_local-includes/etc/generate-sudoers.d/elizaos-capability-runner.toml \
tails/config/chroot_local-includes/usr/lib/python3/dist-packages/tps/configuration/features.py \
tails/config/chroot_local-includes/usr/local/lib/elizaos/persistence-maintenance \
tails/config/chroot_local-includes/usr/local/lib/elizaos/runtime-env \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-health-check \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-manager \
tails/config/chroot_local-includes/etc/systemd/system/elizaos-update-health-check.service \
tails/config/chroot_local-includes/etc/systemd/system/elizaos-update-verify.service \
tails/config/chroot_local-includes/etc/systemd/system/milady.service \
tails/config/chroot_local-includes/etc/systemd/user/elizaos-agent.service \
tails/config/chroot_local-includes/etc/systemd/user/elizaos-renderer.service \
tails/config/chroot_local-includes/etc/systemd/user/milady.service \
schemas/update-manifest.schema.json \
schemas/model-catalog.schema.json
do
require_file "${path}"
done
echo "==> capability broker boundary"
broker=tails/config/chroot_local-includes/usr/local/lib/elizaos/capability-runner
sudoers=tails/config/chroot_local-includes/etc/generate-sudoers.d/elizaos-capability-runner.toml
require_fixed 'executable = "/usr/local/lib/elizaos/capability-runner"' \
"${sudoers}" "elizaOS sudoers must target only capability-runner"
require_fixed 'allowed_user = "amnesia"' \
"${sudoers}" "elizaOS sudoers must be limited to the live user"
require_fixed 'args = ["root-status"]' \
"${sudoers}" "elizaOS sudoers must allow only the root-status smoke command"
if grep -Eq 'ALL|arbitrary_arguments|NOPASSWD: ALL|/bin/(ba)?sh|apt(-get)?|systemctl|service |nmcli|dd |mkfs|mount |umount ' "${sudoers}"; then
fail "elizaOS sudoers grants broad or mutating root authority: ${sudoers}"
fi
for command_name in status root-status open-persistent-storage privacy-mode; do
require_grep "^[[:space:]]*${command_name}\\)" "${broker}" \
"capability-runner missing command allowlist case ${command_name}"
done
if grep -Eq 'apt(-get)?|dpkg|systemctl|service |nmcli|iptables|nft |dd |mkfs|mount |umount |parted|sgdisk|curl |wget ' "${broker}"; then
fail "capability-runner exposes broad package, network, service, or disk mutation"
fi
require_fixed 'exec sudo -n "${self}" "$@"' "${broker}" \
"capability-runner must recurse through the exact sudo allowlist"
echo "==> broad sudoers inventory"
sudoers_review=docs/inherited-tails-sudoers-review.md
require_file "${sudoers_review}"
reviewed_inherited_sudoers=(
tails/config/chroot_local-includes/etc/generate-sudoers.d/tails-greeter-cryptsetup.toml
tails/config/chroot_local-includes/etc/generate-sudoers.d/tails-greeter-umount.toml
tails/config/chroot_local-includes/etc/generate-sudoers.d/tbb.toml
tails/config/chroot_local-includes/etc/generate-sudoers.d/tps.toml
tails/config/chroot_local-includes/etc/generate-sudoers.d/upgrade.toml
tails/config/chroot_local-includes/etc/generate-sudoers.d/whisperback.toml
)
is_reviewed_inherited_sudoers() {
local candidate="$1"
local reviewed
for reviewed in "${reviewed_inherited_sudoers[@]}"; do
if [ "${candidate}" = "${reviewed}" ]; then
return 0
fi
done
return 1
}
for reviewed in "${reviewed_inherited_sudoers[@]}"; do
require_file "${reviewed}"
require_fixed "$(basename "${reviewed}")" "${sudoers_review}" \
"inherited sudoers review missing entry"
done
while IFS= read -r file; do
[ -n "${file}" ] || continue
case "${file}" in
*elizaos*)
fail "unexpected broad elizaOS sudoers rule: ${file}"
;;
*)
if ! is_reviewed_inherited_sudoers "${file}"; then
strict_or_warn "unreviewed inherited Tails broad sudoers rule: ${file}"
fi
;;
esac
done < <(
rg -l 'executable = "ALL"|NOPASSWD: ALL|arbitrary_arguments]' \
tails/config/chroot_local-includes/etc/generate-sudoers.d || true
)
echo "==> runtime packaging boundary"
require_file docs/runtime-packaging.md
require_file scripts/prepare-milady-app-overlay.mjs
require_file scripts/validate-runtime-overlay.mjs
require_file tails/config/chroot_local-hooks/9100-install-milady
require_fixed 'not a post-boot injection path' docs/runtime-packaging.md \
"runtime packaging docs must reject post-boot injection"
require_fixed 'Resources/app/elizaos-live-overlay-manifest.json' docs/runtime-packaging.md \
"runtime packaging docs must require the live overlay manifest"
require_fixed 'Production replacement:' docs/runtime-packaging.md \
"runtime packaging docs must name the production replacement path"
require_fixed 'elizaos-live-overlay-manifest.json' scripts/prepare-milady-app-overlay.mjs \
"runtime prepare step must write an overlay manifest"
require_fixed 'manifest package inventory does not match staged node_modules' \
scripts/validate-runtime-overlay.mjs \
"runtime validator must compare package inventory"
require_grep '^src=/usr/share/elizaos/milady-app$' \
tails/config/chroot_local-hooks/9100-install-milady \
"runtime install hook must use the staged live-build app root"
require_grep '^dst=/opt/milady$' \
tails/config/chroot_local-hooks/9100-install-milady \
"runtime install hook must install to the immutable factory runtime root"
if grep -Eq 'curl |wget |git clone|npm install|bun install|pnpm install|yarn install' \
tails/config/chroot_local-hooks/9100-install-milady; then
fail "runtime install hook must not fetch or resolve dependencies during image build"
fi
echo "==> persistence boundary"
python3 - <<'PY'
from pathlib import Path
path = Path("tails/config/chroot_local-includes/usr/lib/python3/dist-packages/tps/configuration/features.py")
text = path.read_text()
required = {
'Binding("milady/eliza", "/home/amnesia/.eliza")',
'Binding("milady/milady", "/home/amnesia/.milady")',
'Binding("milady/config", "/home/amnesia/.config/elizaOS")',
'Binding("milady/config-legacy", "/home/amnesia/.config/milady")',
'Binding("milady/config-legacy-caps", "/home/amnesia/.config/Milady")',
'Binding("milady/cef-cache", "/home/amnesia/.cache/ai.elizaos.app")',
'Binding("milady/cef-cache-legacy", "/home/amnesia/.cache/ai.milady.milady")',
'translatable_name = "elizaOS Data"',
}
missing = sorted(item for item in required if item not in text)
if missing:
raise SystemExit(f"{path}: missing elizaOS persistence policy: {missing}")
import re
start = text.index("class MiladyData")
end = text.index("class WelcomeScreen", start)
milady = text[start:end]
for name, target in re.findall(r'Binding\("([^"]+)", "([^"]+)"', milady):
if not name.startswith("milady/"):
continue
if target == "/home/amnesia" or target.startswith(("/etc", "/usr", "/var", "/root", "/opt")):
raise SystemExit(f"{path}: forbidden elizaOS persistence target: {name} -> {target}")
if "uses_symlinks=True" in milady:
raise SystemExit(f"{path}: elizaOS persistence must use bind mounts, not symlink persistence")
PY
persistence_helper=tails/config/chroot_local-includes/usr/local/lib/elizaos/persistence-maintenance
require_fixed 'run_dir=/run/elizaos' "${persistence_helper}" \
"persistence maintenance must use a root-owned runtime guard directory"
require_fixed 'flag="${run_dir}/persistence-maintenance"' "${persistence_helper}" \
"persistence maintenance must expose an activation/deactivation guard"
require_fixed 'kill --kill-whom=all --signal=TERM' "${persistence_helper}" \
"persistence maintenance must quiesce user services before bind changes"
require_fixed 'kill --kill-whom=all --signal=KILL' "${persistence_helper}" \
"persistence maintenance must force-stop user services if graceful stop fails"
if grep -Eq 'pkill .* -u amnesia|rm -rf -- /home/amnesia($|/")|chown .* /home/amnesia' "${persistence_helper}"; then
fail "persistence-maintenance must not mutate the whole live-user home"
fi
cache_hook=tails/config/chroot_local-includes/usr/local/lib/persistent-storage/on-activated-hooks/MiladyData/10-clean-runtime-state
require_file "${cache_hook}"
require_fixed 'find -P "${state_dir}" -xdev' "${cache_hook}" \
"cache cleanup must not cross symlinks or filesystems"
if grep -Eq '/home/amnesia($|[[:space:]])|rm -rf -- /home/amnesia' "${cache_hook}"; then
fail "cache cleanup must stay within elizaOS-owned persistence paths"
fi
echo "==> service constraints"
root_unit=tails/config/chroot_local-includes/etc/systemd/system/milady.service
require_fixed 'ExecStart=/usr/local/lib/elizaos/milady-keeper' "${root_unit}" \
"root supervisor must run only the keeper"
require_fixed 'NoNewPrivileges=yes' "${root_unit}" \
"root supervisor must disable privilege escalation"
require_fixed 'PrivateTmp=yes' "${root_unit}" \
"root supervisor must use a private tmp"
require_fixed 'ProtectSystem=full' "${root_unit}" \
"root supervisor must protect the system tree"
if grep -Eq 'ExecStart=.*(/usr/local/bin/milady|/opt/milady|launcher)' "${root_unit}"; then
fail "root system service must not launch the app runtime directly"
fi
for unit in \
tails/config/chroot_local-includes/etc/systemd/user/elizaos-agent.service \
tails/config/chroot_local-includes/etc/systemd/user/elizaos-renderer.service \
tails/config/chroot_local-includes/etc/systemd/user/milady.service
do
require_fixed 'ConditionUser=1000' "${unit}" \
"user service must be pinned to the live user"
require_fixed 'NoNewPrivileges=yes' "${unit}" \
"user service must disable privilege escalation"
if grep -q 'After=.*desktop.target' "${unit}"; then
fail "user service must not wait on the inherited Tails desktop target: ${unit}"
fi
done
for launcher in \
tails/config/chroot_local-includes/usr/local/bin/milady \
tails/config/chroot_local-includes/usr/local/lib/elizaos/start-elizaos-agent-user \
tails/config/chroot_local-includes/usr/local/lib/elizaos/start-elizaos-renderer-user
do
require_grep '127\.0\.0\.1|localhost|::1|normalize_loopback_bind' "${launcher}" \
"elizaOS runtime launchers must bind local services to loopback"
done
echo "==> update signing and recovery markers"
if [ -d tails/config/chroot_local-includes/usr/src/iuk ]; then
require_grep 'verify_signature|signature' \
tails/config/chroot_local-includes/usr/src/iuk/lib/Tails/IUK/Utils.pm \
"IUK stack must retain signature verification helpers"
require_grep 'valid signature|invalid signature|trusted key|untrusted key' \
tails/config/chroot_local-includes/usr/src/iuk/features/download_upgrade-description_file/Download_Upgrade-Description_File.feature \
"IUK tests must cover signature trust outcomes"
fi
if ! rg -q 'app/runtime update|model catalog|signed app bundle|signed model' docs/distribution-and-updates.md docs/security-model.md; then
fail "docs must define signed app/model update policy"
fi
if ! rg -q 'rollback|fail-closed|fail closed' docs/distribution-and-updates.md docs/security-model.md; then
fail "docs must define update rollback and fail-closed policy"
fi
update_manager=tails/config/chroot_local-includes/usr/local/lib/elizaos/update-manager
runtime_env=tails/config/chroot_local-includes/usr/local/lib/elizaos/runtime-env
update_unit=tails/config/chroot_local-includes/etc/systemd/system/elizaos-update-verify.service
require_fixed 'gpgv --keyring' "${update_manager}" \
"update manager must verify detached signed manifests"
require_fixed 'write_fallback_selector "missing-keyring"' "${update_manager}" \
"update manager must fail closed when no trusted keyring is present"
require_fixed 'ELIZAOS_RUNTIME_ROOT' "${runtime_env}" \
"runtime selector must export a runtime root for app wrappers"
require_fixed 'ExecStart=/usr/local/lib/elizaos/update-manager verify' "${update_unit}" \
"boot unit must verify staged updates before selecting a runtime"
require_fixed 'modelCatalog' schemas/update-manifest.schema.json \
"update manifest schema must cover a model catalog"
require_fixed 'filesComplete' schemas/update-manifest.schema.json \
"update manifest schema must require complete runtime file inventories"
require_fixed 'elizaos.modelCatalog' schemas/model-catalog.schema.json \
"model catalog schema must identify elizaOS model catalogs"
require_fixed 'contains unlisted file' "${update_manager}" \
"update manager must reject files outside the signed runtime inventory"
require_fixed 'runtime_store' "${update_manager}" \
"update manager must materialize verified runtimes into a root-owned store"
require_fixed 'os.O_NOFOLLOW' "${update_manager}" \
"update manager must materialize files without following source symlinks"
require_fixed 'tempfile.mkstemp' "${update_manager}" \
"update manager must materialize files through temporary files"
require_fixed 'verified runtime file changed while copying' "${update_manager}" \
"update manager must re-check manifest digests during materialization"
if grep -q 'ELIZAOS_ALLOW_RUNTIME_ENV_OVERRIDES' "${runtime_env}"; then
fail "runtime selector must not expose caller-controlled runtime override escape hatches"
fi
if grep -q 'ELIZAOS_BAKED_RUNTIME' "${runtime_env}"; then
fail "runtime selector must not honor caller-supplied baked runtime paths"
fi
require_fixed 'mark-${action} must run as root' "${update_manager}" \
"update promotion markers must be root-only"
require_fixed 'update-manager mark-good' \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-health-check \
"update health checker must promote healthy signed candidates"
require_fixed 'update-manager mark-bad' \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-health-check \
"update health checker must support rollback marking"
require_fixed 'ELIZAOS_UPDATE_HEALTH_MARK_BAD_ON_TIMEOUT' \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-health-check \
"update health checker must fail safe by default before rejecting candidates"
override_probe="$(
ELIZAOS_RUNTIME_ENV_FILE=/tmp/elizaos-fake-runtime.env \
ELIZAOS_BAKED_RUNTIME=/tmp/attacker \
ELIZAOS_RUNTIME_ROOT=/tmp/attacker \
ELIZAOS_BUN=/tmp/attacker/bin/bun \
"${runtime_env}" print
)"
if printf '%s\n' "${override_probe}" | grep -q /tmp/attacker; then
fail "runtime selector honored caller-supplied runtime override paths"
fi
tmp_mark="$(mktemp -d)"
trap 'rm -rf "${tmp_mark}"' EXIT
mkdir -p "${tmp_mark}/run" "${tmp_mark}/state"
cat > "${tmp_mark}/run/runtime-selection.json" <<'JSON'
{"trust":"signed","manifestSha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","channel":"stable","sequence":1}
JSON
set +e
ELIZAOS_RUN_DIR="${tmp_mark}/run" \
ELIZAOS_UPDATE_STATE_DIR="${tmp_mark}/state" \
"${update_manager}" mark-good >/dev/null 2>"${tmp_mark}/mark.err"
mark_code=$?
set -e
if [ "${mark_code}" -ne 77 ]; then
fail "non-root mark-good must exit 77, got ${mark_code}"
fi
if find "${tmp_mark}/state" -type f | grep -q .; then
fail "non-root mark-good wrote update state"
fi
rm -rf "${tmp_mark}"
if ! find tails/config/chroot_local-includes/usr/share/keyrings \
tails/config/chroot_local-includes/etc/elizaos \
-type f \( -name 'elizaos-update.gpg' -o -name 'update-keyring.gpg' \) 2>/dev/null | grep -q .
then
strict_or_warn "production elizaOS update keyring is not baked yet"
fi
if command -v gpg >/dev/null 2>&1 && command -v gpgv >/dev/null 2>&1; then
tmp="$(mktemp -d)"
trap 'rm -rf "${tmp}"' EXIT
export GNUPGHOME="${tmp}/gnupg"
mkdir -m 700 "${GNUPGHOME}"
gpg --batch --quiet --pinentry-mode loopback --passphrase '' \
--quick-gen-key 'elizaOS smoke <smoke@elizaos.invalid>' ed25519 sign 1d
gpg --batch --quiet --export 'smoke@elizaos.invalid' > "${tmp}/elizaos-update.gpg"
runtime="${tmp}/staged/channels/stable/runtime"
mkdir -p "${runtime}/bin" "${runtime}/Resources/app/eliza-dist/node_modules"
printf '#!/bin/sh\nexit 0\n' > "${runtime}/bin/bun"
printf '#!/bin/sh\nexit 0\n' > "${runtime}/bin/launcher"
printf 'console.log("elizaOS smoke")\n' > "${runtime}/Resources/app/eliza-dist/entry.js"
chmod 755 "${runtime}/bin/bun" "${runtime}/bin/launcher"
bun_sha="$(sha256sum "${runtime}/bin/bun" | awk '{print $1}')"
launcher_sha="$(sha256sum "${runtime}/bin/launcher" | awk '{print $1}')"
entry_sha="$(sha256sum "${runtime}/Resources/app/eliza-dist/entry.js" | awk '{print $1}')"
cat > "${tmp}/staged/channels/stable/manifest.json" <<EOF
{
"schemaVersion": 1,
"kind": "elizaos.updateManifest",
"manifestVersion": "smoke",
"channel": "stable",
"sequence": 1,
"publishedAt": "2026-05-17T00:00:00Z",
"runtime": {
"id": "smoke",
"version": "0.0.1",
"bundlePath": "runtime",
"filesComplete": true,
"entrypoints": {
"bun": "bin/bun",
"launcher": "bin/launcher",
"agent": "Resources/app/eliza-dist/entry.js",
"nodeModules": "Resources/app/eliza-dist/node_modules"
},
"files": [
{ "path": "bin/bun", "sha256": "${bun_sha}" },
{ "path": "bin/launcher", "sha256": "${launcher_sha}" },
{ "path": "Resources/app/eliza-dist/entry.js", "sha256": "${entry_sha}" }
]
}
}
EOF
gpg --batch --quiet --yes --pinentry-mode loopback --passphrase '' \
--detach-sign --output "${tmp}/staged/channels/stable/manifest.json.sig" \
"${tmp}/staged/channels/stable/manifest.json"
ELIZAOS_UPDATE_KEYRING="${tmp}/elizaos-update.gpg" \
ELIZAOS_STAGED_UPDATE_DIR="${tmp}/staged" \
ELIZAOS_RUN_DIR="${tmp}/run-good" \
ELIZAOS_RUNTIME_STORE="${tmp}/store" \
ELIZAOS_UPDATE_STATE_DIR="${tmp}/state" \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-manager verify
require_fixed "ELIZAOS_RUNTIME_TRUST=signed" "${tmp}/run-good/runtime.env" \
"signed smoke update must be selected"
if grep -q "${tmp}/staged" "${tmp}/run-good/runtime.env"; then
fail "signed runtime selector must point at the materialized store, not staged user storage"
fi
require_fixed "${tmp}/store" "${tmp}/run-good/runtime.env" \
"signed runtime selector must point at the materialized store"
materialized_entry="$(
sed -n "s/^ELIZAOS_AGENT_ENTRY=//p" "${tmp}/run-good/runtime.env" | tr -d "'"
)"
printf 'materialized tamper\n' >> "${materialized_entry}"
ELIZAOS_UPDATE_KEYRING="${tmp}/elizaos-update.gpg" \
ELIZAOS_STAGED_UPDATE_DIR="${tmp}/staged" \
ELIZAOS_RUN_DIR="${tmp}/run-materialized-tamper" \
ELIZAOS_RUNTIME_STORE="${tmp}/store" \
ELIZAOS_UPDATE_STATE_DIR="${tmp}/state" \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-manager verify
require_fixed "ELIZAOS_RUNTIME_TRUST='baked'" "${tmp}/run-materialized-tamper/runtime.env" \
"tampered materialized runtime must fall back to baked runtime"
rm -rf "${tmp}/store"
printf 'extra\n' > "${runtime}/extra.js"
ELIZAOS_UPDATE_KEYRING="${tmp}/elizaos-update.gpg" \
ELIZAOS_STAGED_UPDATE_DIR="${tmp}/staged" \
ELIZAOS_RUN_DIR="${tmp}/run-unlisted" \
ELIZAOS_RUNTIME_STORE="${tmp}/store-unlisted" \
ELIZAOS_UPDATE_STATE_DIR="${tmp}/state-unlisted" \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-manager verify
require_fixed "ELIZAOS_RUNTIME_TRUST='baked'" "${tmp}/run-unlisted/runtime.env" \
"runtime with unlisted files must fall back to baked runtime"
rm -f "${runtime}/extra.js"
printf 'tampered\n' >> "${runtime}/Resources/app/eliza-dist/entry.js"
ELIZAOS_UPDATE_KEYRING="${tmp}/elizaos-update.gpg" \
ELIZAOS_STAGED_UPDATE_DIR="${tmp}/staged" \
ELIZAOS_RUN_DIR="${tmp}/run-badhash" \
ELIZAOS_RUNTIME_STORE="${tmp}/store-badhash" \
ELIZAOS_UPDATE_STATE_DIR="${tmp}/state-badhash" \
tails/config/chroot_local-includes/usr/local/lib/elizaos/update-manager verify
require_fixed "ELIZAOS_RUNTIME_TRUST='baked'" "${tmp}/run-badhash/runtime.env" \
"tampered signed runtime must fall back to baked runtime"
else
strict_or_warn "gpg/gpgv unavailable; signed updater smoke was skipped"
fi
if ! rg -q 'gpgv --keyring|ELIZAOS_RELEASE_KEYRING|ELIZAOS_REQUIRE_ISO_SIGNATURE' scripts/usb-write.sh; then
strict_or_warn "USB writer verifies checksum but not a release signature"
fi
echo "==> SBOM and provenance markers"
if ! rg -q 'SBOM|provenance|license bundle' docs/distribution-and-updates.md docs/security-model.md docs/production-readiness.md; then
fail "docs must define SBOM/provenance release gates"
fi
require_file scripts/generate-release-evidence.mjs
if ! find . -maxdepth 4 -type f \( -iname '*sbom*' -o -iname '*provenance*' -o -iname '*attestation*' \) | grep -q .; then
strict_or_warn "production SBOM/provenance artifacts are not generated in this variant"
fi
if [ "${failures}" -ne 0 ]; then
echo "security smoke failed: ${failures} failure(s), ${warnings} warning(s)" >&2
exit 1
fi
echo "security smoke passed: ${warnings} warning(s)"