V9.14.022.2026.06.11: add path security helpers
This commit is contained in:
@@ -0,0 +1,340 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
guard_sourcing || return "${ERR_GUARD_SRCE}"
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Prints a generic build-directory validation error without disclosing a path.
|
||||||
|
# Arguments:
|
||||||
|
# 1: unsafe input class
|
||||||
|
# 2: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# ERR_BUILD_PATH
|
||||||
|
#######################################
|
||||||
|
build_directory_validation_error() {
|
||||||
|
declare error_class="$1" quiet="${2:-false}"
|
||||||
|
|
||||||
|
if [[ "${quiet}" != "true" ]]; then
|
||||||
|
printf "\e[91m❌ Unsafe build-directory input rejected: %s. \e[0m\n" "${error_class}" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f build_directory_validation_error
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Canonicalises an existing path or a path whose parent exists.
|
||||||
|
# Arguments:
|
||||||
|
# 1: candidate path
|
||||||
|
# 2: output variable name
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_BUILD_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
canonicalize_build_directory() {
|
||||||
|
declare candidate="$1" output_variable="$2" basename="" parent="" resolved_path=""
|
||||||
|
|
||||||
|
[[ -n "${candidate}" && "${candidate}" == /* ]] || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
[[ "${candidate}" == "/" ]] || candidate="${candidate%/}"
|
||||||
|
[[ ! -L "${candidate}" ]] || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
|
||||||
|
if [[ -e "${candidate}" ]]; then
|
||||||
|
resolved_path="$(realpath "${candidate}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
else
|
||||||
|
basename="${candidate##*/}"
|
||||||
|
parent="${candidate%/*}"
|
||||||
|
[[ -n "${parent}" ]] || parent="/"
|
||||||
|
[[ -n "${basename}" && "${basename}" != "." && "${basename}" != ".." && -d "${parent}" ]] || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
resolved_path="$(realpath "${parent}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
resolved_path="${resolved_path%/}/${basename}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "${candidate}" == "${resolved_path}" ]] || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
printf -v "${output_variable}" '%s' "${resolved_path}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f canonicalize_build_directory
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates a build directory against the repository cleanup policy.
|
||||||
|
# Globals:
|
||||||
|
# VAR_TMP_SECRET
|
||||||
|
# VAR_WORKDIR
|
||||||
|
# Arguments:
|
||||||
|
# 1: candidate path
|
||||||
|
# 2: output variable name
|
||||||
|
# 3: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_BUILD_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_build_directory_path() {
|
||||||
|
declare candidate="$1" output_variable="${2:-}" quiet="${3:-false}" secret_root="" validated_path="" workdir=""
|
||||||
|
declare -a rejected_paths=(
|
||||||
|
"/" "/bin" "/boot" "/dev" "/etc" "/home" "/lib" "/lib64" "/media" "/mnt" "/opt" "/proc" "/root" "/run" "/sbin" "/srv"
|
||||||
|
"/sys" "/tmp" "/usr" "/usr/local" "/var" "/var/lib" "/var/tmp"
|
||||||
|
)
|
||||||
|
declare rejected_path=""
|
||||||
|
|
||||||
|
canonicalize_build_directory "${candidate}" validated_path || {
|
||||||
|
build_directory_validation_error \
|
||||||
|
"path is empty, non-absolute, non-canonical, missing its parent, or is a symlink" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
}
|
||||||
|
|
||||||
|
for rejected_path in "${rejected_paths[@]}"; do
|
||||||
|
if [[ "${validated_path}" == "${rejected_path}" ]]; then
|
||||||
|
build_directory_validation_error "broad or system parent directory" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
workdir="$(realpath "${VAR_WORKDIR}" 2>/dev/null)" || {
|
||||||
|
build_directory_validation_error "repository work directory cannot be resolved" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
}
|
||||||
|
secret_root="$(realpath "${VAR_TMP_SECRET}" 2>/dev/null)" || {
|
||||||
|
build_directory_validation_error "secret root cannot be resolved" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${workdir}" == "${validated_path}" || "${workdir}" == "${validated_path}/"* || "${validated_path}" == "${workdir}/"* \
|
||||||
|
|| "${validated_path}" == "${secret_root}" || "${validated_path}" == "${secret_root}/"* ]]; then
|
||||||
|
build_directory_validation_error "path is outside the dedicated build-directory policy" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${output_variable}" ]]; then
|
||||||
|
printf -v "${output_variable}" '%s' "${validated_path}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_build_directory_path
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates the builder-owned marker for an exact build directory.
|
||||||
|
# Globals:
|
||||||
|
# EUID
|
||||||
|
# Arguments:
|
||||||
|
# 1: candidate path
|
||||||
|
# 2: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_BUILD_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_build_directory_marker() {
|
||||||
|
declare candidate="$1" quiet="${2:-false}" directory_mode="" directory_owner="" marker="" marker_build_dir=""
|
||||||
|
declare expected_marker_value="" marker_link_count="" marker_value="" mode="" owner=""
|
||||||
|
|
||||||
|
validate_build_directory_path "${candidate}" marker_build_dir "${quiet}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
[[ -d "${marker_build_dir}" ]] || {
|
||||||
|
build_directory_validation_error "build directory does not exist" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
}
|
||||||
|
|
||||||
|
directory_owner="$(secure_stat -c '%u' "${marker_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
directory_mode="$(secure_stat -c '%a' "${marker_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
if [[ "${directory_owner}" != "${EUID}" ]] || (( (8#${directory_mode} & 022) != 0 )); then
|
||||||
|
build_directory_validation_error "build directory ownership or permissions are unsafe" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
marker="${marker_build_dir}/.ciss-live-builder-owned"
|
||||||
|
if [[ -L "${marker}" || ! -f "${marker}" ]]; then
|
||||||
|
build_directory_validation_error "builder-owned marker is missing or unsafe" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
owner="$(secure_stat -c '%u' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
mode="$(secure_stat -c '%a' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
marker_link_count="$(secure_stat -c '%h' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
marker_value="$(cat "${marker}" || exit $?; printf '.')" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
marker_value="${marker_value%.}"
|
||||||
|
expected_marker_value="${marker_build_dir}"$'\n'
|
||||||
|
|
||||||
|
if [[ "${owner}" != "${EUID}" || "${mode}" != "400" || "${marker_link_count}" != "1" \
|
||||||
|
|| "${marker_value}" != "${expected_marker_value}" ]]; then
|
||||||
|
build_directory_validation_error "builder-owned marker does not match the exact directory" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_build_directory_marker
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates an existing exact subpath below a marker-owned build directory.
|
||||||
|
# Arguments:
|
||||||
|
# 1: build directory
|
||||||
|
# 2: relative subpath
|
||||||
|
# 3: output variable name
|
||||||
|
# 4: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_BUILD_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_build_directory_subpath() {
|
||||||
|
declare build_directory="$1" relative_path="$2" output_variable="$3" quiet="${4:-false}"
|
||||||
|
declare candidate_subpath="" resolved_subpath=""
|
||||||
|
|
||||||
|
validate_build_directory_marker "${build_directory}" "${quiet}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
|
||||||
|
if [[ -z "${relative_path}" || "${relative_path}" == /* || "${relative_path}" == "." || "${relative_path}" == ".." \
|
||||||
|
|| "${relative_path}" == ../* || "${relative_path}" == */../* || "${relative_path}" == */.. \
|
||||||
|
|| "${relative_path}" == ./* || "${relative_path}" == */./* || "${relative_path}" == */. ]]; then
|
||||||
|
build_directory_validation_error "unsafe relative cleanup subpath" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
candidate_subpath="${build_directory}/${relative_path}"
|
||||||
|
if [[ -L "${candidate_subpath}" || ! -e "${candidate_subpath}" ]]; then
|
||||||
|
build_directory_validation_error "cleanup subpath is missing or is a symlink" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolved_subpath="$(realpath "${candidate_subpath}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
if [[ "${resolved_subpath}" != "${candidate_subpath}" || "${resolved_subpath}" != "${build_directory}/"* ]]; then
|
||||||
|
build_directory_validation_error "cleanup subpath escapes the exact build directory" "${quiet}"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${output_variable}" '%s' "${resolved_subpath}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_build_directory_subpath
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Initialises a new or empty build directory and its ownership marker.
|
||||||
|
# Arguments:
|
||||||
|
# 1: candidate path
|
||||||
|
# 2: output variable name
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_BUILD_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
initialize_build_directory() {
|
||||||
|
declare candidate="$1" output_variable="$2" directory_mode="" directory_owner="" existing_entry=""
|
||||||
|
declare initialized_build_dir="" marker=""
|
||||||
|
|
||||||
|
validate_build_directory_path "${candidate}" initialized_build_dir || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
|
||||||
|
if [[ ! -e "${initialized_build_dir}" ]]; then
|
||||||
|
mkdir -m 0700 "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_build_directory_path "${initialized_build_dir}" initialized_build_dir || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
[[ -d "${initialized_build_dir}" && ! -L "${initialized_build_dir}" ]] || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
|
||||||
|
directory_owner="$(secure_stat -c '%u' "${initialized_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
directory_mode="$(secure_stat -c '%a' "${initialized_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
if [[ "${directory_owner}" != "${EUID}" ]] || (( (8#${directory_mode} & 022) != 0 )); then
|
||||||
|
build_directory_validation_error "build directory ownership or permissions are unsafe"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
marker="${initialized_build_dir}/.ciss-live-builder-owned"
|
||||||
|
|
||||||
|
if [[ -e "${marker}" || -L "${marker}" ]]; then
|
||||||
|
validate_build_directory_marker "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
else
|
||||||
|
existing_entry="$(find "${initialized_build_dir}" -mindepth 1 -maxdepth 1 -print -quit)" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
if [[ -n "${existing_entry}" ]]; then
|
||||||
|
build_directory_validation_error "non-empty directory has no builder-owned marker"
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
(umask 077; printf '%s\n' "${initialized_build_dir}" >| "${marker}") || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
chmod 0400 "${marker}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
validate_build_directory_marker "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${output_variable}" '%s' "${initialized_build_dir}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f initialize_build_directory
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Removes paths with a one-filesystem boundary where supported.
|
||||||
|
# Arguments:
|
||||||
|
# paths to remove
|
||||||
|
# Returns:
|
||||||
|
# rm exit status
|
||||||
|
#######################################
|
||||||
|
remove_build_paths() {
|
||||||
|
# shellcheck disable=SC2312
|
||||||
|
if rm --help 2>&1 | grep -q -- '--one-file-system'; then
|
||||||
|
rm -rf --one-file-system -- "$@"
|
||||||
|
else
|
||||||
|
rm -rf -- "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f remove_build_paths
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Deletes all content except the ownership marker from an exact build directory.
|
||||||
|
# Arguments:
|
||||||
|
# 1: candidate path
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_BUILD_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
clean_build_directory_contents() {
|
||||||
|
declare candidate="$1" build_entry=""
|
||||||
|
declare -a build_entries=()
|
||||||
|
declare -i old_dotglob=0 old_failglob=0 old_nullglob=0
|
||||||
|
|
||||||
|
validate_build_directory_marker "${candidate}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
|
||||||
|
shopt -q dotglob && old_dotglob=1
|
||||||
|
shopt -q failglob && old_failglob=1
|
||||||
|
shopt -q nullglob && old_nullglob=1
|
||||||
|
shopt -s dotglob nullglob
|
||||||
|
shopt -u failglob
|
||||||
|
|
||||||
|
build_entries=("${candidate}"/*)
|
||||||
|
for build_entry in "${build_entries[@]}"; do
|
||||||
|
[[ "${build_entry}" == "${candidate}/.ciss-live-builder-owned" ]] && continue
|
||||||
|
remove_build_paths "${build_entry}" || {
|
||||||
|
if (( old_dotglob )); then shopt -s dotglob; else shopt -u dotglob; fi
|
||||||
|
if (( old_failglob )); then shopt -s failglob; else shopt -u failglob; fi
|
||||||
|
if (( old_nullglob )); then shopt -s nullglob; else shopt -u nullglob; fi
|
||||||
|
return "${ERR_BUILD_PATH:-217}"
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( old_dotglob )); then shopt -s dotglob; else shopt -u dotglob; fi
|
||||||
|
if (( old_failglob )); then shopt -s failglob; else shopt -u failglob; fi
|
||||||
|
if (( old_nullglob )); then shopt -s nullglob; else shopt -u nullglob; fi
|
||||||
|
|
||||||
|
validate_build_directory_marker "${candidate}" || return "${ERR_BUILD_PATH:-217}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f clean_build_directory_contents
|
||||||
|
|
||||||
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
guard_sourcing || return "${ERR_GUARD_SRCE}"
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Replaces exact registered secret values in one controlled log file.
|
||||||
|
# Globals:
|
||||||
|
# _ARY_SECRET_REDACTION_VALUES
|
||||||
|
# Arguments:
|
||||||
|
# 1: log file
|
||||||
|
# Returns:
|
||||||
|
# 0: on success or missing log
|
||||||
|
# ERR_SANITIZING: on failure
|
||||||
|
#######################################
|
||||||
|
sanitize_debug_log() {
|
||||||
|
declare log_file="$1" log_text="" replacement="" secret_value="" tmp_file=""
|
||||||
|
|
||||||
|
[[ -n "${log_file}" && -f "${log_file}" ]] || return 0
|
||||||
|
[[ ! -L "${log_file}" ]] || return "${ERR_SANITIZING:-133}"
|
||||||
|
|
||||||
|
log_text="$(cat "${log_file}" || exit $?; printf '.')" || return "${ERR_SANITIZING:-133}"
|
||||||
|
log_text="${log_text%.}"
|
||||||
|
|
||||||
|
for secret_value in "${_ARY_SECRET_REDACTION_VALUES[@]}"; do
|
||||||
|
[[ -n "${secret_value}" ]] || continue
|
||||||
|
printf -v replacement '%*s' "${#secret_value}" ''
|
||||||
|
replacement="${replacement// /*}"
|
||||||
|
log_text="${log_text//"${secret_value}"/"${replacement}"}"
|
||||||
|
done
|
||||||
|
|
||||||
|
tmp_file="$(mktemp "${log_file}.sanitize.XXXXXX")" || return "${ERR_SANITIZING:-133}"
|
||||||
|
chmod 0600 "${tmp_file}" || {
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
return "${ERR_SANITIZING:-133}"
|
||||||
|
}
|
||||||
|
printf '%s' "${log_text}" >| "${tmp_file}" || {
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
return "${ERR_SANITIZING:-133}"
|
||||||
|
}
|
||||||
|
mv -f "${tmp_file}" "${log_file}" || {
|
||||||
|
rm -f "${tmp_file}"
|
||||||
|
return "${ERR_SANITIZING:-133}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f sanitize_debug_log
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Runs the final exact-value sanitisation pass for controlled logs.
|
||||||
|
# Globals:
|
||||||
|
# LOG_DEBUG
|
||||||
|
# LOG_ERROR
|
||||||
|
# LOG_VAR
|
||||||
|
# Arguments:
|
||||||
|
# None
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SANITIZING: on failure
|
||||||
|
#######################################
|
||||||
|
sanitize_debug_logs() {
|
||||||
|
declare log_file=""
|
||||||
|
declare -a log_files=("${LOG_DEBUG:-}" "${LOG_VAR:-}" "${LOG_ERROR:-}")
|
||||||
|
|
||||||
|
set +x
|
||||||
|
if [[ -e "/proc/$$/fd/42" || -e "/dev/fd/42" ]]; then
|
||||||
|
exec 42>&-
|
||||||
|
fi
|
||||||
|
|
||||||
|
for log_file in "${log_files[@]}"; do
|
||||||
|
sanitize_debug_log "${log_file}" || return "${ERR_SANITIZING:-133}"
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f sanitize_debug_logs
|
||||||
|
|
||||||
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
guard_sourcing || return "${ERR_GUARD_SRCE}"
|
||||||
|
|
||||||
|
if ! declare -p _ARY_SECRET_REDACTION_VALUES >/dev/null 2>&1; then
|
||||||
|
declare -ga _ARY_SECRET_REDACTION_VALUES=()
|
||||||
|
fi
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Runs GNU stat on Debian and gstat on macOS development hosts.
|
||||||
|
# Arguments:
|
||||||
|
# stat arguments
|
||||||
|
# Returns:
|
||||||
|
# stat exit status
|
||||||
|
#######################################
|
||||||
|
secure_stat() {
|
||||||
|
if command -v gstat >/dev/null 2>&1; then
|
||||||
|
gstat "$@"
|
||||||
|
else
|
||||||
|
stat "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f secure_stat
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Prints a generic secret validation error without disclosing a path or value.
|
||||||
|
# Arguments:
|
||||||
|
# 1: unsafe input class
|
||||||
|
# 2: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# ERR_SECRET_PATH
|
||||||
|
#######################################
|
||||||
|
secret_validation_error() {
|
||||||
|
declare error_class="$1" quiet="${2:-false}"
|
||||||
|
|
||||||
|
if [[ "${quiet}" != "true" ]]; then
|
||||||
|
printf "\e[91m❌ Unsafe secret input rejected: %s. \e[0m\n" "${error_class}" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f secret_validation_error
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Registers an exact known secret value for final log redaction.
|
||||||
|
# Globals:
|
||||||
|
# _ARY_SECRET_REDACTION_VALUES
|
||||||
|
# Arguments:
|
||||||
|
# 1: secret value
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
#######################################
|
||||||
|
register_secret_value() {
|
||||||
|
declare secret_value="$1" registered_value="" was_traced="false"
|
||||||
|
|
||||||
|
[[ $- == *x* ]] && was_traced="true"
|
||||||
|
set +x
|
||||||
|
|
||||||
|
if [[ -n "${secret_value}" ]]; then
|
||||||
|
for registered_value in "${_ARY_SECRET_REDACTION_VALUES[@]}"; do
|
||||||
|
if [[ "${registered_value}" == "${secret_value}" ]]; then
|
||||||
|
[[ "${was_traced}" == "true" ]] && set -x
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
_ARY_SECRET_REDACTION_VALUES+=("${secret_value}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "${was_traced}" == "true" ]] && set -x
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f register_secret_value
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Registers exact text values from a controlled secret file.
|
||||||
|
# Globals:
|
||||||
|
# _ARY_SECRET_REDACTION_VALUES
|
||||||
|
# Arguments:
|
||||||
|
# 1: secret file
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
#######################################
|
||||||
|
register_secret_file_for_redaction() {
|
||||||
|
declare secret_file="$1" secret_line="" secret_text="" was_traced="false"
|
||||||
|
|
||||||
|
[[ $- == *x* ]] && was_traced="true"
|
||||||
|
set +x
|
||||||
|
|
||||||
|
secret_text="$(cat "${secret_file}" || exit $?; printf '.')" || {
|
||||||
|
[[ "${was_traced}" == "true" ]] && set -x
|
||||||
|
return "${ERR_SANITIZING:-133}"
|
||||||
|
}
|
||||||
|
secret_text="${secret_text%.}"
|
||||||
|
register_secret_value "${secret_text}"
|
||||||
|
|
||||||
|
while IFS= read -r secret_line || [[ -n "${secret_line}" ]]; do
|
||||||
|
register_secret_value "${secret_line}"
|
||||||
|
done < "${secret_file}"
|
||||||
|
|
||||||
|
[[ "${was_traced}" == "true" ]] && set -x
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f register_secret_file_for_redaction
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates a filename-only secret argument.
|
||||||
|
# Arguments:
|
||||||
|
# 1: filename
|
||||||
|
# 2: input class
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SECRET_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_secret_filename() {
|
||||||
|
declare filename="$1" input_class="${2:-filename-only secret argument}"
|
||||||
|
declare filename_regex='^[A-Za-z0-9._@%+=:,~-]+$'
|
||||||
|
|
||||||
|
if [[ -z "${filename}" || "${filename}" == "." || "${filename}" == ".." || "${filename}" == */* \
|
||||||
|
|| ! "${filename}" =~ ${filename_regex} ]]; then
|
||||||
|
secret_validation_error "${input_class}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_secret_filename
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates a restrictively permissioned secret directory.
|
||||||
|
# Arguments:
|
||||||
|
# 1: directory path
|
||||||
|
# 2: input class
|
||||||
|
# 3: require tmpfs
|
||||||
|
# 4: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SECRET_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_secret_directory() {
|
||||||
|
declare directory="$1" input_class="${2:-secret directory}" require_tmpfs="${3:-false}" quiet="${4:-false}"
|
||||||
|
declare fs_type="" mode="" owner=""
|
||||||
|
|
||||||
|
if [[ -z "${directory}" || -L "${directory}" || ! -d "${directory}" ]]; then
|
||||||
|
secret_validation_error "${input_class}" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
owner="$(secure_stat -c '%u' "${directory}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} ownership" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
mode="$(secure_stat -c '%a' "${directory}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} permissions" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${owner}" != "${EUID}" || "${mode}" != "700" ]]; then
|
||||||
|
secret_validation_error "${input_class} ownership or permissions" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${require_tmpfs}" == "true" ]]; then
|
||||||
|
fs_type="$(secure_stat -f -c '%T' "${directory}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} filesystem" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
if [[ "${fs_type}" != "tmpfs" && "${fs_type}" != "ramfs" ]]; then
|
||||||
|
secret_validation_error "${input_class} is not tmpfs-backed" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_secret_directory
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates and registers a secret file.
|
||||||
|
# Arguments:
|
||||||
|
# 1: file path
|
||||||
|
# 2: input class
|
||||||
|
# 3: register for redaction
|
||||||
|
# 4: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SECRET_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_secret_file() {
|
||||||
|
declare secret_file="$1" input_class="${2:-secret file}" register_value="${3:-true}" quiet="${4:-false}"
|
||||||
|
declare link_count="" mode="" owner=""
|
||||||
|
|
||||||
|
if [[ -z "${secret_file}" || -L "${secret_file}" || ! -f "${secret_file}" ]]; then
|
||||||
|
secret_validation_error "${input_class}" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
owner="$(secure_stat -c '%u' "${secret_file}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} ownership" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
mode="$(secure_stat -c '%a' "${secret_file}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} permissions" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
link_count="$(secure_stat -c '%h' "${secret_file}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} link count" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${owner}" != "${EUID}" || "${link_count}" != "1" || ( "${mode}" != "400" && "${mode}" != "600" ) ]]; then
|
||||||
|
secret_validation_error "${input_class} ownership, permissions, or link count" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${register_value}" == "true" ]]; then
|
||||||
|
register_secret_file_for_redaction "${secret_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_secret_file
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates an explicitly supported absolute secret file path.
|
||||||
|
# Arguments:
|
||||||
|
# 1: file path
|
||||||
|
# 2: input class
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SECRET_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_secret_absolute_file() {
|
||||||
|
declare secret_file="$1" input_class="${2:-absolute secret file}" resolved_file=""
|
||||||
|
|
||||||
|
if [[ -z "${secret_file}" || "${secret_file}" != /* ]]; then
|
||||||
|
secret_validation_error "${input_class} must be an absolute path"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolved_file="$(realpath "${secret_file}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} cannot be resolved"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
if [[ "${resolved_file}" != "${secret_file}" ]]; then
|
||||||
|
secret_validation_error "${input_class} must be canonical and must not traverse symlinked parents"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_secret_file "${secret_file}" "${input_class}" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_secret_absolute_file
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates an explicitly supported absolute secret directory path.
|
||||||
|
# Arguments:
|
||||||
|
# 1: directory path
|
||||||
|
# 2: input class
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SECRET_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_secret_absolute_directory() {
|
||||||
|
declare directory="$1" input_class="${2:-absolute secret directory}" resolved_directory=""
|
||||||
|
|
||||||
|
if [[ -z "${directory}" || "${directory}" != /* ]]; then
|
||||||
|
secret_validation_error "${input_class} must be an absolute path"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolved_directory="$(realpath "${directory}" 2>/dev/null)" || {
|
||||||
|
secret_validation_error "${input_class} cannot be resolved"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
}
|
||||||
|
if [[ "${resolved_directory}" != "${directory}" ]]; then
|
||||||
|
secret_validation_error "${input_class} must be canonical and must not traverse symlinked parents"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_secret_directory "${directory}" "${input_class}" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_secret_absolute_directory
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates a filename-only secret file below the fixed secret root.
|
||||||
|
# Globals:
|
||||||
|
# VAR_TMP_SECRET
|
||||||
|
# Arguments:
|
||||||
|
# 1: filename
|
||||||
|
# 2: input class
|
||||||
|
# 3: register for redaction
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SECRET_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_secret_file_in_root() {
|
||||||
|
declare filename="$1" input_class="${2:-secret file}" register_value="${3:-true}"
|
||||||
|
|
||||||
|
validate_secret_directory "${VAR_TMP_SECRET}" "secret root" "true" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
validate_secret_filename "${filename}" "${input_class} filename" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
validate_secret_file "${VAR_TMP_SECRET}/${filename}" "${input_class}" "${register_value}" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_secret_file_in_root
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Validates the fixed tmpfs secret staging area and all entries below it.
|
||||||
|
# Globals:
|
||||||
|
# VAR_TMP_SECRET
|
||||||
|
# Arguments:
|
||||||
|
# 1: quiet flag
|
||||||
|
# Returns:
|
||||||
|
# 0: on success
|
||||||
|
# ERR_SECRET_PATH: on failure
|
||||||
|
#######################################
|
||||||
|
validate_secret_staging_area() {
|
||||||
|
declare quiet="${1:-false}" secret_entries_file="" secret_entry=""
|
||||||
|
declare -a secret_entries=()
|
||||||
|
|
||||||
|
validate_secret_directory "${VAR_TMP_SECRET}" "secret root" "true" "${quiet}" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
|
||||||
|
secret_entries_file="$(mktemp)" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
if ! find "${VAR_TMP_SECRET}" -xdev -mindepth 1 -print0 >| "${secret_entries_file}"; then
|
||||||
|
rm -f "${secret_entries_file}"
|
||||||
|
secret_validation_error "secret-root enumeration failed" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
mapfile -d '' -t secret_entries < "${secret_entries_file}"
|
||||||
|
rm -f "${secret_entries_file}"
|
||||||
|
|
||||||
|
for secret_entry in "${secret_entries[@]}"; do
|
||||||
|
if [[ -L "${secret_entry}" ]]; then
|
||||||
|
secret_validation_error "symlink below secret root" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
elif [[ -d "${secret_entry}" ]]; then
|
||||||
|
validate_secret_directory "${secret_entry}" "directory below secret root" "false" "${quiet}" \
|
||||||
|
|| return "${ERR_SECRET_PATH:-216}"
|
||||||
|
elif [[ -f "${secret_entry}" ]]; then
|
||||||
|
validate_secret_file "${secret_entry}" "file below secret root" "true" "${quiet}" || return "${ERR_SECRET_PATH:-216}"
|
||||||
|
else
|
||||||
|
secret_validation_error "non-regular entry below secret root" "${quiet}"
|
||||||
|
return "${ERR_SECRET_PATH:-216}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
### Prevents accidental 'unset -f'.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly -f validate_secret_staging_area
|
||||||
|
|
||||||
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
|
||||||
@@ -28,6 +28,7 @@ touch "${LOG_ERROR}" && chmod 0600 "${LOG_ERROR}"
|
|||||||
|
|
||||||
declare -g __umask=""
|
declare -g __umask=""
|
||||||
declare -g VAR_ARCHITECTURE=""
|
declare -g VAR_ARCHITECTURE=""
|
||||||
|
declare -g VAR_ARG_SANITIZED=""
|
||||||
declare -g VAR_DROPBEAR_VERSION="2026.91"
|
declare -g VAR_DROPBEAR_VERSION="2026.91"
|
||||||
declare -g VAR_HANDLER_BUILD_DIR=""
|
declare -g VAR_HANDLER_BUILD_DIR=""
|
||||||
declare -g VAR_HANDLER_CDI="false"
|
declare -g VAR_HANDLER_CDI="false"
|
||||||
@@ -91,6 +92,8 @@ declare -gir ERR__SSH__PORT=212 # --ssh-port MUST be an integer between '1' and
|
|||||||
declare -gir ERR_ARG_MSMTCH=213 # Wrong Number of optional Arguments provided
|
declare -gir ERR_ARG_MSMTCH=213 # Wrong Number of optional Arguments provided
|
||||||
declare -gir ERR_DROPBEAR_V=214 # --dropbear-version MUST match the bundled Dropbear tarball version format
|
declare -gir ERR_DROPBEAR_V=214 # --dropbear-version MUST match the bundled Dropbear tarball version format
|
||||||
declare -gir ERR__SOPS__VER=215 # --sops-version MUST match the upstream SOPS semantic version format
|
declare -gir ERR__SOPS__VER=215 # --sops-version MUST match the upstream SOPS semantic version format
|
||||||
|
declare -gir ERR_SECRET_PATH=216 # Unsafe secret root, filename, or file.
|
||||||
|
declare -gir ERR_BUILD_PATH=217 # Unsafe build-directory path or marker.
|
||||||
declare -gir ERR_SECRETSSYM=251 # VAR_TMP_SECRET is a symlink.
|
declare -gir ERR_SECRETSSYM=251 # VAR_TMP_SECRET is a symlink.
|
||||||
declare -gir ERR_NOTABSPATH=252 # Not an absolute path
|
declare -gir ERR_NOTABSPATH=252 # Not an absolute path
|
||||||
declare -gir ERR_INVLD_CHAR=253 # Invalid Character
|
declare -gir ERR_INVLD_CHAR=253 # Invalid Character
|
||||||
|
|||||||
Reference in New Issue
Block a user