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"
|
||||
work_dir: str = "/var/cache/aur-shield/build"
|
||||
build_user: str = "nobody"
|
||||
scan_only: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
+14
-1
@@ -53,6 +53,7 @@ async def status() -> dict[str, Any]:
|
||||
"ollama_url": cfg.ollama.url,
|
||||
"repo_dir": cfg.server.repo_dir,
|
||||
"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
|
||||
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(
|
||||
source.pkgbuild, package,
|
||||
cfg.server, cfg.build,
|
||||
|
||||
@@ -9,6 +9,7 @@ server:
|
||||
repo_dir: /var/cache/aur-shield/repo
|
||||
work_dir: /var/cache/aur-shield/build
|
||||
build_user: nobody # never build as root
|
||||
scan_only: true # if true, /api/build returns scan result without building
|
||||
|
||||
security:
|
||||
block_patterns:
|
||||
|
||||
+59
-27
@@ -276,10 +276,10 @@ for ioc in d.get('ioc_matches',[]):
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --scan-only mode
|
||||
SCAN_ONLY=false
|
||||
if [ "$1" = "--scan-only" ]; then
|
||||
SCAN_ONLY=true
|
||||
# --noinstall mode (scan only, don't build/install)
|
||||
NOINSTALL=false
|
||||
if [ "$1" = "--noinstall" ] || [ "$1" = "--scan-only" ]; then
|
||||
NOINSTALL=true
|
||||
shift
|
||||
fi
|
||||
|
||||
@@ -288,43 +288,75 @@ EXIT_CODE=0
|
||||
for pkg in "$@"; do
|
||||
echo "→ Processing $pkg..."
|
||||
|
||||
if [ "$SCAN_ONLY" = true ]; then
|
||||
RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>&1) || {
|
||||
echo " ✗ Failed to scan $pkg"
|
||||
echo " $RESPONSE"
|
||||
EXIT_CODE=1
|
||||
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
|
||||
# Always scan first (server handles IOC + regex + LLM)
|
||||
RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>&1) || {
|
||||
echo " ✗ Failed to scan $pkg"
|
||||
echo " $RESPONSE"
|
||||
EXIT_CODE=1
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
clean)
|
||||
echo " ✓ Clean — package verified"
|
||||
if [ "$SCAN_ONLY" = false ]; then
|
||||
echo " Installing via pacman..."
|
||||
sudo pacman -Sy "aur-shield/$pkg" || {
|
||||
echo " ⚠ Package not in repo yet — may need to wait for build"
|
||||
echo " ✓ Clean — verified by AUR-Shield"
|
||||
if [ "$NOINSTALL" = true ]; then
|
||||
echo " (scan only — not installing)"
|
||||
elif [ "$SERVER_SCAN_ONLY" = True ]; then
|
||||
# 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
|
||||
}
|
||||
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
|
||||
;;
|
||||
suspicious)
|
||||
echo " ⚠ Suspicious — review recommended"
|
||||
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
|
||||
if [ "$SCAN_ONLY" = false ]; then
|
||||
read -rp " Install anyway? [y/N] " FORCE
|
||||
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
|
||||
fi
|
||||
if [[ "${FORCE,,}" == "y" ]]; then
|
||||
sudo pacman -Sy "aur-shield/$pkg"
|
||||
if [ "$SERVER_SCAN_ONLY" = True ]; then
|
||||
yay -S "$pkg"
|
||||
else
|
||||
sudo pacman -Sy "aur-shield/$pkg"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user