From 18cf46c3f653220c893f27be8271c5f41351dd14 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Sat, 19 Sep 2026 17:45:24 +0530 Subject: [PATCH 1/2] fix: verify staged vendor payloads before install --- scripts/vendor | 66 +++++++++++++++++++++++++++++++++++++++++++---- tests/vendor.bats | 25 ++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/scripts/vendor b/scripts/vendor index cea4b94..e880388 100755 --- a/scripts/vendor +++ b/scripts/vendor @@ -37,15 +37,67 @@ verify_bundle() { } copy_verified_bundle() { - local source="$1" destination="$2" checksum path + local source="$1" destination="$2" checksum path source_hash destination_hash + local source_manifest_hash source_manifest_hash_after destination_manifest_hash + [[ -f "$source/MANIFEST.sha256" && ! -L "$source/MANIFEST.sha256" ]] || return 1 + source_manifest_hash="$(hash_file "$source/MANIFEST.sha256")" || return 1 while read -r checksum path; do [[ -n "$checksum" ]] || continue [[ -n "$path" ]] || continue + [[ -f "$source/$path" && ! -L "$source/$path" ]] || { + error "bundle payload changed to a symlink or non-file while being copied: $path" + return 1 + } + source_hash="$(hash_file "$source/$path")" || return 1 + [[ "$source_hash" == "$checksum" ]] || { + error "bundle payload changed before it was copied: $path" + return 1 + } mkdir -p "$destination/$(dirname -- "$path")" || return 1 cp -- "$source/$path" "$destination/$path" || return 1 + [[ -f "$destination/$path" && ! -L "$destination/$path" ]] || { + error "copied bundle payload is not a regular file: $path" + return 1 + } + destination_hash="$(hash_file "$destination/$path")" || return 1 + [[ "$destination_hash" == "$checksum" ]] || { + error "copied bundle payload hash mismatch: $path" + return 1 + } + [[ -f "$source/$path" && ! -L "$source/$path" ]] || { + error "bundle payload changed to a symlink or non-file while being copied: $path" + return 1 + } + source_hash="$(hash_file "$source/$path")" || return 1 + [[ "$source_hash" == "$checksum" ]] || { + error "bundle payload changed while it was being copied: $path" + return 1 + } if [[ -x "$source/$path" ]]; then chmod +x "$destination/$path" || return 1; fi done < "$source/MANIFEST.sha256" cp -- "$source/MANIFEST.sha256" "$destination/MANIFEST.sha256" || return 1 + destination_manifest_hash="$(hash_file "$destination/MANIFEST.sha256")" || return 1 + [[ "$destination_manifest_hash" == "$source_manifest_hash" ]] || { + error 'copied bundle checksum manifest changed while it was being copied' + return 1 + } + source_manifest_hash_after="$(hash_file "$source/MANIFEST.sha256")" || return 1 + [[ "$source_manifest_hash_after" == "$source_manifest_hash" ]] || { + error 'bundle checksum manifest changed while it was being copied' + return 1 + } +} + +verify_manifest_copy() { + local root="$1" checksum path actual + + [[ -f "$root/MANIFEST.sha256" && ! -L "$root/MANIFEST.sha256" ]] || return 1 + while read -r checksum path; do + [[ -n "$checksum" && -n "$path" ]] || continue + [[ -f "$root/$path" && ! -L "$root/$path" ]] || return 1 + actual="$(hash_file "$root/$path")" || return 1 + [[ "$actual" == "$checksum" ]] || return 1 + done < "$root/MANIFEST.sha256" } validate_payload_file() { @@ -160,7 +212,8 @@ install_bundle() { rm -rf -- "$temporary" return 1 fi - if ! write_lock "$temporary" "$bundle" "$mode"; then + if ! write_lock "$temporary" "$temporary" "$mode" || + ! BUNDLE_VERIFY_ALLOW_LOCK=1 "$repo_root/scripts/library-bundle" verify "$temporary" > /dev/null; then rm -rf -- "$temporary" return 1 fi @@ -191,7 +244,8 @@ update_bundle() { rm -rf -- "$temporary" return 1 fi - if ! write_lock "$temporary" "$bundle" update; then + if ! write_lock "$temporary" "$temporary" update || + ! BUNDLE_VERIFY_ALLOW_LOCK=1 "$repo_root/scripts/library-bundle" verify "$temporary" > /dev/null; then rm -rf -- "$temporary" return 1 fi @@ -340,7 +394,9 @@ standalone_bundle() { if ! mkdir -p "$temporary/vendor/base-bash-libs" "$temporary/bin" || ! copy_verified_bundle "$framework_bundle" "$temporary" || ! copy_verified_bundle "$framework_bundle" "$temporary/vendor/base-bash-libs" || - ! write_lock "$temporary/vendor/base-bash-libs" "$framework_bundle" standalone || + ! write_lock "$temporary/vendor/base-bash-libs" "$temporary/vendor/base-bash-libs" standalone || + ! verify_manifest_copy "$temporary" || + ! BUNDLE_VERIFY_ALLOW_LOCK=1 "$repo_root/scripts/library-bundle" verify "$temporary/vendor/base-bash-libs" > /dev/null || ! chmod +x "$temporary/bin/base-bash"; then rm -rf -- "$temporary" return 1 @@ -361,7 +417,7 @@ standalone_bundle() { return 1 } printf 'standalone_format=1\nframework_lock=%s\nprovenance=verified-offline-bundle\n' \ - "$(hash_file "$framework_bundle/MANIFEST.sha256")" > "$temporary/BASE_BASH_STANDALONE.release" || { + "$(hash_file "$temporary/MANIFEST.sha256")" > "$temporary/BASE_BASH_STANDALONE.release" || { rm -rf -- "$temporary" return 1 } diff --git a/tests/vendor.bats b/tests/vendor.bats index 3433614..60fd49f 100644 --- a/tests/vendor.bats +++ b/tests/vendor.bats @@ -21,6 +21,12 @@ vendor_test_make_copy_race_stub() { set -u source_path="${@: -2:1}" if [[ "$source_path" == "${VENDOR_TEST_RACE_SOURCE-}" ]]; then + if [[ "${VENDOR_TEST_RACE_MODE-}" == content-after ]]; then + "$VENDOR_TEST_REAL_CP" "$@" + copy_status=$? + printf 'tampered-after-copy\n' > "$source_path" + exit "$copy_status" + fi if [[ "${VENDOR_TEST_RACE_MODE-}" == parent ]]; then mv -- "$VENDOR_TEST_RACE_PARENT" "$VENDOR_TEST_RACE_BACKUP" || exit 1 ln -s -- "$VENDOR_TEST_RACE_TARGET" "$VENDOR_TEST_RACE_PARENT" || { @@ -49,6 +55,25 @@ EOF chmod +x "$stub_dir/cp" } +@test "vendor create rejects framework payload mutation during copy" { + local real_cp + real_cp="$(command -v cp)" + vendor_test_make_copy_race_stub + + bats_run env PATH="$TEST_TMPDIR/racing-cp-bin:$BASE_TEST_ORIG_PATH" \ + VENDOR_TEST_REAL_CP="$real_cp" \ + VENDOR_TEST_RACE_MODE=content-after \ + VENDOR_TEST_RACE_SOURCE="$framework_bundle/VERSION" \ + "$BASE_REPO_ROOT/scripts/vendor" create "$framework_bundle" "$vendor_tree" + [ "$status" -eq 1 ] + [[ "$output" == *"changed while it was being copied"* ]] || { + printf 'Unexpected vendor staging output: %s\n' "$output" >&2 + false + } + [ ! -e "$vendor_tree" ] + [ -z "$(find "${vendor_tree}.tmp."* -maxdepth 0 -print -quit 2>/dev/null)" ] +} + @test "vendor create and verify are offline and immutable" { bats_run "$BASE_REPO_ROOT/scripts/vendor" create "$framework_bundle" "$vendor_tree" [ "$status" -eq 0 ] From 3f2893a22442d765fbc7d6485a8af1c2717bfeba Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:24:07 +0530 Subject: [PATCH 2/2] fix: harden verified standalone staging --- docs/vendor-workflow.md | 11 +- scripts/bundle-manifest.sh | 28 +++++ scripts/library-bundle | 10 +- scripts/vendor | 251 +++++++++++++++++++++++++++++++------ tests/library-bundle.bats | 7 ++ tests/validate.sh | 2 + tests/vendor.bats | 14 +++ 7 files changed, 279 insertions(+), 44 deletions(-) create mode 100644 scripts/bundle-manifest.sh diff --git a/docs/vendor-workflow.md b/docs/vendor-workflow.md index 14bc606..ff355d3 100644 --- a/docs/vendor-workflow.md +++ b/docs/vendor-workflow.md @@ -69,7 +69,10 @@ its own `base-bash-libs.lock`, so consumers can verify it independently: scripts/vendor verify dist/app/vendor/base-bash-libs ``` -Both framework copies carry the same `MANIFEST.sha256`, version, and source commit from -the input bundle. Standalone creation stages the complete payload and its lock -before one atomic move. No command downloads, executes, or evaluates remote -content. +Both framework copies carry the same framework version, source commit, and +canonical manifest before the application payload is restored. The root copy's +manifest then records the application's user-visible `VERSION`, while +`BASE_BASH_STANDALONE.release` binds `framework_lock` to the canonical manifest +in `vendor/base-bash-libs`. Standalone creation stages the complete payload and +its lock before one atomic move. No command downloads, executes, or evaluates +remote content. diff --git a/scripts/bundle-manifest.sh b/scripts/bundle-manifest.sh new file mode 100644 index 0000000..5efc69a --- /dev/null +++ b/scripts/bundle-manifest.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Shared validation for paths named by MANIFEST.sha256 files. Callers retain +# responsibility for reporting the context-specific failure message. + +bundle_manifest_path_is_safe() { + local path="${1-}" + + [[ "$path" =~ ^[A-Za-z0-9_./-]+$ ]] || return 1 + [[ -n "$path" && "$path" != /* && "$path" != */ && "$path" != *//* ]] || return 1 + case "$path" in + . | .. | ./* | ../* | */./* | */../* | */. | */..) + return 1 + ;; + esac +} + +bundle_manifest_path_has_no_symlink_component() { + local root="$1" relative="$2" candidate component + local -a components=() + + IFS='/' read -r -a components <<< "$relative" + candidate="$root" + for component in "${components[@]}"; do + candidate="$candidate/$component" + [[ ! -L "$candidate" ]] || return 1 + done +} diff --git a/scripts/library-bundle b/scripts/library-bundle index ebca190..8b64cff 100755 --- a/scripts/library-bundle +++ b/scripts/library-bundle @@ -5,6 +5,9 @@ repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)" || exit 1 +# shellcheck source=bundle-manifest.sh +source "$repo_root/scripts/bundle-manifest.sh" || exit 1 + usage() { cat >&2 << 'EOF' Usage: @@ -167,8 +170,7 @@ verify_bundle() { } expected="${BASH_REMATCH[1]}" path="${BASH_REMATCH[2]}" - [[ "$path" != /* && "$path" != ./* && "$path" != */ && "$path" != *'//'* && - "$path" != *'/../'* && "$path" != ../* && "$path" != *'/./'* && "$path" != ./ ]] || { + bundle_manifest_path_is_safe "$path" || { error "unsafe checksum path: $path" return 1 } @@ -185,6 +187,10 @@ verify_bundle() { error "bundle file must not be a symlink: $path" return 1 } + bundle_manifest_path_has_no_symlink_component "$root" "$path" || { + error "bundle path traverses a symlink: $path" + return 1 + } actual="$(hash_file "$root/$path")" [[ "$actual" == "$expected" ]] || { error "bundle hash mismatch: $path" diff --git a/scripts/vendor b/scripts/vendor index e880388..bb0e319 100755 --- a/scripts/vendor +++ b/scripts/vendor @@ -5,6 +5,9 @@ repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)" || exit 1 +# shellcheck source=bundle-manifest.sh +source "$repo_root/scripts/bundle-manifest.sh" || exit 1 + usage() { cat >&2 << 'EOF' Usage: @@ -37,51 +40,122 @@ verify_bundle() { } copy_verified_bundle() { - local source="$1" destination="$2" checksum path source_hash destination_hash + local source="$1" destination="$2" checksum path line source_hash destination_hash local source_manifest_hash source_manifest_hash_after destination_manifest_hash - [[ -f "$source/MANIFEST.sha256" && ! -L "$source/MANIFEST.sha256" ]] || return 1 - source_manifest_hash="$(hash_file "$source/MANIFEST.sha256")" || return 1 - while read -r checksum path; do - [[ -n "$checksum" ]] || continue - [[ -n "$path" ]] || continue - [[ -f "$source/$path" && ! -L "$source/$path" ]] || { - error "bundle payload changed to a symlink or non-file while being copied: $path" + local source_executable line_number=0 + local -a manifest_lines=() + + [[ -d "$source" && ! -L "$source" ]] || { + error "bundle source is not a real directory: $source" + return 1 + } + [[ -f "$source/MANIFEST.sha256" && ! -L "$source/MANIFEST.sha256" ]] || { + error "bundle checksum manifest is missing or is a symlink: $source" + return 1 + } + source_manifest_hash="$(hash_file "$source/MANIFEST.sha256")" || { + error "unable to hash bundle checksum manifest: $source" + return 1 + } + while IFS= read -r line || [[ -n "$line" ]]; do + manifest_lines+=("$line") + done < "$source/MANIFEST.sha256" + + for line in "${manifest_lines[@]}"; do + line_number=$((line_number + 1)) + [[ "$line" =~ ^([0-9a-f]{64})[[:space:]][[:space:]]([A-Za-z0-9_./-]+)$ ]] || { + error "malformed bundle checksum entry at line $line_number" + return 1 + } + checksum="${BASH_REMATCH[1]}" + path="${BASH_REMATCH[2]}" + bundle_manifest_path_is_safe "$path" || { + error "unsafe bundle checksum path: $path" + return 1 + } + [[ -f "$source/$path" ]] || { + error "bundle payload is missing while being copied: $path" + return 1 + } + [[ ! -L "$source/$path" ]] || { + error "bundle payload is a symlink while being copied: $path" + return 1 + } + bundle_manifest_path_has_no_symlink_component "$source" "$path" || { + error "bundle payload path traverses a symlink while being copied: $path" + return 1 + } + source_hash="$(hash_file "$source/$path")" || { + error "unable to hash bundle payload while copying: $path" return 1 } - source_hash="$(hash_file "$source/$path")" || return 1 [[ "$source_hash" == "$checksum" ]] || { error "bundle payload changed before it was copied: $path" return 1 } - mkdir -p "$destination/$(dirname -- "$path")" || return 1 - cp -- "$source/$path" "$destination/$path" || return 1 + source_executable=0 + [[ -x "$source/$path" ]] && source_executable=1 + mkdir -p "$destination/$(dirname -- "$path")" || { + error "unable to create bundle destination path: $path" + return 1 + } + cp -- "$source/$path" "$destination/$path" || { + error "unable to copy bundle payload: $path" + return 1 + } [[ -f "$destination/$path" && ! -L "$destination/$path" ]] || { error "copied bundle payload is not a regular file: $path" return 1 } - destination_hash="$(hash_file "$destination/$path")" || return 1 + destination_hash="$(hash_file "$destination/$path")" || { + error "unable to hash copied bundle payload: $path" + return 1 + } [[ "$destination_hash" == "$checksum" ]] || { error "copied bundle payload hash mismatch: $path" return 1 } - [[ -f "$source/$path" && ! -L "$source/$path" ]] || { - error "bundle payload changed to a symlink or non-file while being copied: $path" + [[ -f "$source/$path" ]] || { + error "bundle payload disappeared while it was being copied: $path" + return 1 + } + [[ ! -L "$source/$path" ]] || { + error "bundle payload changed to a symlink while it was being copied: $path" + return 1 + } + bundle_manifest_path_has_no_symlink_component "$source" "$path" || { + error "bundle payload path changed to a symlink while it was being copied: $path" + return 1 + } + source_hash="$(hash_file "$source/$path")" || { + error "unable to re-hash bundle payload after copying: $path" return 1 } - source_hash="$(hash_file "$source/$path")" || return 1 [[ "$source_hash" == "$checksum" ]] || { error "bundle payload changed while it was being copied: $path" return 1 } - if [[ -x "$source/$path" ]]; then chmod +x "$destination/$path" || return 1; fi - done < "$source/MANIFEST.sha256" - cp -- "$source/MANIFEST.sha256" "$destination/MANIFEST.sha256" || return 1 - destination_manifest_hash="$(hash_file "$destination/MANIFEST.sha256")" || return 1 + if ((source_executable)) && ! chmod +x "$destination/$path"; then + error "unable to preserve executable mode for bundle payload: $path" + return 1 + fi + done + cp -- "$source/MANIFEST.sha256" "$destination/MANIFEST.sha256" || { + error 'unable to copy bundle checksum manifest' + return 1 + } + destination_manifest_hash="$(hash_file "$destination/MANIFEST.sha256")" || { + error 'unable to hash copied bundle checksum manifest' + return 1 + } [[ "$destination_manifest_hash" == "$source_manifest_hash" ]] || { error 'copied bundle checksum manifest changed while it was being copied' return 1 } - source_manifest_hash_after="$(hash_file "$source/MANIFEST.sha256")" || return 1 + source_manifest_hash_after="$(hash_file "$source/MANIFEST.sha256")" || { + error 'unable to re-hash bundle checksum manifest after copying' + return 1 + } [[ "$source_manifest_hash_after" == "$source_manifest_hash" ]] || { error 'bundle checksum manifest changed while it was being copied' return 1 @@ -89,14 +163,54 @@ copy_verified_bundle() { } verify_manifest_copy() { - local root="$1" checksum path actual + local root="$1" checksum path actual line line_number=0 + local -A checksum_seen=() - [[ -f "$root/MANIFEST.sha256" && ! -L "$root/MANIFEST.sha256" ]] || return 1 - while read -r checksum path; do - [[ -n "$checksum" && -n "$path" ]] || continue - [[ -f "$root/$path" && ! -L "$root/$path" ]] || return 1 - actual="$(hash_file "$root/$path")" || return 1 - [[ "$actual" == "$checksum" ]] || return 1 + [[ -d "$root" && ! -L "$root" ]] || { + error "manifest verification root is not a real directory: $root" + return 1 + } + [[ -f "$root/MANIFEST.sha256" && ! -L "$root/MANIFEST.sha256" ]] || { + error "manifest verification root lacks a checksum manifest: $root" + return 1 + } + while IFS= read -r line || [[ -n "$line" ]]; do + line_number=$((line_number + 1)) + [[ "$line" =~ ^([0-9a-f]{64})[[:space:]][[:space:]]([A-Za-z0-9_./-]+)$ ]] || { + error "malformed manifest entry at line $line_number" + return 1 + } + checksum="${BASH_REMATCH[1]}" + path="${BASH_REMATCH[2]}" + bundle_manifest_path_is_safe "$path" || { + error "unsafe manifest path: $path" + return 1 + } + [[ -z "${checksum_seen[$path]+set}" ]] || { + error "duplicate manifest entry: $path" + return 1 + } + checksum_seen["$path"]=1 + [[ -f "$root/$path" ]] || { + error "manifest payload is missing: $path" + return 1 + } + [[ ! -L "$root/$path" ]] || { + error "manifest payload must not be a symlink: $path" + return 1 + } + bundle_manifest_path_has_no_symlink_component "$root" "$path" || { + error "manifest path traverses a symlink: $path" + return 1 + } + actual="$(hash_file "$root/$path")" || { + error "unable to hash manifest payload: $path" + return 1 + } + [[ "$actual" == "$checksum" ]] || { + error "manifest payload hash mismatch: $path" + return 1 + } done < "$root/MANIFEST.sha256" } @@ -183,6 +297,45 @@ standalone_destination_is_external() { fi } +refresh_standalone_manifest() { + local root="$1" version_hash line temporary version_seen=0 + + version_hash="$(hash_file "$root/VERSION")" || { + error 'unable to hash standalone application VERSION' + return 1 + } + temporary="$(mktemp "$root/MANIFEST.sha256.tmp.XXXXXX")" || { + error 'unable to stage the standalone root checksum manifest' + return 1 + } + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" == *' VERSION' ]]; then + version_seen=$((version_seen + 1)) + printf '%s VERSION\n' "$version_hash" >> "$temporary" || { + rm -f -- "$temporary" + error 'unable to write the standalone root VERSION checksum' + return 1 + } + else + printf '%s\n' "$line" >> "$temporary" || { + rm -f -- "$temporary" + error 'unable to rewrite the standalone root checksum manifest' + return 1 + } + fi + done < "$root/MANIFEST.sha256" + if ((version_seen != 1)); then + rm -f -- "$temporary" + error 'standalone root checksum manifest does not contain exactly one VERSION entry' + return 1 + fi + mv -- "$temporary" "$root/MANIFEST.sha256" || { + rm -f -- "$temporary" + error 'unable to install the standalone root checksum manifest' + return 1 + } +} + write_lock() { local root="$1" bundle="$2" mode="$3" local source_version source_commit manifest_hash @@ -322,7 +475,7 @@ verify_destination() { standalone_bundle() { local application="$1" framework_bundle="$2" destination="$3" temporary application_payload relative - local application_root required_payload + local application_root required_payload framework_manifest_hash root_manifest_hash local -a payload_files=() selected_paths=() local -A payload_seen=() required_payloads=() shift 3 @@ -382,25 +535,43 @@ standalone_bundle() { error "unable to create a private staging directory beside '$destination'" return 1 } - application_payload="$temporary/.application-payload" - if ! copy_payload_files "$application_root" "$application_payload" "${selected_paths[@]}"; then - rm -rf -- "$temporary" - return 1 - fi # Put the verified framework at the standalone root for the established # artifact layout, then restore the consumer-owned metadata that shares a # framework filename. The launcher reads its version from the framework # release file, so the application VERSION remains user-visible. if ! mkdir -p "$temporary/vendor/base-bash-libs" "$temporary/bin" || ! copy_verified_bundle "$framework_bundle" "$temporary" || - ! copy_verified_bundle "$framework_bundle" "$temporary/vendor/base-bash-libs" || - ! write_lock "$temporary/vendor/base-bash-libs" "$temporary/vendor/base-bash-libs" standalone || - ! verify_manifest_copy "$temporary" || - ! BUNDLE_VERIFY_ALLOW_LOCK=1 "$repo_root/scripts/library-bundle" verify "$temporary/vendor/base-bash-libs" > /dev/null || + ! BUNDLE_VERIFY_ALLOW_LOCK=1 "$repo_root/scripts/library-bundle" verify "$temporary" > /dev/null || ! chmod +x "$temporary/bin/base-bash"; then rm -rf -- "$temporary" return 1 fi + if ! copy_verified_bundle "$framework_bundle" "$temporary/vendor/base-bash-libs" || + ! write_lock "$temporary/vendor/base-bash-libs" "$temporary/vendor/base-bash-libs" standalone || + ! BUNDLE_VERIFY_ALLOW_LOCK=1 "$repo_root/scripts/library-bundle" verify "$temporary/vendor/base-bash-libs" > /dev/null; then + rm -rf -- "$temporary" + return 1 + fi + framework_manifest_hash="$(hash_file "$temporary/vendor/base-bash-libs/MANIFEST.sha256")" || { + rm -rf -- "$temporary" + error 'unable to hash the staged framework manifest' + return 1 + } + root_manifest_hash="$(hash_file "$temporary/MANIFEST.sha256")" || { + rm -rf -- "$temporary" + error 'unable to hash the staged standalone root manifest' + return 1 + } + [[ "$root_manifest_hash" == "$framework_manifest_hash" ]] || { + rm -rf -- "$temporary" + error 'standalone framework copies observed different manifest content' + return 1 + } + application_payload="$temporary/.application-payload" + if ! copy_payload_files "$application_root" "$application_payload" "${selected_paths[@]}"; then + rm -rf -- "$temporary" + return 1 + fi while IFS= read -r relative; do [[ -n "$relative" ]] || continue mkdir -p "$temporary/$(dirname -- "$relative")" || { @@ -416,8 +587,12 @@ standalone_bundle() { rm -rf -- "$temporary" return 1 } + if ! refresh_standalone_manifest "$temporary" || ! verify_manifest_copy "$temporary"; then + rm -rf -- "$temporary" + return 1 + fi printf 'standalone_format=1\nframework_lock=%s\nprovenance=verified-offline-bundle\n' \ - "$(hash_file "$temporary/MANIFEST.sha256")" > "$temporary/BASE_BASH_STANDALONE.release" || { + "$framework_manifest_hash" > "$temporary/BASE_BASH_STANDALONE.release" || { rm -rf -- "$temporary" return 1 } diff --git a/tests/library-bundle.bats b/tests/library-bundle.bats index 60d07b8..0be0384 100644 --- a/tests/library-bundle.bats +++ b/tests/library-bundle.bats @@ -118,6 +118,13 @@ setup() { [ "$status" -eq 1 ] [[ "$output" == *"symlink"* ]] + cp -R "$source" "$TEST_TMPDIR/parent-symlink" + mv "$TEST_TMPDIR/parent-symlink/lib" "$TEST_TMPDIR/parent-symlink/lib.real" + ln -s lib.real "$TEST_TMPDIR/parent-symlink/lib" + bats_run "$BASE_REPO_ROOT/scripts/library-bundle" verify "$TEST_TMPDIR/parent-symlink" + [ "$status" -eq 1 ] + [[ "$output" == *"path traverses a symlink"* ]] + cp -R "$source" "$TEST_TMPDIR/metadata" awk '{ if ($0 ~ /^source_version=/) print "source_version=9.9.9"; else print }' \ "$TEST_TMPDIR/metadata/BUNDLE.release" > "$TEST_TMPDIR/metadata/BUNDLE.tmp" diff --git a/tests/validate.sh b/tests/validate.sh index a1125df..3051264 100755 --- a/tests/validate.sh +++ b/tests/validate.sh @@ -48,6 +48,7 @@ required_files=( scripts/release-bom-row scripts/release-version-policy.sh scripts/api-manifest + scripts/bundle-manifest.sh scripts/library-bundle scripts/vendor scripts/migrate-v2-symbols @@ -412,6 +413,7 @@ done <<< "$manifest_source_paths" run_stage "ShellCheck error profile" shellcheck --severity=error \ bin/base-bash \ scripts/api-manifest \ + scripts/bundle-manifest.sh \ scripts/library-bundle \ scripts/vendor \ scripts/release \ diff --git a/tests/vendor.bats b/tests/vendor.bats index 60fd49f..f7eb962 100644 --- a/tests/vendor.bats +++ b/tests/vendor.bats @@ -13,6 +13,14 @@ setup() { BASE_BASH_LIBS_DIR="$BASE_BASH_DIR" "$BASE_REPO_ROOT/bin/base-bash" init --profile standard --dir "$application" >/dev/null } +vendor_test_hash_file() { + if command -v sha256sum > /dev/null 2>&1; then + sha256sum -- "$1" | awk '{print $1}' + else + shasum -a 256 -- "$1" | awk '{print $1}' + fi +} + vendor_test_make_copy_race_stub() { local stub_dir="$TEST_TMPDIR/racing-cp-bin" mkdir -p "$stub_dir" @@ -174,6 +182,12 @@ SCRIPT "$(sed -n 's/^source_version=//p' "$standalone/vendor/base-bash-libs/BUNDLE.release")" ] [ "$(sed -n 's/^source_commit=//p' "$standalone/vendor/base-bash-libs/base-bash-libs.lock")" = \ "$(sed -n 's/^source_commit=//p' "$standalone/vendor/base-bash-libs/BUNDLE.release")" ] + root_version_hash="$(sed -n 's/^\([0-9a-f]*\) VERSION$/\1/p' "$standalone/MANIFEST.sha256")" + nested_version_hash="$(sed -n 's/^\([0-9a-f]*\) VERSION$/\1/p' "$standalone/vendor/base-bash-libs/MANIFEST.sha256")" + [ "$root_version_hash" = "$(vendor_test_hash_file "$standalone/VERSION")" ] + [ "$nested_version_hash" = "$(vendor_test_hash_file "$standalone/vendor/base-bash-libs/VERSION")" ] + [ "$(sed -n 's/^framework_lock=//p' "$standalone/BASE_BASH_STANDALONE.release")" = \ + "$(vendor_test_hash_file "$standalone/vendor/base-bash-libs/MANIFEST.sha256")" ] bats_run env PATH="$standalone/bin:$PATH" "$standalone/bin/app" run [ "$status" -eq 0 ] [[ "$output" == *"hello=world"* ]]