Add .ship-policy.json (opts out AI adversarial/red-team/security-specialist review; requires local operator sign-off) and test/run-security.sh (the operator-local security regression subset). Document the policy in CLAUDE.md and README, fix stale test counts (manifest.bats 41->58, total 174->191), and update the storage-recursion note to reflect rekey/list now walking the full project tree.
65 lines
2 KiB
Bash
Executable file
65 lines
2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Local-only security regression suite. Uses attack-payload fixtures on purpose.
|
|
# Do NOT ask hosted AI agents to run this script or to perform equivalent red-team review.
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
if ! command -v bats >/dev/null 2>&1; then
|
|
echo "bats-core is required: brew install bats-core" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Subset of the full suite: adversarial regressions + explicit SECURITY tests +
|
|
# closely related path/injection rails. Keeps the run focused and fast.
|
|
FILTER='SECURITY|F1:|F2:|F3:|F4:|F5:|command injection does not execute|outside HOME is refused|symlinked target is refused|shell metacharacters|command-substitution|symlinked .secrets-store is skipped|symlinked .secrets-files is ignored|symlinked .secrets.json is refused'
|
|
|
|
echo "Security regression suite (operator-local only)"
|
|
echo "Repository policy: see .ship-policy.json"
|
|
echo ""
|
|
|
|
bats --filter "$FILTER" test/
|
|
|
|
echo ""
|
|
echo "All filtered security regression tests passed."
|
|
echo ""
|
|
|
|
if [ ! -t 0 ]; then
|
|
echo "Refusing non-interactive sign-off. Re-run in a terminal and complete operator certification." >&2
|
|
exit 1
|
|
fi
|
|
|
|
read -r -p "Operator name: " OPERATOR
|
|
if [ -z "${OPERATOR//[[:space:]]/}" ]; then
|
|
echo "Operator name is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
read -r -p "Type SIGNOFF to certify you ran this suite locally: " CONFIRM
|
|
if [ "$CONFIRM" != "SIGNOFF" ]; then
|
|
echo "Sign-off aborted (expected exactly SIGNOFF)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
SIGNOFF_DIR="$ROOT/.gstack"
|
|
mkdir -p "$SIGNOFF_DIR"
|
|
COMMIT="$(git rev-parse HEAD 2>/dev/null || echo unknown)"
|
|
TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
SIGNOFF_FILE="$SIGNOFF_DIR/security-signoff.json"
|
|
|
|
# Escape operator name for JSON (minimal — names should not contain quotes).
|
|
OPERATOR_JSON="${OPERATOR//\\/\\\\}"
|
|
OPERATOR_JSON="${OPERATOR_JSON//\"/\\\"}"
|
|
|
|
cat >"$SIGNOFF_FILE" <<EOF
|
|
{
|
|
"operator": "$OPERATOR_JSON",
|
|
"signed_at": "$TS",
|
|
"commit": "$COMMIT",
|
|
"suite": "test/run-security.sh",
|
|
"filter": "$FILTER"
|
|
}
|
|
EOF
|
|
|
|
echo "Sign-off recorded at $SIGNOFF_FILE (gitignored — local only)."
|