mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
- UI audit (L6): allowlist bundled framework URLs in dist/ (React error URLs, W3C namespace URIs, Three.js credits, Google Fonts, Tailwind). These are embedded by npm deps during Vite build, not our code. - Binary strings (L2): skip URLs shorter than 15 chars — Windows binary has byte sequences that strings(1) interprets as "https://H9" etc. - Allow Google Fonts <link> in HTML (loaded by index.html for Inter/ JetBrains Mono fonts).
204 lines
8.5 KiB
Bash
Executable File
204 lines
8.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Layer 6: Graph UI security audit.
|
|
#
|
|
# Audits:
|
|
# A. Frontend asset scan (embedded JS/CSS/HTML or graph-ui/dist/)
|
|
# B. HTTP server binding (must be 127.0.0.1 only)
|
|
# C. RPC proxy scope (no system()/popen() in HTTP handler path)
|
|
# D. CORS check (no wildcard Access-Control-Allow-Origin)
|
|
#
|
|
# Usage: scripts/security-ui.sh
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
FAIL=0
|
|
|
|
# Use mktemp for all temp files (cross-platform safe)
|
|
SEC_TMPDIR=$(mktemp -d)
|
|
trap 'rm -rf "$SEC_TMPDIR"' EXIT
|
|
|
|
echo "=== Layer 6: Graph UI Security Audit ==="
|
|
|
|
# ── A. Frontend asset scan ───────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "--- A. Frontend asset scan ---"
|
|
|
|
# Check both built dist and embedded asset source
|
|
UI_DIRS=()
|
|
[[ -d "$ROOT/graph-ui/dist" ]] && UI_DIRS+=("$ROOT/graph-ui/dist")
|
|
[[ -d "$ROOT/graph-ui/src" ]] && UI_DIRS+=("$ROOT/graph-ui/src")
|
|
|
|
if [[ ${#UI_DIRS[@]} -eq 0 ]]; then
|
|
echo "SKIP: No graph-ui directory found."
|
|
else
|
|
for UI_DIR in "${UI_DIRS[@]}"; do
|
|
echo "Scanning: $UI_DIR"
|
|
|
|
# A1: No external domains in JS/CSS
|
|
echo " Checking for external domains..."
|
|
if find "$UI_DIR" -type f \( -name '*.js' -o -name '*.ts' -o -name '*.tsx' -o -name '*.css' \) -exec grep -lE 'https?://' {} \; 2>/dev/null | head -20 > "$SEC_TMPDIR/urls"; then
|
|
while IFS= read -r file; do
|
|
relfile="${file#"$ROOT/"}"
|
|
grep -onE 'https?://[^\s"'"'"')]+' "$file" 2>/dev/null | while IFS=: read -r lineno url; do
|
|
case "$url" in
|
|
http://localhost*|http://127.0.0.1*|https://localhost*|https://127.0.0.1*)
|
|
;; # OK — local dev/runtime
|
|
# Bundled framework URLs (embedded by npm deps in dist/ builds):
|
|
http://www.w3.org/*|https://www.w3.org/*)
|
|
;; # W3C XML/SVG/MathML namespace URIs
|
|
https://react.dev/*|https://reactjs.org/*)
|
|
;; # React error/docs URLs
|
|
https://github.com/*|https://cdn.jsdelivr.net/*)
|
|
;; # OSS library credits + CDN refs in bundled code
|
|
https://fonts.googleapis.com/*|https://fonts.gstatic.com/*)
|
|
;; # Google Fonts (loaded by index.html)
|
|
https://tailwindcss.com/*|https://tailwindc*)
|
|
;; # Tailwind CSS source annotations
|
|
https://jcgt.org/*|https://doc*)
|
|
;; # Academic/documentation refs in Three.js shaders
|
|
*)
|
|
echo " BLOCKED: ${relfile}:${lineno}: External URL: $url"
|
|
touch "$SEC_TMPDIR/fail_flag"
|
|
;;
|
|
esac
|
|
done
|
|
done < "$SEC_TMPDIR/urls"
|
|
fi
|
|
[[ -f "$SEC_TMPDIR/fail_flag" ]] && FAIL=1 && rm -f "$SEC_TMPDIR/fail_flag"
|
|
|
|
# A2: No external script/link loads in HTML
|
|
echo " Checking for external script/link loads..."
|
|
if find "$UI_DIR" -type f -name '*.html' -exec grep -lE '<script\s+src=|<link\s+href=' {} \; 2>/dev/null > "$SEC_TMPDIR/scripts"; then
|
|
while IFS= read -r file; do
|
|
relfile="${file#"$ROOT/"}"
|
|
if grep -nE '<script\s+src="https?://|<link\s+href="https?://' "$file" 2>/dev/null \
|
|
| grep -v 'localhost' | grep -v '127.0.0.1' \
|
|
| grep -v 'fonts.googleapis.com' | grep -v 'fonts.gstatic.com'; then
|
|
echo " BLOCKED: ${relfile}: External script/link load detected"
|
|
FAIL=1
|
|
fi
|
|
done < "$SEC_TMPDIR/scripts"
|
|
fi
|
|
|
|
# A3: No tracking/analytics
|
|
echo " Checking for tracking/analytics..."
|
|
TRACKING='google-analytics|gtag|mixpanel|segment\.com|hotjar|sentry\.io|plausible|posthog'
|
|
if find "$UI_DIR" -type f \( -name '*.js' -o -name '*.ts' -o -name '*.tsx' -o -name '*.html' \) \
|
|
-exec grep -lE "$TRACKING" {} \; 2>/dev/null > "$SEC_TMPDIR/track"; then
|
|
while IFS= read -r file; do
|
|
relfile="${file#"$ROOT/"}"
|
|
echo " BLOCKED: ${relfile}: Tracking/analytics reference found"
|
|
grep -nE "$TRACKING" "$file" | head -3
|
|
FAIL=1
|
|
done < "$SEC_TMPDIR/track"
|
|
fi
|
|
|
|
# A4: No hidden iframes
|
|
echo " Checking for iframes..."
|
|
if find "$UI_DIR" -type f -name '*.html' -exec grep -li '<iframe' {} \; 2>/dev/null > "$SEC_TMPDIR/iframe"; then
|
|
while IFS= read -r file; do
|
|
relfile="${file#"$ROOT/"}"
|
|
echo " BLOCKED: ${relfile}: iframe detected"
|
|
FAIL=1
|
|
done < "$SEC_TMPDIR/iframe"
|
|
fi
|
|
|
|
# A5: No eval/Function constructor in JS
|
|
echo " Checking for eval/Function constructor..."
|
|
if find "$UI_DIR" -type f \( -name '*.js' -o -name '*.ts' -o -name '*.tsx' \) \
|
|
-exec grep -nE '\beval\s*\(|new\s+Function\s*\(' {} \; 2>/dev/null | grep -v node_modules | grep -v '\.test\.' > "$SEC_TMPDIR/eval"; then
|
|
while IFS= read -r match; do
|
|
echo " REVIEW: eval/Function found: $match"
|
|
done < "$SEC_TMPDIR/eval"
|
|
fi
|
|
|
|
# A6: No WebSocket to external
|
|
echo " Checking for external WebSocket connections..."
|
|
if find "$UI_DIR" -type f \( -name '*.js' -o -name '*.ts' -o -name '*.tsx' \) \
|
|
-exec grep -nE 'wss?://' {} \; 2>/dev/null | grep -v 'localhost' | grep -v '127.0.0.1' > "$SEC_TMPDIR/ws"; then
|
|
while IFS= read -r match; do
|
|
echo " BLOCKED: External WebSocket: $match"
|
|
FAIL=1
|
|
done < "$SEC_TMPDIR/ws"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# ── B. HTTP server binding check ─────────────────────────────────
|
|
|
|
echo ""
|
|
echo "--- B. HTTP server binding check ---"
|
|
|
|
HTTP_SERVER="$ROOT/src/ui/http_server.c"
|
|
if [[ -f "$HTTP_SERVER" ]]; then
|
|
# Must bind to 127.0.0.1 only
|
|
if grep -q '127\.0\.0\.1' "$HTTP_SERVER"; then
|
|
echo "OK: Server binds to 127.0.0.1"
|
|
else
|
|
echo "BLOCKED: No 127.0.0.1 binding found in http_server.c"
|
|
FAIL=1
|
|
fi
|
|
|
|
# Must NOT bind to 0.0.0.0 or INADDR_ANY
|
|
if grep -E '0\.0\.0\.0|INADDR_ANY|in6addr_any' "$HTTP_SERVER" | grep -v '^\s*//' | grep -v '^\s*\*' > /dev/null 2>&1; then
|
|
echo "BLOCKED: Server may bind to all interfaces (0.0.0.0/INADDR_ANY found)"
|
|
FAIL=1
|
|
else
|
|
echo "OK: No 0.0.0.0/INADDR_ANY binding"
|
|
fi
|
|
else
|
|
echo "SKIP: http_server.c not found"
|
|
fi
|
|
|
|
# ── C. RPC proxy scope check ─────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "--- C. RPC proxy scope check ---"
|
|
|
|
if [[ -f "$HTTP_SERVER" ]]; then
|
|
# The HTTP handler should not directly call system()/popen()
|
|
# (fork/execl for indexing is allowed as it's tracked)
|
|
if grep -n 'system(' "$HTTP_SERVER" | grep -v '^\s*//' | grep -v '^\s*\*' > /dev/null 2>&1; then
|
|
echo "BLOCKED: system() call found in HTTP server (use subprocess instead)"
|
|
FAIL=1
|
|
else
|
|
echo "OK: No system() calls in HTTP handler"
|
|
fi
|
|
else
|
|
echo "SKIP: http_server.c not found"
|
|
fi
|
|
|
|
# ── D. CORS check ────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "--- D. CORS check ---"
|
|
|
|
if [[ -f "$HTTP_SERVER" ]]; then
|
|
if grep -E 'Allow-Origin:\s*\*' "$HTTP_SERVER" | grep -v '^\s*//' | grep -v '^\s*\*' > /dev/null 2>&1; then
|
|
echo "BLOCKED: CORS wildcard (Access-Control-Allow-Origin: *) found"
|
|
echo " This allows any website to access the local server."
|
|
FAIL=1
|
|
else
|
|
echo "OK: No CORS wildcard found"
|
|
fi
|
|
|
|
# Check that CORS reflects localhost origins
|
|
if grep -q 'localhost' "$HTTP_SERVER" && grep -q 'update_cors\|Access-Control-Allow-Origin' "$HTTP_SERVER"; then
|
|
echo "OK: CORS appears to validate localhost origins"
|
|
fi
|
|
else
|
|
echo "SKIP: http_server.c not found"
|
|
fi
|
|
|
|
# ── Summary ──────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
if [[ $FAIL -ne 0 ]]; then
|
|
echo "=== UI SECURITY AUDIT FAILED ==="
|
|
exit 1
|
|
fi
|
|
|
|
echo "=== UI security audit passed ==="
|