From 8922f763ef990d096478157781814d1da8fbd60e Mon Sep 17 00:00:00 2001 From: Jeremy McSpadden Date: Tue, 24 Mar 2026 14:34:25 -0500 Subject: [PATCH] ci(security): add base64-encoded directive scan to lint job (#2371) Adds scripts/base64-scan.sh and a corresponding CI step to detect prompt injection payloads that are base64-encoded to evade the existing docs-prompt-injection-scan.sh check. --- .github/workflows/ci.yml | 3 + scripts/base64-scan.sh | 242 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100755 scripts/base64-scan.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b76dc34cb..84a5fcb7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,9 @@ jobs: - name: Scan for hardcoded secrets run: bash scripts/secret-scan.sh --diff origin/main + - name: Scan for base64-encoded secrets + run: bash scripts/base64-scan.sh --diff origin/main + - name: Ensure .gsd/ is not checked in run: | if [ -d ".gsd" ]; then diff --git a/scripts/base64-scan.sh b/scripts/base64-scan.sh new file mode 100755 index 000000000..e79428430 --- /dev/null +++ b/scripts/base64-scan.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +# Base64 obfuscation scanner — extracts base64 blobs from changed files, +# decodes them, and checks decoded content for prompt injection patterns. +# +# Catches obfuscated directives that would bypass docs-prompt-injection-scan.sh, +# which only scans raw text in markdown files. +# +# Usage: +# scripts/base64-scan.sh # scan staged files (pre-commit mode) +# scripts/base64-scan.sh --diff origin/main # scan diff vs branch (CI mode) +# scripts/base64-scan.sh --file path # scan a specific file +# +# Works on macOS (BSD grep) and Linux (GNU grep) — uses only ERE patterns. + +set -euo pipefail + +RED='\033[0;31m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +IGNOREFILE=".base64scanignore" +EXIT_CODE=0 +FINDINGS=0 + +# Blobs shorter than this have too many false positives. +# 40 base64 chars decodes to ~30 bytes — minimum length for a meaningful directive. +MIN_BLOB_LEN=40 + +# ── Prompt injection patterns to match against decoded content ──────── +# Format: "Label:::flags:::regex" +# Mirrors the patterns in docs-prompt-injection-scan.sh but applied to +# base64-decoded content across all file types. +DECODED_PATTERNS=( + # System prompt markers + "System prompt marker:::i:::" + "System prompt marker:::i:::<\|im_start\|>system" + "System prompt marker:::i:::\[SYSTEM\][[:space:]]*:" + + # Role injection / override + "Role injection:::i:::you are now [a-z]" + "Instruction override:::i:::ignore (all )?previous instructions" + "Instruction override:::i:::ignore (all )?prior instructions" + "Instruction override:::i:::disregard (all )?(above|previous|prior)" + "Instruction override:::i:::forget (all )?(above|previous|prior) (instructions|context|rules)" + "Instruction override:::i:::new instructions:" + "Instruction override:::i:::override (all )?instructions" + "Instruction override:::i:::your new role is" + "Instruction override:::i:::from now on,? (you (are|will|must|should)|act as)" + + # Hidden HTML directives + "Hidden directive::::::