Tilbake til forsiden
#AI#Agents#Security

Bygg ditt eget KI-sikkerhetsagent-hjemmelab

Toni Nowak
Bygg ditt eget KI-sikkerhetsagent-hjemmelab

Hvordan lage et autonomt system for oppdatering av sårbarheter ved hjelp av LM Studio 0.4.1, Devstral og Claude CLI.

Enterprise KI-sikkerhetsverktøy som Cogent Security hentet nettopp inn $42 millioner for å automatisere sårbarhetshåndtering. Imponerende. Men hva om du kunne bygge noe lignende for ditt eget hjemmelab — ved hjelp av åpen kildekode-verktøy og lokal KI?

I denne artikkelen viser jeg deg hvordan du lager din egen KI-sikkerhetsagent som:

  • Scanner Docker-containere, Proxmox-VM-er og LXC-containere for sårbarheter
  • Bruker LM Studio 0.4.1 med Anthropic-kompatibelt API — fungerer naturlig med Claude CLI
  • Kjører Mistral Devstral Small 2-modellen lokalt (68 % på SWE-bench Verified)
  • Koster $0 i API-avgifter — alt kjører på din maskinvare

Dette er guiden jeg ønsket eksisterte da jeg begynte å automatisere hjemmelab-sikkerheten min.

Hva endret seg i januar 2026

LM Studio 0.4.1 (utgitt 29. januar 2026) introduserte en spillveksler: naturlig Anthropic API-kompatibilitet.

Dette betyr:

  • ✅ Claude CLI fungerer direkte med lokale modeller
  • ✅ Bruk /v1/messages-endepunktet (samme som Anthropic)
  • ✅ Erstatning uten kodeendringer for Anthropic SDKer
  • ✅ Streaming-støtte (message_start, content_block_delta, message_stop)

Ingen mer omveier. Ingen mer OpenAI-kompatibilitets-lag. Bare naturlig Claude-integrasjon med dine lokale modeller.

Stakken

KomponentFormål
LM Studio 0.4.1Lokal modellserver med Anthropic API
Devstral Small 224B-parameter kodespesialisert modell
Claude CLIAgentgrensesnitt
TrivySkanner for containersårbarheter
GrypeAlternativ sårbarhetsskanner
proxmoxerProxmox Python API

Del 1: Sette opp LM Studio 0.4.1+ med Anthropic API

Hvorfor LM Studio 0.4.1?

Utgivelsen 29. januar 2026 la til naturlig Anthropic API-kompatibilitet:

  • Endepunkt: http://localhost:1234/v1/messages
  • Miljø: ANTHROPIC_BASE_URL=http://localhost:1234
  • Auth: ANTHROPIC_AUTH_TOKEN=lmstudio (eller en hvilken som helst streng)

Dette gjør LM Studio til en erstatning uten kodeendringer for Anthropics cloud-API.

Installasjon

Linux (AppImage):

# Last ned LM Studio 0.4.1+
wget https://releases.lmstudio.ai/linux/0.4.1/LM-Studio-0.4.1-x86_64.AppImage
chmod +x LM-Studio-0.4.1-x86_64.AppImage
./LM-Studio-0.4.1-x86_64.AppImage

macOS:

brew install --cask lm-studio

Windows: Last ned installasjonsprogram fra https://lmstudio.ai/

Installere LM Studio CLI

LM Studio 0.4.1+ inkluderer kommandolinjeverktøyet lms:

# Verifiser CLI-installasjon (etter å ha kjørt LM Studio GUI minst én gang)
lms --help

# Sjekk om server kjører
lms ps

# List opp lastede modeller
lms ls

Laste ned Devstral Small 2-modellen

# Installer Hugging Face Hub
pip install -U huggingface-hub

# Last ned Devstral Small 2 (Q4_K_M-kvantisering)
huggingface-cli download \
  unsloth/Devstral-Small-2-24B-Instruct-2512-GGUF \
  Devstral-Small-2-24B-Instruct-2512-Q4_K_M.gguf \
  --local-dir ~/.lmstudio/models

# Alternativ: Codestral-22B (mindre, raskere)
huggingface-cli download \
  lmstudio-community/Codestral-22B-v0.1-GGUF \
  Codestral-22B-v0.1-Q4_K_M.gguf \
  --local-dir ~/.lmstudio/models

