V9.14.022.2026.06.11: document and test audit safeguards

This commit is contained in:
2026-06-11 05:08:18 +02:00
parent 9d3f283297
commit 85ff080b40
4 changed files with 246 additions and 21 deletions
+29 -17
View File
@@ -60,12 +60,15 @@ and spoofing surfaces.
Internally, the builder employs a dedicated secret-handling pipeline backed by a tmpfs-only secrets directory
(`/dev/shm/cdlb_secrets`). Sensitive material such as root passwords, SSH keys, and signing keys never appears on the command
line, is guarded by strict `0400 root:root` permissions, and any symlink inside the secret path is treated as a hard failure
that aborts the run. Critical code paths temporarily disable Bash xtrace so that credentials never leak into debug logs, and
transient secret files are shredded (`shred -fzu`) as soon as they are no longer needed. GNUPG homes used for signing are
wiped, unencrypted chroot artifacts and includes are removed after `lb build`, and the final artifact is reduced to the
encrypted SquashFS inside the LUKS2 container. At runtime, LUKS passphrases in the live ISO and installer are transported via
named pipes inside the initramfs instead of process arguments, further minimizing exposure in process listings.
line, is guarded by a `0700 root:root` secret root and single-link regular `0400` or `0600` root-owned files, and any symlink
inside the secret path is treated as a hard failure that aborts the run. Filename-only secret arguments reject slashes and
traversal.
Critical code paths temporarily disable Bash xtrace, and a final exact-value debug-log sanitisation pass provides additional
defence in depth. Transient secret files are shredded (`shred -fzu`) as soon as they are no longer needed, but this is only a
best-effort cleanup on SSD, NVMe, copy-on-write, journaled, and virtualised storage. Use tmpfs for secrets and encrypted storage
for build workspaces. Destructive build cleanup is restricted to the exact canonical directory carrying the
`.ciss-live-builder-owned` marker. This private operator workflow still requires strict local path validation; it does not
define public ISO release policy.
Check out more leading world-class services powered by Centurion Intelligence Consulting Agency:
* [CenturionDNS Resolver](https://eddns.eu/)
@@ -493,10 +496,14 @@ To use **``CISS.debian.live.builder``** as intended, the following baseline is e
2. Preparation:
1. Ensure you are root.
2. Create the build directory `mkdir /opt/cdlb` and the tmpfs secrets directory `mkdir /dev/shm/cdlb_secrets`.
3. Place your desired SSH public key in the `authorized_keys` file, for example, in the `/dev/shm/cdlb_secrets` directory.
4. Place your desired Password in the `password.txt` file, for example, in the `/dev/shm/cdlb_secrets` directory.
5. Make any other changes you need to.
2. Create the empty build directory with `install -d -m 0700 -o root -g root /opt/cdlb`.
3. Create the tmpfs secret root with `install -d -m 0700 -o root -g root /dev/shm/cdlb_secrets`.
4. Place required secret files in the secret root as single-link regular, non-symlink, root-owned files with mode `0400`
or `0600`.
5. Place your desired SSH public key in `/dev/shm/cdlb_secrets/authorized_keys`.
6. Place your desired root password in `/dev/shm/cdlb_secrets/password.txt`.
7. Use filename-only values without slashes, `.` or `..` for `--key_age`, `--key_luks`, and signing-file arguments.
8. Make any other changes you need to.
3. Run the config builder script `./ciss_live_builder.sh` and the integrated `lb build` command (example):
@@ -538,6 +545,10 @@ To use **``CISS.debian.live.builder``** as intended, the following baseline is e
both the newer Sigstore bundle asset, and the legacy-split certificate/signature assets before checking the downloaded
SOPS binary with `sha256sum -c --ignore-missing`.
On the first run, the builder creates `.ciss-live-builder-owned` in a new or empty build directory whose canonical parent
already exists. A populated directory without that marker is rejected and is never adopted automatically. Cleanup remains
intentionally destructive inside the exact validated marker-owned directory.
4. Locate your ISO in the `--build-directory`.
5. Boot from the ISO and login to the live image via the console, or the multi-layer secured **coresecret** SSH tunnel.
6. Type `sysp` for the final kernel hardening features.
@@ -559,7 +570,8 @@ preview it or run it.
2. Preparation:
1. Ensure you are root.
2. Create the build directory `mkdir /opt/cdlb` and the tmpfs secrets directory `mkdir /dev/shm/cdlb_secrets`.
2. Create the empty build directory and tmpfs secret root with restrictive ownership and permissions:
`install -d -m 0700 -o root -g root /opt/cdlb /dev/shm/cdlb_secrets`.
3. Place your desired SSH public key in the `authorized_keys` file, for example, in the `/dev/shm/cdlb_secrets` directory.
4. Place your desired Password in the `password.txt` file, for example, in the `/dev/shm/cdlb_secrets` directory.
5. Copy and edit the sample and set your options (no spaces around commas in lists):
@@ -656,10 +668,10 @@ The private directory is ignored by Git. The hooks fail if the CISS EFI image si
#...
- name: Preparing the build environment.
run: |
mkdir -p /opt/config
mkdir -p /opt/livebuild
echo "${{ secrets.CHANGE_ME }}" >| /opt/config/password.txt
echo "${{ secrets.CHANGE_ME }}" >| /opt/config/authorized_keys
install -d -m 0700 -o root -g root /opt/livebuild /dev/shm/cdlb_secrets
umask 0077
printf '%s\n' "${{ secrets.CHANGE_ME }}" >| /dev/shm/cdlb_secrets/password.txt
printf '%s\n' "${{ secrets.CHANGE_ME }}" >| /dev/shm/cdlb_secrets/authorized_keys
#...
- name: Starting CISS.debian.live.builder. This may take a while ...
run: |
@@ -672,9 +684,9 @@ The private directory is ignored by Git. The hooks fail if the CISS EFI image si
--build-directory /opt/livebuild \
--control "${timestamp}" \
--jump-host "${{ secrets.CHANGE_ME }}" \
--root-password-file /opt/config/password.txt \
--root-password-file /dev/shm/cdlb_secrets/password.txt \
--ssh-port CHANGE_ME \
--ssh-pubkey /opt/config
--ssh-pubkey /dev/shm/cdlb_secrets
#...
### SKIP OR CHANGE ALL REMAINING STEPS
```
+13 -2
View File
@@ -37,6 +37,10 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--build-directory </path/to/build_directory>
Where the Debian Live Build Image should be generated. RECOMMENDED path: </opt/cdlb>
The path MUST be canonical and dedicated to the builder; a new directory's canonical parent MUST already exist.
New or empty directories receive the
'.ciss-live-builder-owned' marker; populated unmarked directories are rejected. Cleanup is intentionally destructive
only inside the exact validated marker-owned directory.
MUST be provided.
--change-splash <STRING> one of <club | hexagon>
@@ -57,6 +61,7 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--debug, -d
Enables debug logging for the main program routine. Detailed logging information are written to:
</tmp/ciss_live_builder_1801049.log>
A final exact-value sanitisation pass is defence in depth and does not replace careful tracing discipline.
--dhcp-centurion
If a DHCP lease is provided, the provider's name server will be overridden and the hardened, privacy-focused
@@ -86,11 +91,13 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--key_age=*
The SOPS AGE private keyring for decryption operations. Change '*' to your desired SOPS AGE key file.
'*' MUST be a filename only without slashes, '.' or '..' traversal.
File MUST be placed in:
</dev/shm/cdlb_secrets>
--key_luks=*
The LUKS encryption / decryption passphrase for '/'-fs-encryption. Change '*' to your desired passphrase file.
'*' MUST be a filename only without slashes, '.' or '..' traversal.
File MUST be placed in:
</dev/shm/cdlb_secrets>
@@ -140,7 +147,7 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--root-password-file </dev/shm/cdlb_secrets/password.txt>>
Password file for 'root', if given, MUST be a string of 42 to 64 characters.
If the argument is omitted, no further login authentication is required for the local console.
MUST be placed in:
Safe absolute paths remain supported and are validated separately. RECOMMENDED path:
</dev/shm/cdlb_secrets/password.txt>
--secure-boot-profile <STRING> one of <debian-shim | ciss-uki>
@@ -156,7 +163,8 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
specified via '--signing_key=*'. If the keyring is protected, then provide the passphrase in its own file.
Specify the fingerprint of the key to use via '--signing_key_fpr=*'.
Optionally import an offline GPG CA signing public key via: '--signing_ca=*'.
Change '*' to your desired files / fingerprint. Files MUST be placed in:
Change '*' to your desired filename-only files / fingerprint. Filename-only values MUST NOT contain slashes or traversal.
Files MUST be placed in:
</dev/shm/cdlb_secrets>
--sshfp
@@ -182,6 +190,9 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
💡 Notes:
🔵 You MUST be 'root' to run this script.
🔵 Private operator control does not remove the requirement for strict local secret path validation.
🔵 '/dev/shm/cdlb_secrets' MUST be tmpfs-backed, root-owned, mode 0700, and contain only single-link regular non-symlink files
with mode 0400 or 0600. Secure deletion with shred is best-effort only on modern storage.
💷 Please consider donating to my work at:
🌐 https://coresecret.eu/spenden/
+13 -2
View File
@@ -67,6 +67,10 @@ usage() {
echo
echo -e "\e[97m --build-directory </path/to/build_directory> \e[0m"
echo " Where the Debian Live Build Image should be generated. RECOMMENDED path: </opt/cdlb>"
echo " The path MUST be canonical and dedicated to the builder; a new directory's canonical parent MUST already exist."
echo " New or empty directories receive the"
echo " '.ciss-live-builder-owned' marker; populated unmarked directories are rejected. Cleanup is intentionally"
echo " destructive only inside the exact validated marker-owned directory."
echo " MUST be provided."
echo
echo -e "\e[97m --change-splash <STRING> one of <club | hexagon> \e[0m"
@@ -87,6 +91,7 @@ usage() {
echo -e "\e[97m --debug, -d \e[0m"
echo " Enables debug logging for the main program routine. Detailed logging information are written to:"
echo " </tmp/ciss_live_builder_$$.log>"
echo " A final exact-value sanitisation pass is defence in depth and does not replace careful tracing discipline."
echo
echo -e "\e[97m --dhcp-centurion \e[0m"
echo " If a DHCP lease is provided, the provider's name server will be overridden and the hardened, privacy-focused "
@@ -108,11 +113,13 @@ usage() {
echo
echo -e "\e[97m --key_age=* \e[0m"
echo " The SOPS AGE private keyring for decryption operations. Change '*' to your desired SOPS AGE key file."
echo " '*' MUST be a filename only without slashes, '.' or '..' traversal."
echo " File MUST be placed in:"
echo " </dev/shm/cdlb_secrets>"
echo
echo -e "\e[97m --key_luks=* \e[0m"
echo " The LUKS encryption / decryption passphrase for '/'-fs-encryption. Change '*' to your desired passphrase file."
echo " '*' MUST be a filename only without slashes, '.' or '..' traversal."
echo " File MUST be placed in:"
echo " </dev/shm/cdlb_secrets>"
echo
@@ -162,7 +169,7 @@ usage() {
echo -e "\e[97m --root-password-file </dev/shm/cdlb_secrets/password.txt>> \e[0m"
echo " Password file for 'root', if given, MUST be a string of 42 to 64 characters."
echo " If the argument is omitted, no further login authentication is required for the local console."
echo " MUST be placed in:"
echo " Safe absolute paths remain supported and are validated separately. RECOMMENDED path:"
echo " </dev/shm/cdlb_secrets/password.txt>"
echo
echo -e "\e[97m --secure-boot-profile <STRING> one of <debian-shim | ciss-uki> \e[0m"
@@ -178,7 +185,8 @@ usage() {
echo " specified via '--signing_key=*'. If the keyring is protected, then provide the passphrase in its own file."
echo " Specify the fingerprint of the key to use via '--signing_key_fpr=*'."
echo " Optionally import an offline GPG CA signing public key via: '--signing_ca=*'."
echo " Change '*' to your desired files / fingerprint. Files MUST be placed in:"
echo " Change '*' to your desired filename-only files / fingerprint. Filename-only values MUST NOT contain slashes"
echo " or traversal. Files MUST be placed in:"
echo " </dev/shm/cdlb_secrets>"
echo
echo -e "\e[97m --sops-version <STRING> \e[0m"
@@ -212,6 +220,9 @@ usage() {
echo
echo -e "\e[93m💡 Notes: \e[0m"
echo -e "\e[93m🔵 You MUST be 'root' to run this script. \e[0m"
echo -e "\e[93m🔵 Private operator control does not remove the requirement for strict local secret path validation. \e[0m"
echo -e "\e[93m🔵 '/dev/shm/cdlb_secrets' MUST be tmpfs-backed, root-owned, mode 0700, and contain only \e[0m"
echo -e "\e[93m single-link regular secret files with mode 0400 or 0600. Secure deletion with shred is best-effort only. \e[0m"
echo
echo -e "\e[95m💷 Please consider donating to my work at: \e[0m"
echo -e "\e[95m🌐 https://coresecret.eu/spenden/ \e[0m"
+191
View File
@@ -0,0 +1,191 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
# SPDX-FileCopyrightText: 2024-2026; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu
set -Ceuo pipefail
# shellcheck disable=SC1091,SC2034
declare TEST_ROOT=""
declare TEST_TMP=""
TEST_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TEST_TMP="$(realpath "$(mktemp -d)")"
readonly TEST_ROOT TEST_TMP
declare -r ERR_BUILD_PATH=217
declare -r ERR_GUARD_SRCE=131
declare -r ERR_SANITIZING=133
declare -r ERR_SECRET_PATH=216
declare -r VAR_WORKDIR="${TEST_ROOT}"
declare VAR_TMP_SECRET="${TEST_TMP}/secret-root"
declare LOG_DEBUG=""
declare LOG_ERROR=""
declare LOG_VAR=""
declare VAR_EARLY_DEBUG="false"
declare ERRTRAP="true"
cleanup_test() {
chmod -R u+rwX "${TEST_TMP}" 2>/dev/null || true
rm -rf "${TEST_TMP}"
}
trap cleanup_test EXIT
guard_sourcing() {
return 0
}
# shellcheck source=../lib/lib_secret_validation.sh
. "${TEST_ROOT}/lib/lib_secret_validation.sh"
# shellcheck source=../lib/lib_build_directory.sh
. "${TEST_ROOT}/lib/lib_build_directory.sh"
# shellcheck source=../lib/lib_debug_sanitizer.sh
. "${TEST_ROOT}/lib/lib_debug_sanitizer.sh"
# shellcheck source=../lib/lib_trap_on_exit.sh
. "${TEST_ROOT}/lib/lib_trap_on_exit.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
expect_failure() {
declare description="$1"
shift
if "$@" >/dev/null 2>&1; then
fail "${description}"
fi
}
mkdir -m 0700 "${VAR_TMP_SECRET}"
printf 'safe-private-value\n' > "${VAR_TMP_SECRET}/safe.txt"
chmod 0400 "${VAR_TMP_SECRET}/safe.txt"
validate_secret_directory "${VAR_TMP_SECRET}" "test secret root" "false" || fail "safe secret root rejected"
validate_secret_filename "safe.txt" "test secret filename" || fail "safe secret filename rejected"
validate_secret_file "${VAR_TMP_SECRET}/safe.txt" "test secret file" || fail "safe secret file rejected"
validate_secret_absolute_file "${VAR_TMP_SECRET}/safe.txt" "test absolute secret file" \
|| fail "safe absolute secret file rejected"
expect_failure "relative external secret path accepted" \
validate_secret_absolute_file "secret-root/safe.txt" "test absolute secret file"
mkdir -m 0700 "${TEST_TMP}/external-secret-dir"
printf 'external-private-value\n' > "${TEST_TMP}/external-secret-dir/external.txt"
chmod 0400 "${TEST_TMP}/external-secret-dir/external.txt"
ln -s "${TEST_TMP}/external-secret-dir" "${TEST_TMP}/external-secret-dir-link"
expect_failure "external secret file through a symlinked parent accepted" \
validate_secret_absolute_file "${TEST_TMP}/external-secret-dir-link/external.txt" "test absolute secret file"
expect_failure "external secret directory through a symlinked parent accepted" \
validate_secret_absolute_directory "${TEST_TMP}/external-secret-dir-link" "test absolute secret directory"
declare secret_root_fs=""
secret_root_fs="$(secure_stat -f -c '%T' "${VAR_TMP_SECRET}")"
if [[ "${secret_root_fs}" != "tmpfs" && "${secret_root_fs}" != "ramfs" ]]; then
expect_failure "persistent secret staging area accepted" validate_secret_staging_area
fi
expect_failure "secret filename traversal accepted" validate_secret_filename "../safe.txt" "test secret filename"
expect_failure "absolute filename-only secret accepted" validate_secret_filename "/tmp/safe.txt" "test secret filename"
expect_failure "slash in filename-only secret accepted" validate_secret_filename "subdir/safe.txt" "test secret filename"
ln -s "${VAR_TMP_SECRET}" "${TEST_TMP}/secret-root-link"
expect_failure "secret-root symlink accepted" \
validate_secret_directory "${TEST_TMP}/secret-root-link" "test secret root" "false"
mkdir -m 0700 "${TEST_TMP}/unsafe-secret-root-mode"
chmod 0755 "${TEST_TMP}/unsafe-secret-root-mode"
expect_failure "broad secret-root permissions accepted" \
validate_secret_directory "${TEST_TMP}/unsafe-secret-root-mode" "test secret root" "false"
ln -s "${VAR_TMP_SECRET}/safe.txt" "${VAR_TMP_SECRET}/unsafe-link"
expect_failure "secret-file symlink accepted" validate_secret_file "${VAR_TMP_SECRET}/unsafe-link" "test secret file"
rm "${VAR_TMP_SECRET}/unsafe-link"
ln "${VAR_TMP_SECRET}/safe.txt" "${VAR_TMP_SECRET}/unsafe-hardlink"
expect_failure "hardlinked secret file accepted" validate_secret_file "${VAR_TMP_SECRET}/safe.txt" "test secret file"
rm "${VAR_TMP_SECRET}/unsafe-hardlink"
declare fake_secret='CISS-CANARY-[exact]-value'
declare expected_redaction=""
declare sanitisation_status=0
printf -v expected_redaction '%*s' "${#fake_secret}" ''
expected_redaction="${expected_redaction// /*}"
register_secret_value "${fake_secret}"
LOG_DEBUG="${TEST_TMP}/debug.log"
LOG_VAR="${TEST_TMP}/var.log"
LOG_ERROR="${TEST_TMP}/error.log"
printf 'before %s after\nunrelated line\n' "${fake_secret}" > "${LOG_DEBUG}"
printf 'unrelated vars\n' > "${LOG_VAR}"
printf 'unrelated error\n' > "${LOG_ERROR}"
chmod 0600 "${LOG_DEBUG}" "${LOG_VAR}" "${LOG_ERROR}"
sanitize_debug_logs || fail "debug-log sanitisation failed"
grep -Fq "${fake_secret}" "${LOG_DEBUG}" && fail "debug-log canary remained"
grep -Fq "${expected_redaction}" "${LOG_DEBUG}" || fail "expected exact-value redaction missing"
grep -Fq 'unrelated line' "${LOG_DEBUG}" || fail "unrelated debug content changed"
ln -s "${LOG_DEBUG}" "${TEST_TMP}/unsafe-debug-link"
(
LOG_DEBUG="${TEST_TMP}/unsafe-debug-link"
trap_on_exit 73 "test" 1 "test" "false"
) 2>/dev/null || sanitisation_status=$?
[[ ${sanitisation_status} -eq 73 ]] || fail "sanitisation failure masked the original exit status"
expect_failure "empty build-directory path accepted" validate_build_directory_path ""
expect_failure "root build-directory path accepted" validate_build_directory_path "/"
expect_failure "broad parent build-directory path accepted" validate_build_directory_path "/tmp"
expect_failure "secret root accepted as build directory" validate_build_directory_path "${VAR_TMP_SECRET}"
mkdir -m 0700 "${VAR_TMP_SECRET}/unsafe-build-child"
expect_failure "secret-root descendant accepted as build directory" \
validate_build_directory_path "${VAR_TMP_SECRET}/unsafe-build-child"
mkdir -m 0700 "${TEST_TMP}/unmarked"
expect_failure "build directory without marker accepted" validate_build_directory_marker "${TEST_TMP}/unmarked"
printf 'do-not-adopt\n' > "${TEST_TMP}/unmarked/content"
expect_failure "non-empty unmarked build directory adopted" initialize_build_directory "${TEST_TMP}/unmarked" unsafe_result
mkdir -m 0700 "${TEST_TMP}/unsafe-build-mode"
chmod 0777 "${TEST_TMP}/unsafe-build-mode"
expect_failure "unsafe build-directory permissions accepted" \
initialize_build_directory "${TEST_TMP}/unsafe-build-mode" unsafe_result
mkdir -m 0700 "${TEST_TMP}/marker-link-dir"
printf '%s\n' "${TEST_TMP}/marker-link-dir" > "${TEST_TMP}/marker-target"
chmod 0400 "${TEST_TMP}/marker-target"
ln -s "${TEST_TMP}/marker-target" "${TEST_TMP}/marker-link-dir/.ciss-live-builder-owned"
expect_failure "symlinked builder-owned marker accepted" validate_build_directory_marker "${TEST_TMP}/marker-link-dir"
mkdir -m 0700 "${TEST_TMP}/marker-hardlink-dir"
printf '%s\n' "${TEST_TMP}/marker-hardlink-dir" > "${TEST_TMP}/marker-hardlink-target"
chmod 0400 "${TEST_TMP}/marker-hardlink-target"
ln "${TEST_TMP}/marker-hardlink-target" "${TEST_TMP}/marker-hardlink-dir/.ciss-live-builder-owned"
expect_failure "hardlinked builder-owned marker accepted" validate_build_directory_marker "${TEST_TMP}/marker-hardlink-dir"
mkdir -m 0700 "${TEST_TMP}/marker-extra-content-dir"
printf '%s\nunexpected\n' "${TEST_TMP}/marker-extra-content-dir" \
> "${TEST_TMP}/marker-extra-content-dir/.ciss-live-builder-owned"
chmod 0400 "${TEST_TMP}/marker-extra-content-dir/.ciss-live-builder-owned"
expect_failure "builder-owned marker with extra content accepted" \
validate_build_directory_marker "${TEST_TMP}/marker-extra-content-dir"
ln -s "${TEST_TMP}/unmarked" "${TEST_TMP}/build-link"
expect_failure "build-directory symlink accepted" validate_build_directory_path "${TEST_TMP}/build-link"
declare validated_build_dir=""
initialize_build_directory "${TEST_TMP}/owned-build" validated_build_dir || fail "safe builder-owned directory rejected"
validate_build_directory_marker "${validated_build_dir}" || fail "builder marker rejected"
mkdir "${validated_build_dir}/subdir"
printf 'remove me\n' > "${validated_build_dir}/artifact"
printf 'remove me too\n' > "${validated_build_dir}/.hidden-artifact"
printf 'nested\n' > "${validated_build_dir}/subdir/nested"
clean_build_directory_contents "${validated_build_dir}" || fail "safe builder-owned cleanup failed"
validate_build_directory_marker "${validated_build_dir}" || fail "builder marker removed by cleanup"
[[ -z "$(find "${validated_build_dir}" -mindepth 1 ! -name '.ciss-live-builder-owned' -print -quit)" ]] \
|| fail "builder-owned cleanup left unexpected content"
mkdir -m 0700 "${TEST_TMP}/outside-build"
mkdir "${TEST_TMP}/outside-build/includes.chroot"
ln -s "${TEST_TMP}/outside-build" "${validated_build_dir}/config"
expect_failure "cleanup subpath through a symlinked parent accepted" \
validate_build_directory_subpath "${validated_build_dir}" "config/includes.chroot" unsafe_subpath
printf 'PASS: secret validation, debug sanitisation, and build cleanup guards\n'