Files
antigravity-skills-reference/scripts/activate-skills.sh
sickn33 bc49ceec90 fix(security): harden skill apply and activation flows
Restrict auto-apply to trusted review comments so spoofed issue comments
cannot write optimized SKILL.md content into pull request branches.

Reject activation symlinks that escape the source root and add
regression coverage for both security checks.
2026-03-26 13:24:04 +01:00

191 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_BASE_DIR="${HOME:-$PWD}/.gemini/antigravity"
BASE_DIR="${AG_BASE_DIR:-$DEFAULT_BASE_DIR}"
SKILLS_DIR="${AG_SKILLS_DIR:-$BASE_DIR/skills}"
LIBRARY_DIR="${AG_LIBRARY_DIR:-$BASE_DIR/skills_library}"
ARCHIVE_PREFIX="${AG_ARCHIVE_PREFIX:-$BASE_DIR/skills_archive}"
REPO_SKILLS_DIR="${AG_REPO_SKILLS_DIR:-$SCRIPT_DIR/../skills}"
BUNDLE_HELPER="${AG_BUNDLE_HELPER:-$SCRIPT_DIR/../tools/scripts/get-bundle-skills.py}"
PYTHON_BIN="${AG_PYTHON_BIN:-}"
SKILLS_LIST_FILE="$(mktemp "${TMPDIR:-/tmp}/ag-skills.XXXXXX")"
cleanup() {
rm -f "$SKILLS_LIST_FILE"
}
trap cleanup EXIT
find_copy_dirs() {
local src_dir="$1"
local dest_dir="$2"
mkdir -p "$dest_dir"
while IFS= read -r -d '' item; do
if [[ -L "$item" ]] && ! is_safe_dir_symlink "$src_dir" "$item"; then
echo " ! Skipping unsafe symlink outside source root: $(basename "$item")"
continue
fi
cp -RP "$item" "$dest_dir/"
done < <(find "$src_dir" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -print0 2>/dev/null)
}
find_move_dirs() {
local src_dir="$1"
local dest_dir="$2"
mkdir -p "$dest_dir"
while IFS= read -r -d '' item; do
mv "$item" "$dest_dir/"
done < <(find "$src_dir" -mindepth 1 -maxdepth 1 ! -name '.' ! -name '..' -print0 2>/dev/null)
}
resolve_python() {
if [[ -n "$PYTHON_BIN" ]]; then
printf '%s\n' "$PYTHON_BIN"
return 0
fi
if command -v python3 >/dev/null 2>&1; then
command -v python3
return 0
fi
if command -v python >/dev/null 2>&1; then
command -v python
return 0
fi
return 1
}
is_safe_dir_symlink() {
local root_dir="$1"
local item="$2"
local python_path=""
if ! python_path="$(resolve_python 2>/dev/null)"; then
return 1
fi
"$python_path" - "$root_dir" "$item" <<'PY'
from pathlib import Path
import sys
root_dir = Path(sys.argv[1]).resolve()
item = Path(sys.argv[2])
try:
target = item.resolve(strict=True)
except FileNotFoundError:
raise SystemExit(1)
if not target.is_dir():
raise SystemExit(1)
try:
target.relative_to(root_dir)
except ValueError:
raise SystemExit(1)
raise SystemExit(0)
PY
}
is_safe_skill_id() {
[[ "$1" =~ ^[A-Za-z0-9._-]+$ ]]
}
echo "Activating Antigravity skills..."
DO_CLEAR=0
EXTRA_ARGS=()
for arg in "$@"; do
if [[ "$arg" == "--clear" ]]; then
DO_CLEAR=1
else
EXTRA_ARGS+=("$arg")
fi
done
if [[ -d "$REPO_SKILLS_DIR" ]]; then
echo "Syncing library with repository source..."
find_copy_dirs "$REPO_SKILLS_DIR" "$LIBRARY_DIR"
fi
if [[ ! -d "$LIBRARY_DIR" ]]; then
echo "Initializing skills library from local state..."
mkdir -p "$LIBRARY_DIR"
if [[ -d "$SKILLS_DIR" ]]; then
echo " + Moving current skills to library..."
find_move_dirs "$SKILLS_DIR" "$LIBRARY_DIR"
fi
while IFS= read -r archive_dir; do
[[ -n "$archive_dir" ]] || continue
echo " + Merging skills from $(basename "$archive_dir")..."
find_copy_dirs "$archive_dir" "$LIBRARY_DIR"
done < <(find "$BASE_DIR" -mindepth 1 -maxdepth 1 -type d -name 'skills_archive*' | sort)
fi
if [[ "$DO_CLEAR" == "1" ]]; then
echo "[RESET] Archiving and clearing existing skills..."
if [[ -d "$SKILLS_DIR" ]]; then
archive_dir="${ARCHIVE_PREFIX}_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$archive_dir"
find_move_dirs "$SKILLS_DIR" "$archive_dir"
fi
else
echo "[APPEND] Layering new skills onto existing folder..."
fi
mkdir -p "$SKILLS_DIR"
echo "Expanding bundles..."
python_path=""
if python_path="$(resolve_python 2>/dev/null)" && [[ -f "$BUNDLE_HELPER" ]]; then
if [[ "${#EXTRA_ARGS[@]}" -gt 0 ]]; then
"$python_path" "$BUNDLE_HELPER" "${EXTRA_ARGS[@]}" >"$SKILLS_LIST_FILE" 2>/dev/null || true
else
"$python_path" "$BUNDLE_HELPER" Essentials >"$SKILLS_LIST_FILE" 2>/dev/null || true
fi
fi
if [[ ! -s "$SKILLS_LIST_FILE" ]]; then
if [[ "${#EXTRA_ARGS[@]}" -eq 0 ]]; then
printf '%s\n' brainstorming systematic-debugging test-driven-development >"$SKILLS_LIST_FILE"
else
: >"$SKILLS_LIST_FILE"
for arg in "${EXTRA_ARGS[@]}"; do
if is_safe_skill_id "$arg"; then
printf '%s\n' "$arg" >>"$SKILLS_LIST_FILE"
fi
done
fi
fi
echo "Restoring selected skills..."
while IFS= read -r skill_id || [[ -n "$skill_id" ]]; do
[[ -n "$skill_id" ]] || continue
if [[ -e "$SKILLS_DIR/$skill_id" ]]; then
echo " . $skill_id (already active)"
elif [[ -e "$LIBRARY_DIR/$skill_id" ]]; then
echo " + $skill_id"
cp -RP "$LIBRARY_DIR/$skill_id" "$SKILLS_DIR/"
else
echo " - $skill_id (not found in library)"
fi
done <"$SKILLS_LIST_FILE"
echo
echo "Done! Antigravity skills are now activated."