Aktivere den Anthropic-kompatible serveren

# Last inn modell og start server
lms load unsloth/Devstral-Small-2-24B-Instruct-2512-GGUF \
  --gpu 1.0 \
  --context-length 32768

# Start server
lms server start --port 1234

Verifiser serverstatus

lms ps
# Forventet utdata:
# ✓ Server running on port 1234
# ✓ Model loaded: Devstral-Small-2-24B-Instruct-2512
# ✓ API format: Anthropic Messages

Teste det Anthropic-kompatible API-et

Python-test:

#!/usr/bin/env python3
from anthropic import Anthropic

def test_lm_studio():
    client = Anthropic(
        base_url="http://localhost:1234",
        api_key="lmstudio"
    )
    try:
        message = client.messages.create(
            model="devstral-small-24b",
            max_tokens=1024,
            messages=[{"role": "user", "content": "What is CVE-2024-3094?"}]
        )
        print("✅ Connection successful!")
        print(f"Response: {message.content[0].text}")
        return True
    except Exception as e:
        print(f"❌ Connection failed: {e}")
        return False

if __name__ == "__main__":
    test_lm_studio()

cURL-test:

curl http://localhost:1234/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: lmstudio" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "devstral-small-24b",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Explain CVE scanning in one paragraph"}]
  }'

Del 2: Installere Claude CLI

Installasjon

# macOS, Linux, WSL
curl -fsSL https://claude.ai/install.sh | bash

# Verifiser
claude --version

Konfigurasjon

# Legg til i ~/.bashrc eller ~/.zshrc
export ANTHROPIC_BASE_URL="http://localhost:1234"
export ANTHROPIC_AUTH_TOKEN="lmstudio"
source ~/.bashrc

Test Claude CLI

# Interaktiv økt
claude

# Enkelt kommando
claude "List all Python files and check for SQL injection vulnerabilities"

# Utskriftsmodus (spørring og avslutt)
claude -p "Review this Dockerfile for security issues"

Verifiser tilkobling

DEBUG=1 claude -p "What is 2+2?"
# Forventet utdata inkluderer:
# → Connecting to http://localhost:1234/v1/messages
# → Model: devstral-small-24b

Del 3: Oppsett av sårbarhetsskanning

Trivy (Anbefalt for Docker)

# Installer siste Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | \
  sh -s -- -b /usr/local/bin

# Skann et Docker-bilde
trivy image nginx:latest --format json --output trivy-report.json

# Skann alle kjørende containere
mkdir -p reports
docker ps --format '{{.Image}}' | while read img; do
  safe_name=$(echo "$img" | tr '/:' '_')
  trivy image "$img" --format json --output "reports/${safe_name}.json"
done

Grype (Alternativ)

curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | \
  sh -s -- -b /usr/local/bin

grype nginx:latest -o json > grype-report.json

Proxmox-skanner

#!/usr/bin/env python3
import json, os
from typing import List, Dict, Any, Optional
from proxmoxer import ProxmoxAPI

class ProxmoxScanner:
    def __init__(self, host: str, user: str,
                 password: Optional[str] = None, verify_ssl: bool = False):
        password = password or os.getenv('PROXMOX_PASSWORD')
        if not password:
            raise ValueError("Password required (set PROXMOX_PASSWORD env var)")
        self.proxmox = ProxmoxAPI(host, user=user, password=password, verify_ssl=verify_ssl)

    def scan_lxc_containers(self) -> List[Dict[str, Any]]:
        results = []
        for node in self.proxmox.nodes.get():
            node_name = node['node']
            for container in self.proxmox.nodes(node_name).lxc.get():
                if container['status'] != 'running':
                    continue
                vmid = container['vmid']
                try:
                    result = self.proxmox.nodes(node_name).lxc(vmid).exec.post(
                        command='apt list --upgradable 2>/dev/null | grep -i security'
                    )
                    results.append({
                        'type': 'lxc', 'node': node_name, 'vmid': vmid,
                        'name': container.get('name', f'CT-{vmid}'),
                        'security_updates': result
                    })
                except Exception as e:
                    print(f"Error scanning LXC {vmid}: {e}")
        return results

