
How to create an autonomous vulnerability patching system using LM Studio 0.4.1, Devstral, and Claude CLI.
Enterprise AI security tools like Cogent Security just raised $42M to automate vulnerability management. Impressive. But what if you could build something similar for your own homelab — using open-source tools and local AI?
In this article, I'll show you how to create your own AI security agent that:
- Scans Docker containers, Proxmox VMs, and LXC containers for vulnerabilities
- Uses LM Studio 0.4.1 with Anthropic-compatible API — works natively with Claude CLI
- Runs Mistral's Devstral Small 2 model locally (68% on SWE-bench Verified)
- Costs $0 in API fees — everything runs on your hardware
This is the guide I wish existed when I started automating my homelab security.
What Changed in January 2026
LM Studio 0.4.1 (released January 29, 2026) introduced a game-changer: native Anthropic API compatibility.
This means:
- ✅ Claude CLI works directly with local models
- ✅ Use
/v1/messagesendpoint (same as Anthropic) - ✅ Drop-in replacement for Anthropic SDKs
- ✅ Streaming support (
message_start,content_block_delta,message_stop)
No more workarounds. No more OpenAI compatibility layer. Just native Claude integration with your local models.
The Stack
| Component | Purpose |
|---|---|
| LM Studio 0.4.1 | Local model server with Anthropic API |
| Devstral Small 2 | 24B parameter code-specialized model |
| Claude CLI | Agent interface |
| Trivy | Container vulnerability scanner |
| Grype | Alternative vulnerability scanner |
| proxmoxer | Proxmox Python API |
Part 1: Setting Up LM Studio 0.4.1+ with Anthropic API
Why LM Studio 0.4.1?
The January 29, 2026 release added native Anthropic API compatibility:
- Endpoint:
http://localhost:1234/v1/messages - Environment:
ANTHROPIC_BASE_URL=http://localhost:1234 - Auth:
ANTHROPIC_AUTH_TOKEN=lmstudio(or any string)
This makes LM Studio a drop-in replacement for Anthropic's cloud API.
Installation
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: Download installer from https://lmstudio.ai/
Installing LM Studio CLI
LM Studio 0.4.1+ includes the lms command-line tool:
# 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
Downloading Devstral Small 2 Model
# 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
Enabling the Anthropic-Compatible Server
# 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
Verify Server Status
lms ps
# Expected output:
# ✓ Server running on port 1234
# ✓ Model loaded: Devstral-Small-2-24B-Instruct-2512
# ✓ API format: Anthropic Messages
Testing the Anthropic-Compatible API
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"}]
}'
Part 2: Installing Claude CLI
Installation
# macOS, Linux, WSL
curl -fsSL https://claude.ai/install.sh | bash
# Verify
claude --version
Configuration
# 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"
Verify Connection
DEBUG=1 claude -p "What is 2+2?"
# Expected output includes:
# → Connecting to http://localhost:1234/v1/messages
# → Model: devstral-small-24b
Part 3: Vulnerability Scanning Setup
Trivy (Recommended for Docker)
# 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 (Alternative)
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 Scanner
#!/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)
Part 4: Building the AI Security Agent
#!/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()
Part 5: Using Claude CLI for Autonomous Patching
# 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."
Python wrapper:
#!/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"))
Part 6: Automated Scheduling
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 Notifications
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>")
Hardware Requirements
- Minimum: 16GB VRAM for Q4_K_M quantization
- Recommended: 32K context length (≈ 18-20GB VRAM total)
- Claude CLI works best with 25K+ context
Troubleshooting
LM Studio Server Issues
lms ps
lms server stop && lms server start --port 1234
Claude CLI Connection Issues
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 / Model Loading Issues
nvidia-smi # check VRAM
# Reduce context or quantization
lms load model-name --gpu 0.7 --context-length 16384
Key Takeaways
- LM Studio 0.4.1 has native Anthropic API support — no workarounds needed
- Claude CLI works directly with local models via
ANTHROPIC_BASE_URL - Devstral Small 2 scores 68% on SWE-bench Verified
- 16GB VRAM minimum for Q4_K_M quantization
- 25K+ context required for best Claude CLI performance
- Total API cost: $0 — everything runs on your hardware
I'm a senior architect specializing in AI/ML, Linux systems, and data centers. I help organizations build secure, automated infrastructure using open-source tools.
#AI #AIAgents #Cybersecurity #Homelab #Proxmox #Docker #LMStudio #Devstral #ClaudeCLI #Anthropic #OpenSource #DevSecOps #Automation