95 lines
4.1 KiB
Bash
Executable File
95 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# This repository is public, and server/ describes real infrastructure. The
|
|
# rule that makes that safe has three parts, and each is pinned here because
|
|
# each fails silently:
|
|
#
|
|
# 1. No tracked file under server/ carries a secret. Compose files reference
|
|
# secrets as ${VAR} interpolations resolved from the .env beside them on
|
|
# the machine; .env.example names the variables with CHANGE_ME in place
|
|
# of every value that matters.
|
|
# 2. .gitignore keeps .env and data/ out of server/containers/ even when a
|
|
# cutover or a mistake puts one there. The live files belong in
|
|
# ~/Server/<Name>/, outside the checkout entirely -- the ignore is a
|
|
# seatbelt, and a seatbelt that got deleted should fail loudly.
|
|
# 3. Nothing named .env is tracked, full stop.
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
server_dir="$repo_dir/server"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
[[ -d "$server_dir" ]] || { printf 'compose secrets contract: no server/ directory\n' >&2; exit 1; }
|
|
|
|
# ── 1. Scanner fixtures and tracked content ──────────────────────────────────
|
|
|
|
scanner="$repo_dir/tests/server/scan-tracked-secrets.py"
|
|
fixtures_dir="$repo_dir/tests/server/fixtures/secrets"
|
|
|
|
if ! python3 "$scanner" "$fixtures_dir/clean" \
|
|
compose.yml .env.example README.md signature-near-misses.txt; then
|
|
note 'the clean secret-scanning fixture was rejected'
|
|
fi
|
|
|
|
expect_leak() {
|
|
local fixture="$1"
|
|
local expected="$2"
|
|
local output
|
|
local status
|
|
|
|
output="$(python3 "$scanner" "$fixtures_dir/leaked" "$fixture" 2>&1)"
|
|
status=$?
|
|
if (( status != 1 )); then
|
|
note "the leaked $fixture fixture exited $status instead of 1"
|
|
elif [[ "$output" != "$expected" ]]; then
|
|
note "the leaked $fixture fixture reported '$output' instead of '$expected'"
|
|
fi
|
|
}
|
|
|
|
expect_leak compose.yml 'compose.yml:4: POSTGRES_PASSWORD'
|
|
expect_leak .env.example '.env.example:1: API_TOKEN'
|
|
expect_leak plain-list.yml 'plain-list.yml:4: API_TOKEN'
|
|
expect_leak quoted-mapping.yml 'quoted-mapping.yml:4: API_TOKEN'
|
|
expect_leak quoted-list.yml 'quoted-list.yml:4: API_TOKEN'
|
|
expect_leak pem-private-key.txt 'pem-private-key.txt:1: private key'
|
|
expect_leak anthropic-token.txt 'anthropic-token.txt:1: provider token'
|
|
expect_leak github-token.txt 'github-token.txt:1: provider token'
|
|
expect_leak slack-token.txt 'slack-token.txt:1: provider token'
|
|
|
|
mapfile -t tracked_server_files < <(git -C "$repo_dir" ls-files 'server/**' 'server/*')
|
|
if ! output="$(python3 "$scanner" "$repo_dir" "${tracked_server_files[@]}" 2>&1)"; then
|
|
while IFS= read -r hit; do
|
|
[[ -n "$hit" ]] && note "$hit"
|
|
done <<< "$output"
|
|
fi
|
|
|
|
# ── 2. The ignore still stands ───────────────────────────────────────────────
|
|
#
|
|
# check-ignore consults the real gitignore for a path that need not exist, so
|
|
# this asserts the rule rather than the current absence of violations.
|
|
|
|
git -C "$repo_dir" check-ignore -q 'server/containers/AnyService/.env' \
|
|
|| note '.gitignore no longer covers .env under server/containers/'
|
|
git -C "$repo_dir" check-ignore -q 'server/containers/AnyService/data/dump.sql' \
|
|
|| note '.gitignore no longer covers data/ under server/containers/'
|
|
|
|
# ── 3. No .env is tracked ────────────────────────────────────────────────────
|
|
|
|
while IFS= read -r tracked; do
|
|
note "a live .env is tracked: $tracked"
|
|
done < <(git -C "$repo_dir" ls-files 'server/**/.env' 'server/.env')
|
|
|
|
# ── Report ───────────────────────────────────────────────────────────────────
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
|
|
printf 'compose secrets contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'compose secrets contract: PASS\n'
|