if __name__ == "__main__":
    scanner = ProxmoxScanner(
        host=os.getenv('PROXMOX_HOST', '192.168.1.100'),
        user=os.getenv('PROXMOX_USER', 'root@pam'),
        password=os.getenv('PROXMOX_PASSWORD')
    )
    results = scanner.scan_lxc_containers()
    with open('proxmox-scan-results.json', 'w') as f:
        json.dump(results, f, indent=2)

Del 4: Bygge KI-sikkerhetsagenten

#!/usr/bin/env python3
import json, subprocess, os
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any
from anthropic import Anthropic

class SecurityAgent:
    def __init__(self, lm_studio_url: str = "http://localhost:1234"):
        if not lm_studio_url.startswith(('http://', 'https://')):
            raise ValueError("Invalid LM Studio URL format")
        self.client = Anthropic(base_url=lm_studio_url, api_key="lmstudio")
        self.model = "devstral-small-24b"
        self.scans_dir = Path("scans")
        self.patches_dir = Path("patches")
        self.scans_dir.mkdir(parents=True, exist_ok=True)
        self.patches_dir.mkdir(parents=True, exist_ok=True)

    def call_devstral(self, system_prompt: str, user_message: str) -> str:
        try:
            message = self.client.messages.create(
                model=self.model, max_tokens=4096,
                system=system_prompt,
                messages=[{"role": "user", "content": user_message}]
            )
            return message.content[0].text
        except Exception as e:
            print(f"Error calling Devstral: {e}")
            return ""

    def scan_docker(self) -> List[Dict[str, Any]]:
        print("🔍 Scanning Docker containers...")
        result = subprocess.run(
            ["docker", "ps", "--format", "{{.Names}}\t{{.Image}}"],
            capture_output=True, text=True, check=False
        )
        containers = [
            {'name': parts[0], 'image': parts[1]}
            for line in result.stdout.strip().split('\n')
            if line and len(parts := line.split('\t')) == 2
        ]

        scan_results = []
        for container in containers:
            print(f"  Scanning {container['name']} ({container['image']})...")
            scan_file = self.scans_dir / f"docker_{container['name']}.json"
            subprocess.run(
                ["trivy", "image", container['image'],
                 "--format", "json", "--output", str(scan_file), "--quiet"],
                capture_output=True, check=False
            )
            if scan_file.exists():
                with open(scan_file) as f:
                    scan_data = json.load(f)
                vulnerabilities = [
                    {
                        'id': v.get('VulnerabilityID'),
                        'severity': v.get('Severity'),
                        'package': v.get('PkgName'),
                        'installed': v.get('InstalledVersion'),
                        'fixed': v.get('FixedVersion'),
                    }
                    for r in scan_data.get('Results', [])
                    for v in r.get('Vulnerabilities', [])
                ]
                scan_results.append({
                    'container': container['name'],
                    'image': container['image'],
                    'vulnerabilities': vulnerabilities
                })
        return scan_results

    def analyze_vulnerabilities(self, scan_results: List[Dict[str, Any]]) -> Dict[str, Any]:
        system_prompt = """You are a security expert AI. Analyze vulnerability scan results and
output a JSON remediation plan: {"summary":"...","critical_vulnerabilities":[...],"remediation_steps":[...],"automation_candidates":[...]}"""
        response = self.call_devstral(
            system_prompt,
            f"Analyze these scan results:\n\n{json.dumps(scan_results, indent=2)}"
        )
        try:
            start, end = response.find('{'), response.rfind('}') + 1
            if start >= 0 and end > start:
                return json.loads(response[start:end])
        except json.JSONDecodeError:
            pass
        return {"raw_analysis": response}

    def run_full_scan_and_patch_cycle(self) -> Dict[str, Any]:
        print("=" * 60)
        print("🤖 Homelab AI Security Agent - Starting Scan")
        print("=" * 60)

        scan_results = self.scan_docker()

        print("\n🧠 Analyzing vulnerabilities with Devstral...")
        analysis = self.analyze_vulnerabilities(scan_results)

        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        analysis_file = self.scans_dir / f"analysis_{timestamp}.json"
        with open(analysis_file, 'w') as f:
            json.dump(analysis, f, indent=2)

        print(f"\n✅ Analysis saved to {analysis_file}")
        return {'scan_results': scan_results, 'analysis': analysis}

