feat: scan-only server mode + local build on client
Server: - config: server.scan_only flag (default: false) - /api/build returns scan_only=true without building when enabled - /api/status reports scan_only mode Client (safe-yay): - Detects server scan_only from API response - Scan-only server: builds locally with yay/paru after clean scan - Full server: installs from pacman repo as before - --noinstall flag for scan-only without build (was --scan-only) - Suspicious: prompts for local build or repo install depending on server mode
This commit is contained in:
@@ -23,6 +23,7 @@ class ServerConfig:
|
|||||||
repo_dir: str = "/var/cache/aur-shield/repo"
|
repo_dir: str = "/var/cache/aur-shield/repo"
|
||||||
work_dir: str = "/var/cache/aur-shield/build"
|
work_dir: str = "/var/cache/aur-shield/build"
|
||||||
build_user: str = "nobody"
|
build_user: str = "nobody"
|
||||||
|
scan_only: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
+14
-1
@@ -53,6 +53,7 @@ async def status() -> dict[str, Any]:
|
|||||||
"ollama_url": cfg.ollama.url,
|
"ollama_url": cfg.ollama.url,
|
||||||
"repo_dir": cfg.server.repo_dir,
|
"repo_dir": cfg.server.repo_dir,
|
||||||
"cached_scans": len(cache.list_all()),
|
"cached_scans": len(cache.list_all()),
|
||||||
|
"scan_only": cfg.server.scan_only,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -148,7 +149,19 @@ async def build_endpoint(package: str) -> dict[str, Any]:
|
|||||||
# Allow suspicious but warn
|
# Allow suspicious but warn
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 5. Build
|
# 5. Build (skip if scan_only mode)
|
||||||
|
if cfg.server.scan_only:
|
||||||
|
return {
|
||||||
|
"package": package,
|
||||||
|
"verdict": verdict,
|
||||||
|
"confidence": confidence,
|
||||||
|
"findings": findings,
|
||||||
|
"scan_only": True,
|
||||||
|
"build_success": False,
|
||||||
|
"message": "Scan-only mode — build locally on client",
|
||||||
|
}
|
||||||
|
|
||||||
|
# 6. Build
|
||||||
build_result = build_package(
|
build_result = build_package(
|
||||||
source.pkgbuild, package,
|
source.pkgbuild, package,
|
||||||
cfg.server, cfg.build,
|
cfg.server, cfg.build,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ server:
|
|||||||
repo_dir: /var/cache/aur-shield/repo
|
repo_dir: /var/cache/aur-shield/repo
|
||||||
work_dir: /var/cache/aur-shield/build
|
work_dir: /var/cache/aur-shield/build
|
||||||
build_user: nobody # never build as root
|
build_user: nobody # never build as root
|
||||||
|
scan_only: true # if true, /api/build returns scan result without building
|
||||||
|
|
||||||
security:
|
security:
|
||||||
block_patterns:
|
block_patterns:
|
||||||
|
|||||||
+51
-19
@@ -276,10 +276,10 @@ for ioc in d.get('ioc_matches',[]):
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --scan-only mode
|
# --noinstall mode (scan only, don't build/install)
|
||||||
SCAN_ONLY=false
|
NOINSTALL=false
|
||||||
if [ "$1" = "--scan-only" ]; then
|
if [ "$1" = "--noinstall" ] || [ "$1" = "--scan-only" ]; then
|
||||||
SCAN_ONLY=true
|
NOINSTALL=true
|
||||||
shift
|
shift
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -288,45 +288,77 @@ EXIT_CODE=0
|
|||||||
for pkg in "$@"; do
|
for pkg in "$@"; do
|
||||||
echo "→ Processing $pkg..."
|
echo "→ Processing $pkg..."
|
||||||
|
|
||||||
if [ "$SCAN_ONLY" = true ]; then
|
# Always scan first (server handles IOC + regex + LLM)
|
||||||
RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>&1) || {
|
RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>&1) || {
|
||||||
echo " ✗ Failed to scan $pkg"
|
echo " ✗ Failed to scan $pkg"
|
||||||
echo " $RESPONSE"
|
echo " $RESPONSE"
|
||||||
EXIT_CODE=1
|
EXIT_CODE=1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
else
|
|
||||||
RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/build/$pkg" 2>&1) || {
|
|
||||||
echo " ✗ Failed to process $pkg"
|
|
||||||
echo " $RESPONSE"
|
|
||||||
EXIT_CODE=1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERDICT=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('verdict','error'))" 2>/dev/null)
|
VERDICT=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('verdict','error'))" 2>/dev/null)
|
||||||
|
SERVER_SCAN_ONLY=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('scan_only',False))" 2>/dev/null)
|
||||||
|
|
||||||
case "$VERDICT" in
|
case "$VERDICT" in
|
||||||
clean)
|
clean)
|
||||||
echo " ✓ Clean — package verified"
|
echo " ✓ Clean — verified by AUR-Shield"
|
||||||
if [ "$SCAN_ONLY" = false ]; then
|
if [ "$NOINSTALL" = true ]; then
|
||||||
echo " Installing via pacman..."
|
echo " (scan only — not installing)"
|
||||||
sudo pacman -Sy "aur-shield/$pkg" || {
|
elif [ "$SERVER_SCAN_ONLY" = True ]; then
|
||||||
echo " ⚠ Package not in repo yet — may need to wait for build"
|
# Scan-only server — build locally
|
||||||
|
echo " Building locally..."
|
||||||
|
if command -v yay &>/dev/null; then
|
||||||
|
yay -S "$pkg" --noconfirm 2>&1 || {
|
||||||
|
echo " ⚠ Build failed"
|
||||||
EXIT_CODE=1
|
EXIT_CODE=1
|
||||||
}
|
}
|
||||||
|
elif command -v paru &>/dev/null; then
|
||||||
|
paru -S "$pkg" --noconfirm 2>&1 || {
|
||||||
|
echo " ⚠ Build failed"
|
||||||
|
EXIT_CODE=1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
echo " ⚠ No AUR helper (yay/paru) — cannot build"
|
||||||
|
EXIT_CODE=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Full server — install from repo
|
||||||
|
echo " Installing via pacman repo..."
|
||||||
|
sudo pacman -Sy "aur-shield/$pkg" || {
|
||||||
|
echo " ⚠ Not in repo — building locally..."
|
||||||
|
if command -v yay &>/dev/null; then
|
||||||
|
yay -S "$pkg"
|
||||||
|
else
|
||||||
|
echo " ⚠ yay not installed — cannot build"
|
||||||
|
EXIT_CODE=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
suspicious)
|
suspicious)
|
||||||
echo " ⚠ Suspicious — review recommended"
|
echo " ⚠ Suspicious — review recommended"
|
||||||
echo " Report: http://$SHIELD_HOST:$SHIELD_PORT/api/report/$pkg"
|
echo " Report: http://$SHIELD_HOST:$SHIELD_PORT/api/report/$pkg"
|
||||||
|
echo "$RESPONSE" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
d = json.loads(sys.stdin.read())
|
||||||
|
for f in d.get('findings',[])[:5]:
|
||||||
|
print(f\" {f}\")
|
||||||
|
" 2>/dev/null
|
||||||
EXIT_CODE=1
|
EXIT_CODE=1
|
||||||
if [ "$SCAN_ONLY" = false ]; then
|
if [ "$NOINSTALL" = false ]; then
|
||||||
|
if [ "$SERVER_SCAN_ONLY" = True ]; then
|
||||||
|
read -rp " Build locally anyway? [y/N] " FORCE
|
||||||
|
else
|
||||||
read -rp " Install anyway? [y/N] " FORCE
|
read -rp " Install anyway? [y/N] " FORCE
|
||||||
|
fi
|
||||||
if [[ "${FORCE,,}" == "y" ]]; then
|
if [[ "${FORCE,,}" == "y" ]]; then
|
||||||
|
if [ "$SERVER_SCAN_ONLY" = True ]; then
|
||||||
|
yay -S "$pkg"
|
||||||
|
else
|
||||||
sudo pacman -Sy "aur-shield/$pkg"
|
sudo pacman -Sy "aur-shield/$pkg"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
malicious)
|
malicious)
|
||||||
echo " ✗ MALICIOUS — package blocked!"
|
echo " ✗ MALICIOUS — package blocked!"
|
||||||
|
|||||||
Reference in New Issue
Block a user