Powrót do strony głównej
#AI#Agents#Security

Zbuduj własne laboratorium agenta SI ds. bezpieczeństwa

Toni Nowak
Zbuduj własne laboratorium agenta SI ds. bezpieczeństwa

Jak stworzyć autonomiczny system łatania podatności przy użyciu LM Studio 0.4.1, Devstral i Claude CLI.

Enterprise'owe narzędzia SI ds. bezpieczeństwa, takie jak Cogent Security, właśnie zebrały 42 miliony dolarów na automatyzację zarządzania podatnościami. Imponujące. Ale co, gdybyś mógł zbudować coś podobnego dla własnego domowego laboratorium — używając narzędzi open-source i lokalnej SI?

W tym artykule pokażę ci, jak stworzyć własnego agenta SI ds. bezpieczeństwa, który:

  • Skanuje kontenery Docker, maszyny wirtualne Proxmox i kontenery LXC w poszukiwaniu podatności
  • Używa LM Studio 0.4.1 z Anthropic-kompatybilnym API — działa natywnie z Claude CLI
  • Uruchamia lokalnie model Devstral Small 2 firmy Mistral (68% na SWE-bench Verified)
  • Kosztuje 0 zł w opłatach za API — wszystko działa na twoim sprzęcie

To jest poradnik, który chciałbym mieć, gdy zaczynałem automatyzować bezpieczeństwo mojego homelaba.

Co się zmieniło w styczniu 2026

LM Studio 0.4.1 (wydany 29 stycznia 2026) wprowadził przełomową funkcję: natywną kompatybilność z Anthropic API.

Oznacza to:

  • ✅ Claude CLI działa bezpośrednio z lokalnymi modelami
  • ✅ Użycie endpointu /v1/messages (identycznego jak w Anthropic)
  • ✅ Drop-in replacement dla Anthropic SDK
  • ✅ Wsparcie dla streamingu (message_start, content_block_delta, message_stop)

Koniec z obejściami. Koniec z warstwą kompatybilności OpenAI. Czysta, natywna integracja Claude z twoimi lokalnymi modelami.

Stos technologiczny

KomponentPrzeznaczenie
LM Studio 0.4.1Lokalny serwer modeli z Anthropic API
Devstral Small 2Model specjalizowany w kodzie, 24B parametrów
Claude CLIInterfejs agenta
TrivySkaner podatności kontenerów
GrypeAlternatywny skaner podatności
proxmoxerProxmox Python API

Część 1: Konfiguracja LM Studio 0.4.1+ z Anthropic API

Dlaczego LM Studio 0.4.1?

Wydanie z 29 stycznia 2026 dodało natywną kompatybilność z Anthropic API:

  • Endpoint: http://localhost:1234/v1/messages
  • Środowisko: ANTHROPIC_BASE_URL=http://localhost:1234
  • Auth: ANTHROPIC_AUTH_TOKEN=lmstudio (lub dowolny ciąg znaków)

Sprawia to, że LM Studio jest drop-in replacement dla chmurowego API Anthropic.

Instalacja

Linux (AppImage):

# Download 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: Pobierz instalator z https://lmstudio.ai/

Instalacja CLI LM Studio

LM Studio 0.4.1+ zawiera narzędzie wiersza poleceń lms:

# Verify CLI installation (after running LM Studio GUI at least once)
lms --help

# Check if server is running
lms ps

# List loaded models
lms ls

Pobieranie modelu Devstral Small 2

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

# Download Devstral Small 2 (Q4_K_M quantization)
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

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

Włączanie serwera kompatybilnego z Anthropic

# Load model and 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

Weryfikacja statusu serwera

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

Testowanie Anthropic-kompatybilnego API

Test Python:

#!/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()

Test cURL:

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"}]
  }'

Część 2: Instalacja Claude CLI

Instalacja

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

# Verify
claude --version

Konfiguracja

# Add to ~/.bashrc or ~/.zshrc
export ANTHROPIC_BASE_URL="http://localhost:1234"
export ANTHROPIC_AUTH_TOKEN="lmstudio"
source ~/.bashrc

Test Claude CLI

# Interactive session
claude

# Single command
claude "List all Python files and check for SQL injection vulnerabilities"

# Print mode (query and exit)
claude -p "Review this Dockerfile for security issues"

Weryfikacja połączenia

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

Część 3: Konfiguracja skanowania podatności

Trivy (zalecany dla Dockera)

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

# Scan a Docker image
trivy image nginx:latest --format json --output trivy-report.json

# Scan all running containers
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 (alternatywa)

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

Skaner Proxmox

#!/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)

Część 4: Budowanie agenta SI ds. bezpieczeństwa

#!/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()

Część 5: Używanie Claude CLI do autonomicznego łatania

# Scan nginx and apply patches for CRITICAL/HIGH CVEs
claude -p "Scan nginx container for vulnerabilities using trivy, then apply security patches if any HIGH or CRITICAL CVEs are found"

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

Wrapper Python:

#!/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"))

Część 6: Automatyczne planowanie zadań

Timer Systemd

# /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

Powiadomienia Telegram

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>")

Wymagania sprzętowe

  • Minimum: 16 GB VRAM dla kwantyzacji Q4_K_M
  • Zalecane: kontekst 32K (≈ 18–20 GB VRAM łącznie)
  • Claude CLI działa najlepiej przy kontekście 25K+

Rozwiązywanie problemów

Problemy z serwerem LM Studio

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

Problemy z połączeniem Claude CLI

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

Problemy z OOM / ładowaniem modelu

nvidia-smi  # check VRAM
# Reduce context or quantization
lms load model-name --gpu 0.7 --context-length 16384

Kluczowe wnioski

  • LM Studio 0.4.1 ma natywne wsparcie Anthropic API — bez żadnych obejść
  • Claude CLI działa bezpośrednio z lokalnymi modelami poprzez ANTHROPIC_BASE_URL
  • Devstral Small 2 osiąga 68% na SWE-bench Verified
  • Minimum 16 GB VRAM dla kwantyzacji Q4_K_M
  • Kontekst 25K+ wymagany dla najlepszej wydajności Claude CLI
  • Całkowity koszt API: 0 zł — wszystko działa na twoim sprzęcie

Jestem architektem systemów specjalizującym się w AI/ML, systemach Linux i centrach danych. Pomagam organizacjom budować bezpieczną, zautomatyzowaną infrastrukturę przy użyciu narzędzi open source.

#SI #AgenciSI #Cyberbezpieczeństwo #Homelab #Proxmox #Docker #LMStudio #Devstral #ClaudeCLI #Anthropic #OpenSource #DevSecOps #Automatyzacja