Establishes this repository as the authoritative source for Pi agent configuration across scenarios, starting with the documentation layer. Key verified findings (probe harness included, zero model tokens): - The skills section of the system prompt is emitted only when an active tool named 'read' exists (system-prompt.js:59,113). Therefore --no-tools silently makes every SKILL.md unreachable and --skill a no-op. - registerTool accepts a plain JSON Schema object, so tool definitions can be served from a backend instead of duplicated in TypeScript. - An extension can shadow a built-in tool by name, which is how a dedicated agent gets a path-restricted 'read' while still satisfying the rule above. - .pi/SYSTEM.md replaces pi's coding-assistant prompt, but the replacement branch contributes neither the tool list nor the guidelines. - Without --no-skills/--no-extensions, user-global resources leak into every scenario; probed leak was find-skills, modsearch, summarize. Measured effect of the full baseline: system prompt 2619 -> 960 characters, coding-assistant framing and pi-docs paths removed, skill finally reachable. Secrets are guarded by scripts/verify-no-secrets.sh, installed as a pre-commit hook. Backups deliberately live outside the repository.
108 lines
3.6 KiB
Bash
Executable File
108 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ---------------------------------------------------------------------------
|
|
# Refuse to commit credentials.
|
|
#
|
|
# This repository stores Pi configuration for several agent scenarios. Some of
|
|
# the files it mirrors (models.json, auth.json, *.env) legitimately contain
|
|
# plaintext API keys on the live host. Templates with ${PLACEHOLDER} markers
|
|
# belong in git; rendered files with real values never do.
|
|
#
|
|
# Usage:
|
|
# scripts/verify-no-secrets.sh # scan staged changes (pre-commit)
|
|
# scripts/verify-no-secrets.sh --all # scan every tracked file
|
|
#
|
|
# Install as a hook:
|
|
# ln -sf ../../scripts/verify-no-secrets.sh .git/hooks/pre-commit
|
|
# ---------------------------------------------------------------------------
|
|
set -uo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$REPO_ROOT" || exit 1
|
|
|
|
MODE="${1:-staged}"
|
|
|
|
if [ "$MODE" = "--all" ]; then
|
|
mapfile -t FILES < <(git ls-files)
|
|
else
|
|
mapfile -t FILES < <(git diff --cached --name-only --diff-filter=ACMR)
|
|
fi
|
|
|
|
if [ "${#FILES[@]}" -eq 0 ]; then
|
|
echo "verify-no-secrets: nothing to scan"
|
|
exit 0
|
|
fi
|
|
|
|
FAILED=0
|
|
|
|
fail() {
|
|
printf '\033[31mBLOCKED\033[0m %s\n %s\n' "$1" "$2" >&2
|
|
FAILED=1
|
|
}
|
|
|
|
# --- Rule 1: filenames that must never be committed ------------------------
|
|
FORBIDDEN_NAMES='(^|/)(models\.json|auth\.json|trust\.json|models-store\.json)$|(^|/)\.env$|\.env\.[^/]*$|(^|/)id_(ed25519|rsa)|\.(pem|p12)$|\.rendered(\..*)?$|(^|/)secrets/(?!.*\.(example|template)$)'
|
|
for f in "${FILES[@]}"; do
|
|
# .env.example / .env.template are allowed
|
|
case "$f" in
|
|
*.env.example|*.env.template|*.example|*.template) continue ;;
|
|
esac
|
|
if printf '%s' "$f" | grep -qP "$FORBIDDEN_NAMES"; then
|
|
fail "$f" "filename is on the never-commit list"
|
|
fi
|
|
done
|
|
|
|
# --- Rule 2: high-confidence credential content ----------------------------
|
|
# Only applied to text files. Placeholders are explicitly tolerated.
|
|
PLACEHOLDER='\$\{[A-Z_][A-Z0-9_]*\}|<[A-Z_][A-Z0-9_]*>|REDACTED|CHANGE_?ME|EXAMPLE|xxx+|\.\.\.'
|
|
|
|
declare -a PATTERNS=(
|
|
'sk-[A-Za-z0-9]{20,}' # OpenAI-style
|
|
'sk-ant-[A-Za-z0-9_-]{20,}' # Anthropic
|
|
'ghp_[A-Za-z0-9]{30,}' # GitHub PAT
|
|
'gho_[A-Za-z0-9]{30,}'
|
|
'github_pat_[A-Za-z0-9_]{30,}'
|
|
'AIza[A-Za-z0-9_-]{30,}' # Google
|
|
'xox[baprs]-[A-Za-z0-9-]{10,}' # Slack
|
|
'[0-9]{8,10}:AA[A-Za-z0-9_-]{30,}' # Telegram bot token
|
|
'tvly-[A-Za-z0-9]{20,}' # Tavily
|
|
'-----BEGIN [A-Z ]*PRIVATE KEY-----'
|
|
)
|
|
|
|
# key-ish assignment with a long opaque value
|
|
ASSIGN='(?i)(api[_-]?key|apikey|secret|token|password|passwd|access[_-]?key)["'"'"' ]*[:=]["'"'"' ]*[A-Za-z0-9/_+=-]{16,}'
|
|
|
|
for f in "${FILES[@]}"; do
|
|
[ -f "$f" ] || continue
|
|
# skip binaries
|
|
if ! grep -Iq . "$f" 2>/dev/null; then continue; fi
|
|
|
|
for pat in "${PATTERNS[@]}"; do
|
|
if hit=$(grep -nP "$pat" "$f" 2>/dev/null | head -1); then
|
|
[ -n "$hit" ] && fail "$f" "credential pattern: ${hit:0:120}"
|
|
fi
|
|
done
|
|
|
|
if hit=$(grep -nP "$ASSIGN" "$f" 2>/dev/null | grep -vP "$PLACEHOLDER" | head -1); then
|
|
[ -n "$hit" ] && fail "$f" "credential-like assignment: ${hit:0:120}"
|
|
fi
|
|
done
|
|
|
|
if [ "$FAILED" -ne 0 ]; then
|
|
cat >&2 <<'MSG'
|
|
|
|
verify-no-secrets: commit refused.
|
|
|
|
If a match is a false positive, either
|
|
- replace the value with a ${PLACEHOLDER} and keep the file as a *.template, or
|
|
- move the file outside the repository, or
|
|
- add a narrow exception to scripts/verify-no-secrets.sh with a comment
|
|
explaining why the value is not a credential.
|
|
|
|
Do not bypass with --no-verify.
|
|
MSG
|
|
exit 1
|
|
fi
|
|
|
|
echo "verify-no-secrets: ${#FILES[@]} file(s) scanned, clean"
|
|
exit 0
|