if __name__ == "__main__":
    agent = SecurityAgent()
    agent.run_full_scan_and_patch_cycle()

Del 5: Bruke Claude CLI for autonom lapping

# Skann nginx og bruk patcher for CRITICAL/HIGH CVE-er
claude -p "Scan nginx container for vulnerabilities using trivy, then apply security patches if any HIGH or CRITICAL CVEs are found"

# Lag Proxmox-lappende spillebok
claude -p "Create an Ansible playbook that updates all security packages on Proxmox LXC containers. Include error handling and rollback capabilities."

Python-innpakning:

#!/usr/bin/env python3
import subprocess, os

def run_claude_agent(prompt: str, workdir: str = ".") -> str:
    env = os.environ.copy()
    env["ANTHROPIC_BASE_URL"] = "http://localhost:1234"
    env["ANTHROPIC_AUTH_TOKEN"] = "lmstudio"
    result = subprocess.run(
        ["claude", "-p", prompt],
        cwd=workdir, env=env, capture_output=True, text=True, check=False
    )
    return result.stdout

print(run_claude_agent("Scan docker-compose.yml for security issues"))

Del 6: Automatisert planlegging

Systemd Timer

# /etc/systemd/system/security-agent.service
[Unit]
Description=Homelab AI Security Agent
After=network.target docker.service

[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /opt/security-agent/main.py
WorkingDirectory=/opt/security-agent
Environment="ANTHROPIC_BASE_URL=http://localhost:1234"
Environment="ANTHROPIC_AUTH_TOKEN=lmstudio"
# /etc/systemd/system/security-agent.timer
[Unit]
Description=Run security agent daily at 6 AM

[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now security-agent.timer
sudo systemctl list-timers security-agent.timer

Telegram-varsler

import requests, os

def send_telegram_alert(message: str) -> bool:
    bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
    chat_id = os.getenv("TELEGRAM_CHAT_ID")
    if not bot_token or not chat_id:
        return False
    try:
        r = requests.post(
            f"https://api.telegram.org/bot{bot_token}/sendMessage",
            json={"chat_id": chat_id, "text": message, "parse_mode": "HTML"},
            timeout=10
        )
        return r.status_code == 200
    except Exception:
        return False

send_telegram_alert("🚨 <b>5 critical vulnerabilities found in nginx</b>")

Maskinvarekrav

  • Minimum: 16 GB VRAM for Q4_K_M-kvantisering
  • Anbefalt: 32K kontekstlengde (≈ 18-20 GB VRAM totalt)
  • Claude CLI fungerer best med 25K+ kontekst

Feilsøking

LM Studio-serverproblemer

lms ps
lms server stop && lms server start --port 1234

Claude CLI-tilkoblingsproblemer

echo $ANTHROPIC_BASE_URL   # should be http://localhost:1234
echo $ANTHROPIC_AUTH_TOKEN  # should be lmstudio
DEBUG=1 claude -p "test"
sudo lsof -i :1234           # check port

OOM / Problemer med innlasting av modell

nvidia-smi  # check VRAM
# Reduser kontekst eller kvantisering
lms load model-name --gpu 0.7 --context-length 16384

Viktige poeng

  • LM Studio 0.4.1 har naturlig Anthropic API-støtte — ingen omveier nødvendig
  • Claude CLI fungerer direkte med lokale modeller via ANTHROPIC_BASE_URL
  • Devstral Small 2 scorer 68 % på SWE-bench Verified
  • 16 GB VRAM minimum for Q4_K_M-kvantisering
  • 25K+ kontekst kreves for best Claude CLI-ytelse
  • Total API-kostnad: $0 — alt kjører på din maskinvare

Jeg er seniorarkitekt med spesialisering i KI/ML, Linux-systemer og datasentre. Jeg hjelper organisasjoner med å bygge sikker, automatisert infrastruktur ved hjelp av åpen kildekode-verktøy.

#KI #KIAgenter #Cybersikkerhet #Hjemmelab #Proxmox #Docker #LMStudio #Devstral #ClaudeCLI #Anthropic #OpenSource #DevSecOps #Automatisering