99# When executed directly, strict mode is enabled in the entrypoint below.
1010# ============================================================
1111
12- SECURITY_SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
12+ _acfs_security_source=" ${BASH_SOURCE[0]} "
13+ _acfs_security_source_dir=" ."
14+ case " $_acfs_security_source " in
15+ * /* ) _acfs_security_source_dir=" ${_acfs_security_source%/* } " ;;
16+ esac
17+ SECURITY_SCRIPT_DIR=" $( cd " $_acfs_security_source_dir " && pwd -P) "
18+ unset _acfs_security_source _acfs_security_source_dir
1319
1420# Ensure we have logging functions available
1521if [[ -z " ${ACFS_BLUE:- } " ]]; then
@@ -82,6 +88,19 @@ acfs_security_curl_binary_path() {
8288 acfs_security_system_binary_path curl
8389}
8490
91+ acfs_security_required_binary_path () {
92+ local name=" ${1:- } "
93+ local path=" "
94+
95+ path=" $( acfs_security_system_binary_path " $name " 2> /dev/null || true) "
96+ if [[ -z " $path " ]]; then
97+ log_error " No trusted $name binary available"
98+ return 127
99+ fi
100+
101+ printf ' %s\n' " $path "
102+ }
103+
85104acfs_security_hash_tool () {
86105 local sha256sum_bin=" "
87106 local shasum_bin=" "
@@ -101,6 +120,53 @@ acfs_security_hash_tool() {
101120 return 1
102121}
103122
123+ acfs_security_mktemp () {
124+ local mktemp_bin=" "
125+
126+ mktemp_bin=" $( acfs_security_required_binary_path mktemp) " || return $?
127+ if [[ " $# " -gt 0 ]]; then
128+ " $mktemp_bin " " $@ "
129+ else
130+ " $mktemp_bin "
131+ fi
132+ }
133+
134+ acfs_security_cat_file () {
135+ local cat_bin=" "
136+ local file=" ${1:- } "
137+
138+ [[ -r " $file " ]] || {
139+ log_error " Cannot read file: $file "
140+ return 1
141+ }
142+
143+ cat_bin=" $( acfs_security_required_binary_path cat) " || return $?
144+ " $cat_bin " " $file "
145+ }
146+
147+ acfs_security_mkdir_p () {
148+ local mkdir_bin=" "
149+ local dir=" ${1:- } "
150+
151+ [[ -n " $dir " ]] || return 1
152+ mkdir_bin=" $( acfs_security_required_binary_path mkdir) " || return $?
153+ " $mkdir_bin " -p " $dir "
154+ }
155+
156+ acfs_security_sort_lines () {
157+ local sort_bin=" "
158+
159+ sort_bin=" $( acfs_security_required_binary_path sort) " || return $?
160+ " $sort_bin "
161+ }
162+
163+ acfs_security_date () {
164+ local date_bin=" "
165+
166+ date_bin=" $( acfs_security_required_binary_path date) " || return $?
167+ " $date_bin " " $@ "
168+ }
169+
104170# Check if running in interactive mode
105171# Returns 0 if interactive, 1 if non-interactive
106172_acfs_is_interactive () {
@@ -167,9 +233,14 @@ acfs_download_to_file() {
167233 local url=" $1 "
168234 local output_path=" $2 "
169235 local name=" ${3:- $url } "
236+ local output_dir=" ${output_path%/* } "
237+
238+ if [[ " $output_dir " == " $output_path " ]]; then
239+ output_dir=" ."
240+ fi
170241
171242 # Ensure parent dir exists
172- mkdir -p " $( dirname " $output_path " ) "
243+ acfs_security_mkdir_p " $output_dir " || return $?
173244
174245 # Use GitHub-specific backoff for GitHub URLs (rate limit handling)
175246 if [[ " $url " == * " github.com" * || " $url " == * " githubusercontent.com" * ]]; then
@@ -231,12 +302,16 @@ DEFAULT_CHECKSUMS_FILE="$SECURITY_SCRIPT_DIR/../../checksums.yaml"
231302
232303# Resolve to absolute path to prevent working directory manipulation
233304if [[ -r " $DEFAULT_CHECKSUMS_FILE " ]]; then
234- # Use realpath if available, otherwise use cd/pwd to get absolute path
235- if command -v realpath & > /dev/null; then
236- DEFAULT_CHECKSUMS_FILE=" $( realpath " $DEFAULT_CHECKSUMS_FILE " ) "
305+ # Use trusted realpath if available, otherwise use cd/pwd to get absolute path.
306+ _acfs_security_realpath_bin=" $( acfs_security_system_binary_path realpath 2> /dev/null || true) "
307+ if [[ -n " $_acfs_security_realpath_bin " ]]; then
308+ DEFAULT_CHECKSUMS_FILE=" $( " $_acfs_security_realpath_bin " " $DEFAULT_CHECKSUMS_FILE " ) "
237309 else
238- DEFAULT_CHECKSUMS_FILE=" $( cd " $( dirname " $DEFAULT_CHECKSUMS_FILE " ) " && pwd) /$( basename " $DEFAULT_CHECKSUMS_FILE " ) "
310+ _acfs_security_checksums_dir=" ${DEFAULT_CHECKSUMS_FILE%/* } "
311+ _acfs_security_checksums_base=" ${DEFAULT_CHECKSUMS_FILE##*/ } "
312+ DEFAULT_CHECKSUMS_FILE=" $( cd " $_acfs_security_checksums_dir " && pwd -P) /$_acfs_security_checksums_base "
239313 fi
314+ unset _acfs_security_realpath_bin _acfs_security_checksums_dir _acfs_security_checksums_base
240315 CHECKSUMS_FILE=" ${CHECKSUMS_FILE:- $DEFAULT_CHECKSUMS_FILE } "
241316else
242317 # If default not found and CHECKSUMS_FILE not set, use absolute path to repo root
@@ -413,8 +488,13 @@ calculate_sha256() {
413488
414489_acfs_remove_temp_files () {
415490 local path
491+ local rm_bin=" "
492+
493+ rm_bin=" $( acfs_security_system_binary_path rm 2> /dev/null || true) "
494+ [[ -n " $rm_bin " ]] || return 0
495+
416496 for path in " $@ " ; do
417- [[ -n " $path " ]] && rm -f -- " $path " 2> /dev/null || true
497+ [[ -n " $path " ]] && " $rm_bin " -f -- " $path " 2> /dev/null || true
418498 done
419499}
420500
@@ -428,7 +508,7 @@ fetch_checksum() {
428508
429509 # Create safe temp file
430510 local tmp_file
431- tmp_file=" $( mktemp " ${TMPDIR:-/ tmp} /acfs-fetch.XXXXXX" ) " || {
511+ tmp_file=" $( acfs_security_mktemp " ${TMPDIR:-/ tmp} /acfs-fetch.XXXXXX" ) " || {
432512 log_error " Failed to create temp file"
433513 return 1
434514 }
@@ -472,7 +552,7 @@ verify_checksum() {
472552
473553 # Create safe temp file
474554 local tmp_file
475- tmp_file=" $( mktemp " ${TMPDIR:-/ tmp} /acfs-verify.XXXXXX" ) " || {
555+ tmp_file=" $( acfs_security_mktemp " ${TMPDIR:-/ tmp} /acfs-verify.XXXXXX" ) " || {
476556 log_error " Failed to create temp file for $name "
477557 return 1
478558 }
@@ -507,7 +587,7 @@ verify_checksum() {
507587 fi
508588
509589 if [[ -z " $verified_file " ]]; then
510- fresh_tmp_file=" $( mktemp " ${TMPDIR:-/ tmp} /acfs-verify.XXXXXX" 2> /dev/null) " || fresh_tmp_file=" "
590+ fresh_tmp_file=" $( acfs_security_mktemp " ${TMPDIR:-/ tmp} /acfs-verify.XXXXXX" 2> /dev/null) " || fresh_tmp_file=" "
511591 if [[ -n " $fresh_tmp_file " ]] && acfs_download_to_file " $refreshed_url " " $fresh_tmp_file " " $name " ; then
512592 refreshed_actual_sha256=" $( calculate_file_sha256 " $fresh_tmp_file " ) " || refreshed_actual_sha256=" "
513593 if [[ -n " $refreshed_actual_sha256 " && " $refreshed_actual_sha256 " == " $refreshed_expected_sha256 " ]]; then
@@ -546,7 +626,7 @@ verify_checksum() {
546626
547627 if [[ " $status " -eq 0 ]]; then
548628 # Return the verified content (verbatim bytes) on stdout.
549- cat " $verified_file "
629+ acfs_security_cat_file " $verified_file "
550630 status=$?
551631 fi
552632
@@ -559,6 +639,7 @@ fetch_and_run() {
559639 local url=" $1 "
560640 local expected_sha256=" ${2:- } "
561641 local name=" ${3:- installer} "
642+ local bash_bin=" "
562643 shift 3 || true
563644 local args=(" $@ " )
564645
@@ -577,9 +658,11 @@ fetch_and_run() {
577658 return 1
578659 fi
579660
661+ bash_bin=" $( acfs_security_required_binary_path bash) " || return $?
662+
580663 (
581664 set -o pipefail
582- verify_checksum " $url " " $expected_sha256 " " $name " | bash -s -- " ${args[@]} "
665+ verify_checksum " $url " " $expected_sha256 " " $name " | " $bash_bin " -s -- " ${args[@]} "
583666 )
584667}
585668
@@ -614,6 +697,7 @@ fetch_and_run_with_recovery() {
614697 local url=" $1 "
615698 local expected_sha256=" ${2:- } "
616699 local name=" ${3:- installer} "
700+ local bash_bin=" "
617701 shift 3 || true
618702 local args=(" $@ " )
619703
@@ -632,9 +716,11 @@ fetch_and_run_with_recovery() {
632716 return 1
633717 fi
634718
719+ bash_bin=" $( acfs_security_required_binary_path bash) " || return $?
720+
635721 # Create safe temp file
636722 local tmp_file
637- tmp_file=" $( mktemp " ${TMPDIR:-/ tmp} /acfs-recovery.XXXXXX" ) " || {
723+ tmp_file=" $( acfs_security_mktemp " ${TMPDIR:-/ tmp} /acfs-recovery.XXXXXX" ) " || {
638724 log_error " Failed to create temp file for $name "
639725 return 1
640726 }
@@ -678,7 +764,7 @@ fetch_and_run_with_recovery() {
678764 elif [[ " $status " -eq 0 ]]; then
679765 log_success " Verified: $name "
680766 # Run the installer
681- bash " $tmp_file " " ${args[@]} "
767+ " $bash_bin " " $tmp_file " " ${args[@]} "
682768 status=$?
683769 fi
684770
@@ -702,7 +788,7 @@ print_upstream_urls() {
702788 for name in " ${! KNOWN_INSTALLERS[@]} " ; do
703789 local url=" ${KNOWN_INSTALLERS[$name]} "
704790 printf " %-20s %s\n" " $name :" " $url "
705- done | sort
791+ done | acfs_security_sort_lines
706792
707793 echo " "
708794 printf " ${DIM} All URLs use HTTPS for secure transport.${NC} \n"
@@ -714,9 +800,7 @@ print_current_checksums() {
714800 local had_failure=false
715801 local tmp_output=" "
716802
717- if command -v mktemp & > /dev/null; then
718- tmp_output=$( mktemp " ${TMPDIR:-/ tmp} /acfs-checksums-out.XXXXXX" 2> /dev/null) || tmp_output=" "
719- fi
803+ tmp_output=" $( acfs_security_mktemp " ${TMPDIR:-/ tmp} /acfs-checksums-out.XXXXXX" 2> /dev/null) " || tmp_output=" "
720804
721805 if [[ -z " $tmp_output " ]]; then
722806 echo " ERROR: unable to create temp file for checksums output" >&2
@@ -730,7 +814,7 @@ print_current_checksums() {
730814
731815 {
732816 # YAML output to stdout
733- echo " # checksums.yaml - Auto-generated $( date -Iseconds) "
817+ echo " # checksums.yaml - Auto-generated $( acfs_security_date -Iseconds) "
734818 echo " # Run: ./scripts/lib/security.sh --update-checksums"
735819 echo " "
736820 echo " installers:"
@@ -742,7 +826,7 @@ print_current_checksums() {
742826 installer_names+=(" $name " )
743827 done
744828 if [[ ${# installer_names[@]} -gt 0 ]]; then
745- mapfile -t installer_names < <( printf ' %s\n' " ${installer_names[@]} " | sort )
829+ mapfile -t installer_names < <( printf ' %s\n' " ${installer_names[@]} " | acfs_security_sort_lines )
746830 fi
747831
748832 for name in " ${installer_names[@]} " ; do
@@ -772,13 +856,13 @@ print_current_checksums() {
772856 done
773857
774858 if [[ " $had_failure " == " true" ]]; then
775- rm -f " $tmp_output " 2> /dev/null || true
859+ _acfs_remove_temp_files " $tmp_output "
776860 echo " ERROR: one or more installer checksums failed to fetch; refusing to emit incomplete checksums.yaml" >&2
777861 return 1
778862 fi
779863
780- cat " $tmp_output "
781- rm -f " $tmp_output " 2> /dev/null || true
864+ acfs_security_cat_file " $tmp_output "
865+ _acfs_remove_temp_files " $tmp_output "
782866}
783867
784868# ============================================================
@@ -901,14 +985,23 @@ declare -g ACFS_CHECKSUMS_REMOTE_REFRESHED=false
901985
902986acfs_checksums_file_looks_valid () {
903987 local file=" $1 "
988+ local line=" "
989+
904990 [[ -r " $file " ]] || return 1
905- grep -Eq ' ^[[:space:]]*installers:[[:space:]]*$' " $file "
991+
992+ while IFS= read -r line || [[ -n " $line " ]]; do
993+ [[ " $line " =~ ^[[:space:]]* installers:[[:space:]]* $ ]] && return 0
994+ done < " $file "
995+
996+ return 1
906997}
907998
908999acfs_fetch_fresh_checksums_to_file () {
9091000 local dest=" $1 "
1001+ local cache_buster=" "
9101002 local api_url=" https://api.github.com/repos/${ACFS_REPO_OWNER} /${ACFS_REPO_NAME} /contents/checksums.yaml?ref=${ACFS_CHECKSUMS_REF} "
911- local raw_url=" https://raw.githubusercontent.com/${ACFS_REPO_OWNER} /${ACFS_REPO_NAME} /${ACFS_CHECKSUMS_REF} /checksums.yaml?cb=$( date +%s) "
1003+ cache_buster=" $( acfs_security_date +%s 2> /dev/null || printf ' 0' ) "
1004+ local raw_url=" https://raw.githubusercontent.com/${ACFS_REPO_OWNER} /${ACFS_REPO_NAME} /${ACFS_CHECKSUMS_REF} /checksums.yaml?cb=${cache_buster} "
9121005
9131006 : > " $dest " 2> /dev/null || {
9141007 log_detail " Unable to initialize temporary checksums file: $dest "
@@ -946,24 +1039,24 @@ acfs_refresh_loaded_checksums_from_remote() {
9461039 fi
9471040
9481041 local refreshed_file=" "
949- refreshed_file=" $( mktemp " ${TMPDIR:-/ tmp} /acfs-checksums-refresh.XXXXXX" 2> /dev/null) " || refreshed_file=" "
1042+ refreshed_file=" $( acfs_security_mktemp " ${TMPDIR:-/ tmp} /acfs-checksums-refresh.XXXXXX" 2> /dev/null) " || refreshed_file=" "
9501043 if [[ -z " $refreshed_file " ]]; then
9511044 log_detail " Unable to create temp file for refreshed checksums"
9521045 return 1
9531046 fi
9541047
9551048 if ! acfs_fetch_fresh_checksums_to_file " $refreshed_file " ; then
956- rm -f " $refreshed_file "
1049+ _acfs_remove_temp_files " $refreshed_file "
9571050 return 1
9581051 fi
9591052
9601053 if ! load_checksums " $refreshed_file " ; then
961- rm -f " $refreshed_file "
1054+ _acfs_remove_temp_files " $refreshed_file "
9621055 return 1
9631056 fi
9641057
9651058 ACFS_CHECKSUMS_REMOTE_REFRESHED=true
966- rm -f " $refreshed_file "
1059+ _acfs_remove_temp_files " $refreshed_file "
9671060 return 0
9681061}
9691062
@@ -1440,7 +1533,7 @@ verify_all_installers() {
14401533# Output: JSON object with matches, mismatches, and errors arrays
14411534verify_all_installers_json () {
14421535 local timestamp
1443- timestamp=$( date -u +" %Y-%m-%dT%H:%M:%SZ" )
1536+ timestamp=" $( acfs_security_date -u +" %Y-%m-%dT%H:%M:%SZ" ) "
14441537
14451538 # Arrays to collect results
14461539 local matches=()
@@ -1477,13 +1570,9 @@ verify_all_installers_json() {
14771570 local fetch_error=" "
14781571 local tmp_err=" "
14791572
1480- # Create temp file for stderr capture
1481- # Fallback to empty if mktemp fails (no capture), do NOT use predictable /tmp paths.
1482- if command -v mktemp & > /dev/null; then
1483- tmp_err=$( mktemp 2> /dev/null) || tmp_err=" "
1484- else
1485- tmp_err=" "
1486- fi
1573+ # Create temp file for stderr capture. If this fails, do not use a
1574+ # predictable fallback path; lose stderr detail instead.
1575+ tmp_err=" $( acfs_security_mktemp 2> /dev/null) " || tmp_err=" "
14871576
14881577 # Capture stdout to variable, stderr to file (if tmp_err exists)
14891578 if [[ -n " $tmp_err " ]]; then
@@ -1492,10 +1581,10 @@ verify_all_installers_json() {
14921581 :
14931582 else
14941583 # Failure
1495- fetch_error=$( cat " $tmp_err " )
1584+ fetch_error=" $( acfs_security_cat_file " $tmp_err " ) "
14961585 [[ -z " $fetch_error " ]] && fetch_error=" Unknown error fetching checksum"
14971586 fi
1498- rm -f " $tmp_err "
1587+ _acfs_remove_temp_files " $tmp_err "
14991588 else
15001589 # Fallback: capture combined output or lose stderr if we can't separate them safely
15011590 # without a temp file. Here we prioritize safety over error details.
0 commit comments