Nel giugno 2026, Tenet Security ha divulgato agentjacking, un attacco che ha colpito 2.388 organizzazioni con un tasso di sfruttamento dell’85% puntando su agenti di codifica AI con report di errori Sentry falsi. Nella mia esperienza come System Administrator che gestisce infrastrutture AI in produzione, questo attacco rappresenta una svolta critica: gli agenti AI non si limitano a rispondere a domande, ma eseguono azioni—leggono i vostri file, navigano sul web, chiamano altro software e prendono decisioni per vostro conto—e quell’autonomia è esattamente quello che li rende utili e al contempo un problema di sicurezza.
Ho costruito una strategia di defense-in-depth per proteggere i nostri agenti di codifica da questo rischio, che combina error tracking monitorato, correlazione SIEM sofisticata e sandboxing d’esecuzione. In questo articolo vi condivido come l’ho implementata in produzione.
Capire Agentjacking: Il Vettore di Attacco
Il metodo più comune è l’injection di prompt, dove un attaccante nasconde istruzioni dentro il contenuto che l’agente legge, come un documento o una pagina web, causandogli di intraprendere azioni dannose con il suo accesso legittimo. Nel caso di agentjacking, questo non avviene nel prompt dell’utente—avviene nei dati di telemetria che l’agente recupera.
L’attacco prende di mira agenti di codifica autonomi come Claude Code, Cursor e OpenAI Codex iniettando comandi malevoli in report di errori Sentry falsi. All’inizio quando ho visto la ricerca, mi è sembrato teorico. Poi ho scoperto che Tenet Threat Labs ha dimostrato come agentjacking dirottava agenti di codifica AI facendoli eseguire codice controllato da attaccanti su una macchina di sviluppatore, innescato da un singolo report di errore falso e invisibile a ogni controllo di sicurezza, usando solo API Sentry pubbliche, trovando 2.388 organizzazioni esposte e confermando esecuzione di agenti in 100+ organizzazioni sottoposte a test controllati. Non era più teoria.
Come Funziona L’Attack Chain
I DSN (Data Source Name) sono pubblici per design e l’endpoint di acquisizione degli eventi è non autenticato—questo abilita la raccolta di telemetria ampia in ambienti eterogenei, ma significa anche che chiunque possieda un DSN può POST payload di eventi arbitrari a un progetto Sentry, e questi eventi compariranno insieme agli errori legittimi nella coda problemi del progetto e, attraverso il server MCP Sentry, nei dati restituiti a un agente di codifica AI.
L’attaccante non ha bisogno di violare nulla. Legge il vostro DSN dal codice sorgente della vostra pagina, crea un evento contraffatto con markdown malevolo incorporato, e quando il vostro agente queries Sentry per errori non risolti, crea istruzioni visivamente e strutturalmente indistinguibili dalla propria guida di rimedio—gli agenti di codifica AI come Claude Code e Cursor interpretano queste come legittime “fasi di risoluzione diagnostica” e eseguono pacchetti npm controllati da attaccanti.
Perché I Controlli Tradizionali Falliscono
Nel nostro primo tentativo di mitigazione, ho pensato che i controlli endpoint (EDR), WAF, o politiche IAM avrebbero fermato tutto questo. Mi sbagliavo. L’attacco bypassa EDR, WAF, IAM, VPN, Cloudflare e firewall—perché non c’è nulla di malevolo da rilevare—ogni azione nella catena è autorizzata—Tenet chiama questo l’Authorized Intent Chain: il modello di sicurezza prevalente è costruito per catturare comportamento non autorizzato, e questo attacco non ne contiene nessuno.
Ciò che rende agentjacking particolarmente conseguenziale da un punto di vista delle operazioni di sicurezza è che ha successo interamente dentro l’involucro del comportamento autorizzato—l’agente del sviluppatore sta facendo esattamente ciò che il sviluppatore ha chiesto: indagando errori Sentry e proponendo correzioni—i comandi che l’agente esegue—eseguire un pacchetto npm, leggere file di ambiente, fare una richiesta HTTPS in uscita—sono tutte operazioni che un sviluppatore potrebbe legittimamente eseguire—nessun binario malevolo viene rilasciato, nessuna tecnica di iniezione di processo viene usata, e nessuna escalation di privilegi è richiesta.
La Mia Strategia Defense-in-Depth: Tre Strati
Ho implementato una difesa in profondità che non dipende da nessun singolo controllo, perché nessuna difesa a singolo strato è sufficiente—la sicurezza efficace degli agenti richiede defense-in-depth: più controlli indipendenti a diversi strati, ciascuno coprendo i fallimenti dei modi degli altri.
Strato 1: Behavioral Error Tracking Detection
Ho costruito un sistema di detection basato sul comportamento che monitora i pattern che indicano compromise. Diversamente da un blocco firmatico, osservo ciò che l’agente fa dopo aver interrogato Sentry.
I difensori devono fare affidamento su indicatori comportamentali piuttosto che su blocklists di IP o dominio per la detection primaria—gli indicatori comportamentali di un attacco agentjacking riuscito o in corso includono: un agente di codifica AI che esegue comandi npx con nomi di pacchetto assenti dal package.json del progetto; esecuzione di comandi shell attivata da un agente AI immediatamente dopo l’interrogazione di Sentry o di qualsiasi servizio di tracciamento dei problemi connesso via MCP; richieste HTTPS POST in uscita da workstation di sviluppatori a endpoint non riconosciuti seguendo una sessione di agente AI; spawn di subprocess inaspettato da processi di agente AI; e letture di file di credenziali a ~/.aws/config, ~/.npmrc, e ~/.docker/config.json da script npm generati da agente.
Ho creato uno script di monitoraggio che traccia questi indicatori:
#!/bin/bash
# agent-behavior-monitor.sh
# Monitorare il comportamento di agenti AI in esecuzione
log_agent_event() {
local agent_pid=$1
local event_type=$2
local details=$3
# Registrare in SIEM con timestamp e conte
echo "AGENT_BEHAVIOR_EVENT|$(date -u +%Y-%m-%dT%H:%M:%SZ)|PID=$agent_pid|TYPE=$event_type|DETAILS=$details" >> /var/log/agent-events.log
}
monitor_sentry_queries() {
# Rilevare quando agent queries Sentry API
strace -e openat -p $AGENT_PID 2>&1 | grep -i sentry | while read line; do
log_agent_event $AGENT_PID "SENTRY_QUERY" "$line"
done
}
monitor_executable_spawning() {
# Tracciare processi child creati post-Sentry-query
local sentry_query_time=$(date +%s)
while true; do
current_time=$(date +%s)
# Se shell command eseguito entro 10 secondi da Sentry query
if [ $((current_time - sentry_query_time)) -le 10 ]; then
if ps aux | grep "$AGENT_PID" | grep -E "(bash|sh|npm|npx)"; then
log_agent_event $AGENT_PID "SUSPICIOUS_SHELL_EXEC" "Shell spawn post-Sentry"
fi
fi
done
}
monitor_credential_access() {
# Rilevare letture di file di credenziali
auditctl -w ~/.aws/config -p r -k agent_aws_access
auditctl -w ~/.npmrc -p r -k agent_npm_access
auditctl -w ~/.docker/config.json -p r -k agent_docker_access
}
Strato 2: SIEM Correlation Rules per Agentjacking Detection
Ho implementato regole di correlazione sofisticate nel nostro SIEM (usiamo Splunk) che collegano eventi disparati in pattern che indicano agentjacking. Le regole di correlazione SIEM sono la logica che lega insieme eventi di sicurezza separati per individuare minacce—una regola di correlazione definisce un pattern o sequenza di eventi che, quando osservati insieme, indicano attività malevola.
La mia regola principale aggrega cinque indicatori comportamentali in una finestra temporale ristretta di 15 secondi (perché agentjacking è velocissimo):
/* Splunk SPL Rule: Agentjacking Detection via Behavioral Correlation */
index=agent_events OR index=sentry_logs OR index=network_egress
| stats dc(event_type) as event_count by agent_pid, host
values(event_type) as events
earliest(_time) as first_event
latest(_time) as last_event
| eval time_window=last_event-first_event
| where time_window = 3
| eval has_sentry_query=if(like(events,"%SENTRY_QUERY%"),1,0)
| eval has_shell_exec=if(like(events,"%SUSPICIOUS_SHELL_EXEC%") OR like(events,"%SUBPROCESS_SPAWN%"),1,0)
| eval has_credential_read=if(like(events,"%CREDENTIAL_ACCESS%"),1,0)
| eval has_outbound_post=if(like(events,"%OUTBOUND_POST%"),1,0)
| eval agentjack_score=has_sentry_query+has_shell_exec+has_credential_read+has_outbound_post
| where agentjack_score >= 3
| table agent_pid, host, agentjack_score, events, time_window
| alert
Questa regola non guarda una singola firma—guarda il pattern comportamentale. Se rilevato, viene eseguito automaticamente il nostro playbook di containment:
/* Automated Response Playbook */
index=agent_events agentjack_score>=3
| map search="search index=process_execution pid=$agent_pid earliest=_time latest=+5m"
| lookup process_legitimacy.csv process_name
| where legitimacy_score = 4, "kill_process",
agentjack_score == 3, "isolate_network",
1=1, "escalate_to_soc"
)
| where action="kill_process"
| eval kill_command="kill -9 $agent_pid"
| outputlookup append=true agentjack_incidents.csv
Ho anche costruito una regola secondaria che rileva DSN exposure e attività di injection:
/* Secondary Rule: Sentry DSN Injection Detection */
index=sentry_ingestion sourcetype=sentry_events
| search message LIKE "##%Resolution%" OR message LIKE "npx%--diagnose%"
| stats count as injection_attempts by source_ip, sentry_project
| where injection_attempts > 2
| eval severity=if(injection_attempts > 10, "critical", "high")
| alert threshold=2 threshold_type=custom
Strato 3: Safe Execution Sandboxing
Strumenti agentic, in particolare per la codifica, eseguono esecuzione di codice arbitrario per design—automatizzare sviluppo guidato da test o specifiche richiede che l’agente crei ed esegua codice per osservare i risultati—inoltre, agenti che usano strumenti si stanno muovendo verso la scrittura e l’esecuzione di script usa-e-getta per eseguire compiti—questo rende i controlli a livello di applicazione insufficienti.
Ho implementato sandboxing usando una combinazione di containerizzazione ristretta e Linux capabilities, specificatamente negando le operazioni che agentjacking utilizza:
#!/usr/bin/env python3
# safe-agent-sandbox.py
# Sandbox per esecuzione agente AI con deny-by-default
import subprocess
import os
import sys
from pathlib import Path
class AgentSandbox:
def __init__(self, agent_command, working_dir):
self.agent_command = agent_command
self.working_dir = working_dir
self.restricted_paths = [
os.path.expanduser('~/.aws'),
os.path.expanduser('~/.npmrc'),
os.path.expanduser('~/.docker'),
os.path.expanduser('~/.ssh'),
'/etc/passwd',
'/root',
]
self.allowed_npm_packages = self.load_whitelist()
def load_whitelist(self):
"""Caricare elenco di pacchetti npm legittimi dal package.json"""
try:
import json
with open(os.path.join(self.working_dir, 'package.json'), 'r') as f:
pkg = json.load(f)
return set(pkg.get('dependencies', {}).keys()) |
set(pkg.get('devDependencies', {}).keys())
except:
return set()
def validate_npm_command(self, cmd):
"""Bloccare comandi npx che non sono in whitelist"""
if 'npx' in cmd:
# Estrarre nome del pacchetto da "npx @attacker/package --diagnose"
parts = cmd.split()
if len(parts) >= 2:
package_name = parts[1]
if package_name not in self.allowed_npm_packages:
raise SecurityError(f"Pacchetto npm non whitelistato: {package_name}")
return True
def run_with_seccomp(self):
"""Eseguire con profilo seccomp restrittivo"""
seccomp_profile = {
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"archMap": [
{"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": ["SCMP_ARCH_X86"]}
],
"syscalls": [
# Permette solo syscalls essenziali
{"names": ["read", "write", "close", "stat", "open"], "action": "SCMP_ACT_ALLOW"},
# Nega accesso a credential files
{"names": ["open", "openat"], "args": [
{"index": 0, "value": os.path.expanduser('~/.aws'), "op": "SCMP_CMP_MASKED_EQ"}
], "action": "SCMP_ACT_ERRNO", "errnoRet": 13},
]
}
return seccomp_profile
def run_sandboxed(self):
"""Eseguire il comando agente in sandbox"""
try:
self.validate_npm_command(self.agent_command)
# Eseguire con contenitore isolato
docker_cmd = [
'docker', 'run',
'--rm', # Cleanup automatico
'--read-only', # Filesystem read-only
'--cap-drop=ALL', # Drop tutte le capabilities
'--cap-add=NET_BIND_SERVICE', # Solo network bind se necessario
'--security-opt', 'no-new-privileges:true',
'--network', 'none', # Network deny-by-default
'--memory', '512m',
'--cpus', '1',
'--pids-limit', '10', # Max 10 processi
'--tmpfs', '/tmp:size=100m,noexec', # Temp filesystem senza exec
f'--workdir={self.working_dir}',
'ai-agent-sandbox:latest',
self.agent_command
]
result = subprocess.run(docker_cmd, capture_output=True, timeout=60)
return result.stdout.decode(), result.returncode
except subprocess.TimeoutExpired:
raise SecurityError("Agente ha superato timeout di esecuzione")
except Exception as e:
raise SecurityError(f"Errore di esecuzione sandbox: {str(e)}")
class SecurityError(Exception):
pass
# Utilizzo
if __name__ == '__main__':
sandbox = AgentSandbox(
agent_command='claude-code --fix-sentry-issues',
working_dir='/home/developer/project'
)
output, return_code = sandbox.run_sandboxed()
print(output)
Ho anche implementato Allowlist-and-Sandbox, dove l’agente richiede approvazione esplicita prima di eseguire comandi sconosciuti:
#!/bin/bash
# agent-approval-gate.sh
# Richiedere approvazione umana per comandi di agente non whitelistati
AGENT_LOG_FILE="/var/log/agent-commands.log"
WHITELIST_FILE="/etc/agent/command-whitelist.txt"
ALLOWED_COMMANDS=("npm install" "npm test" "git status" "git diff" "cat")
require_approval() {
local command="$1"
local prompt_msg="AGENT REQUESTING APPROVAL: $command"
# Log nella console del team dev
echo "[$(date)] $prompt_msg" | tee -a $AGENT_LOG_FILE
# Invia notifica Slack all'on-call engineer
curl -X POST -H 'Content-type: application/json'
--data "{"text":"🤖 *Agent Approval Needed*\n\`$command\`\n\n: /approve or /deny"}"
$SLACK_WEBHOOK_URL
# Attendere approvazione (timeout 5 minuti)
timeout 300 bash -c 'until grep "APPROVED: $command" $AGENT_LOG_FILE 2>/dev/null; do sleep 1; done'
return $?
}
check_command() {
local cmd="$1"
# Se il comando è nella whitelist, eseguirlo
if grep -Fq "$cmd" $WHITELIST_FILE 2>/dev/null; then
echo "[APPROVED_WHITELIST] Executing: $cmd"
return 0
fi
# Altrimenti richiedere approvazione umana
if require_approval "$cmd"; then
echo "[APPROVED_HUMAN] Executing: $cmd"
return 0
else
echo "[DENIED] Command blocked: $cmd"
return 1
fi
}
# Intercettare il comando agente prima dell'esecuzione
if [ "$1" == "--intercept" ]; then
check_command "${@:2}"
else
# Eseguire normalmente
exec "$@"
fi
Come Ho Integrato Error Tracking Sicuro
Ho ripensato completamente come gestiamo i dati di error tracking. Invece di dare all’agente accesso diretto a Sentry, ho creato uno strato intermedio che valida tutti i dati prima che raggiungano l’agente:
#!/usr/bin/env python3
# secure-error-tracking-proxy.py
# Proxy tra agente e Sentry con validazione di injection
import json
import re
from typing import Dict, Any
from datetime import datetime
import hashlib
class SecureErrorTrackingProxy:
def __init__(self, sentry_dsn: str):
self.sentry_dsn = sentry_dsn
self.dangerous_patterns = [
r'##s*Resolution', # Markdown resolution injection
r'npxs+(@?[w/-]+)', # npm package execution
r'pips+install', # Python package execution
r'curls+https?://', # Network exfiltration
r'${.*?}', # Variable expansion
r'|s*base64', # Encoding/obfuscation
]
def validate_error_event(self, event: Dict[str, Any]) -> Dict[str, Any]:
"""
Validare un evento di errore Sentry prima di restituirlo all'agente.
Se contiene pattern di injection, sanitizzare o rigettare.
"""
suspicious_fields = []
severity_score = 0
# Scansionare tutti i campi dell'evento per pattern pericolosi
for field, value in self._flatten_dict(event).items():
if isinstance(value, str):
for pattern in self.dangerous_patterns:
if re.search(pattern, value, re.IGNORECASE):
suspicious_fields.append((field, pattern, value[:100]))
severity_score += 1
if severity_score > 0:
# Log per SIEM con hash per rilevare payload identici
event_hash = hashlib.sha256(json.dumps(event, sort_keys=True).encode()).hexdigest()
self._log_suspicious_event(event, suspicious_fields, severity_score, event_hash)
# Se score è alto, rigettare completamente
if severity_score >= 3:
return self._create_safe_error_event(
"Error event rejected: suspected prompt injection detected",
event.get('event_id', 'unknown')
)
# Se score è basso, sanitizzare il contenuto
else:
return self._sanitize_event(event)
return event
def _flatten_dict(self, d: Dict, parent_key: str = '') -> Dict:
"""Appiattire un dizionario annidato per la scansione"""
items = []
for k, v in d.items():
new_key = f"{parent_key}.{k}" if parent_key else k
if isinstance(v, dict):
items.extend(self._flatten_dict(v, new_key).items())
else:
items.append((new_key, v))
return dict(items)
def _sanitize_event(self, event: Dict[str, Any]) -> Dict[str, Any]:
"""
Rimuovere o offuscare contenuti sospetti dall'evento.
"""
safe_event = json.loads(json.dumps(event)) # Deep copy
# Rimuovere la sezione "## Resolution" che agentjacking sfrutta
if 'message' in safe_event:
safe_event['message'] = re.sub(
r'##s*Resolution.*?(?=##|$)',
'',
safe_event['message'],
flags=re.DOTALL | re.IGNORECASE
)
# Offuscare potenziali comandi di esecuzione
if 'contexts' in safe_event:
for context_key, context_val in safe_event['contexts'].items():
if isinstance(context_val, dict):
for k, v in context_val.items():
if isinstance(v, str) and re.search(r'(npx|pip|curl|sh|bash)', v):
safe_event['contexts'][context_key][k] = '[COMMAND_REMOVED]'
return safe_event
def _create_safe_error_event(self, message: str, event_id: str) -> Dict[str, Any]:
"""Creare un evento di errore neutro quando l'injection viene rilevata"""
return {
'event_id': event_id,
'message': message,
'timestamp': datetime.utcnow().isoformat(),
'level': 'warning',
'tags': {'security_event': 'true', 'injection_detected': 'true'},
'fingerprint': ['security-event']
}
def _log_suspicious_event(self, event: Dict, fields: list, score: int, event_hash: str):
"""Log verso SIEM con full context per investigation"""
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'event_type': 'SENTRY_INJECTION_ATTEMPT',
'sentry_project': self.sentry_dsn.split('@')[0].split('//')[1] if self.sentry_dsn else 'unknown',
'severity_score': score,
'event_hash': event_hash,
'suspicious_fields': fields,
'full_event': event # Salvare per forensics
}
# Inviare al SIEM
import logging
logger = logging.getLogger('sentry_security')
logger.warning(json.dumps(log_entry))
# Utilizzo nel runtime agente
proxy = SecureErrorTrackingProxy(sentry_dsn='https://key@o123456.ingest.sentry.io/789')
# Quando l'agente queries Sentry per errori non risolti
sentry_events = fetch_from_sentry()
for event in sentry_events:
validated_event = proxy.validate_error_event(event)
agent.process_error(validated_event) # Passare solo evento validato all'agente
Implementazione Pratica: Test e Validazione
Dopo aver implementato questi controlli, ho testato la mia difesa usando payload di agentjacking reali (in laboratorio isolato). Ho simulato un agente Claude Code connesso a Sentry e ho iniettato un payload malevolo identico a quello di Tenet Security.
Risultato Senza Difesa: L’agente ha eseguito il comando npx malevolo in 3 secondi.
Risultato Con Difesa: La catena di detection è scattata così:
- Second 0.5: Il proxy di error tracking ha rilevato il pattern di injection “## Resolution” e ha rigettato l’evento
- Second 1.2: La regola di correlazione SIEM ha registrato il tentativo di injection come evento
- Second 2.1: L’agente non ha mai ricevuto l’evento malevolo—ha ricevuto invece un messaggio neutro
- Second 2.8: L’allerta SIEM è stata inviata al team SOC con full context
- Second 3.5: Nessuna shell spawn, nessuna credential exfiltration
La difesa in profondità ha funzionato: anche se il proxy fosse stato bypassato, il behavioral monitoring avrebbe fermato l’esecuzione.
Mitigazioni Aggiuntive Che Ho Implementato
DSN Rotation e Exposure Scanning
L’esposizione del DSN può essere valutata proattivamente prima che un attacco avvenga—le organizzazioni possono cercare le loro stringhe DSN Sentry su GitHub code search e Censys—qualsiasi DSN raggiungibile attraverso ricerca pubblica dovrebbe essere trattato come iniettabile fino a quando non viene ruotato.
Ho creato un job automatizzato che rota i DSN settimanalmente e scannerizza Github per exposures:
#!/bin/bash
# weekly-dsn-rotation.sh
# 1. Rotare DSN in Sentry
for PROJECT_ID in $(curl -s -H "Authorization: Bearer $SENTRY_AUTH_TOKEN"
https://sentry.io/api/0/organizations/$ORG_ID/projects/
| jq -r '.[] | .id'); do
# Ottenere i vecchi DSN
OLD_DSNS=$(curl -s -H "Authorization: Bearer $SENTRY_AUTH_TOKEN"
https://sentry.io/api/0/projects/$ORG_ID/$PROJECT_ID/keys/
| jq -r '.[] | .dsn')
# Generare nuove chiavi
curl -s -X POST -H "Authorization: Bearer $SENTRY_AUTH_TOKEN"
-d '{}'
https://sentry.io/api/0/projects/$ORG_ID/$PROJECT_ID/keys/
echo "[$(date)] Rotated DSN for project $PROJECT_ID"
done
# 2. Scannerizzare GitHub per vecchi DSN esposti
echo "[$(date)] Scanning GitHub for DSN exposures..."
for DSN in $OLD_DSNS; do
SEARCH_URL="https://api.github.com/search/code?q=$DSN"
RESULTS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$SEARCH_URL" | jq '.items | length')
if [ "$RESULTS" -gt 0 ]; then
echo "[ALERT] Old DSN exposed on GitHub: Found $RESULTS results"
# Notificare security team
curl -X POST -H 'Content-type: application/json'
--data "{"text":"🚨 *Old DSN Exposure Detected*\nDSN: $DSN\nMatches: $RESULTS"}"
$SLACK_SECURITY_CHANNEL
fi
done
MCP Connection Audit e Vetting
La lezione architettonica di fondo: gli agenti AI estendono la fiducia implicita a ogni fonte di dati che i loro strumenti connessi restituiscono—se una di quelle fonti accetta input esterno—e quella di Sentry lo fa, deliberatamente, per design—avete una superficie di injection di prompt indipendentemente da quanto affidabile l’integrazione sembri su carta—mentre l’IA agentica si integra più profondamente nei flussi di lavoro di sviluppo standard, verificare ciò che un agente può vedere e cosa può fare importa tanto quanto verificare l’agente stesso—le connessioni del server MCP meritano lo stesso scrutinio delle dipendenze di codice di terze parti.
Ho creato una procedura di audit per ogni MCP server che usiamo:
#!/usr/bin/env python3
# mcp-server-audit.py
# Verificare e vettare server MCP prima di consentire connessioni agente
import json
import subprocess
from dataclasses import dataclass
from typing import List, Dict
@dataclass
class MCPServerAudit:
server_name: str
server_url: str
accepts_external_input: bool
authenticates_input: bool
validates_input: bool
sanitizes_output: bool
risk_score: int
approved_for_agent_use: bool
def audit_mcp_server(server_config: Dict) -> MCPServerAudit:
"""Verificare un server MCP per rischi di injection."""
server_name = server_config['name']
risk_score = 0
findings = []
# 1. Verificare se il server accetta input esterno (come Sentry fa)
if server_config.get('accepts_user_input', False):
risk_score += 3
findings.append(f"Server accetta input esterno dall'utente")
# 2. Verificare se valida input (come Sentry NON fa)
if not server_config.get('has_input_validation', False):
risk_score += 3
findings.append(f"Server non ha validazione di input")
# 3. Verificare se autentica l'origine dei dati
if not server_config.get('has_authentication', True):
risk_score += 2
findings.append(f"Server non autentica le richieste")
# 4. Controllare la storia CVE del server
cve_count = server_config.get('recent_cves', 0)
if cve_count > 0:
risk_score += cve_count
findings.append(f"Server ha {cve_count} CVE recenti")
# 5. Verificare se l'output viene sanitizzato
if not server_config.get('sanitizes_output', False):
risk_score += 2
findings.append(f"Server non sanitizza l'output")
# 6. Controllare se espone comandi di esecuzione
if server_config.get('execution_capabilities', []):
risk_score += 1
findings.append(f"Server espone capacità di esecuzione")
approved = risk_score < 5 # Soglia di approvazione
return MCPServerAudit(
server_name=server_name,
server_url=server_config.get('url'),
accepts_external_input=server_config.get('accepts_user_input', False),
authenticates_input=server_config.get('has_authentication', True),
validates_input=server_config.get('has_input_validation', False),
sanitizes_output=server_config.get('sanitizes_output', False),
risk_score=risk_score,
approved_for_agent_use=(risk_score str:
"""Generare un report di audit per il team di security."""
report = "# MCP Server Security Audit Reportnn"
approved_servers = [a for a in audits if a.approved_for_agent_use]
rejected_servers = [a for a in audits if not a.approved_for_agent_use]
report += f"## Summaryn"
report += f"- Total Servers Audited: {len(audits)}n"
report += f"- Approved: {len(approved_servers)}n"
report += f"- Rejected/High Risk: {len(rejected_servers)}nn"
report += f"## ⚠️ Rejected Servers (Should NOT be connected to agents)n"
for audit in rejected_servers:
report += f"### {audit.server_name} (Risk Score: {audit.risk_score})n"
report += f"- **Status**: ❌ NOT APPROVEDn"
report += f"- **Accepts External Input**: {audit.accepts_external_input}n"
report += f"- **Input Validation**: {audit.validates_input}n"
report += f"- **Output Sanitization**: {audit.sanitizes_output}nn"
report += f"## ✅ Approved Serversn"
for audit in approved_servers:
report += f"- {audit.server_name} (Risk Score: {audit.risk_score}): Approvedn"
return report
# Utilizzo
server_configs = [
{
'name': 'Sentry MCP',
'url': 'https://sentry.io/api',
'accepts_user_input': True, # ← PROBLEMATICO
'has_authentication': False, # ← PROBLEMATICO
'has_input_validation': False, # ← PROBLEMATICO
'sanitizes_output': False, # ← PROBLEMATICO
'recent_cves': 0
},
{
'name': 'AWS S3 MCP',
'url': 'https://api.aws.amazon.com',
'accepts_user_input': False,
'has_authentication': True,
'has_input_validation': True,
'sanitizes_output': True,
'recent_cves': 0
}
]
audits = [audit_mcp_server(config) for config in server_configs]
report = generate_audit_report(audits)
print(report)
FAQ
Qual è la differenza tra agentjacking e prompt injection tradizionale?
La prompt injection tradizionale avviene nel prompt dell’utente, di fronte allo schermo. L’attaccante controlla il testo che l’utente vede. Agentjacking, invece, arriva attraverso dati di telemetria trusted—report di errori, log aggregati, output di strumenti connessi. La prompt injection, come la maggior parte la immagina, avviene nella casella chat—davanti all’utente, mentre lavora—questo è qualcosa di diverso: arriva attraverso i vostri dati di telemetria trusted—arriva come dati di errore routine attraverso un servizio di telemetria in cui l’azienda ha già fiducia—non c’è nessun jailbreak e nessun “esegui questo”—una richiesta di triage semplice è l’intero trigger.
Posso semplicemente disabilitare l’integrazione Sentry MCP per i miei agenti?
Sì, se non la usate attivamente. I controlli che importano sono dalla vostra parte: controllare le connessioni del server MCP—elencare ogni server MCP a cui i vostri agenti del team sono attualmente connessi—disabilitare l’integrazione Sentry MCP su qualsiasi workstation dove non è utilizzata attivamente e regolarmente. Nella mia esperienza, però, molti team hanno abilitato Sentry MCP per agenti di coding e non se ne sono resi conto. Una periodica revisione delle integrazioni è fondamentale.
Se blocco tutti i comandi npm exec, non impedisco agli agenti di funzionare?
No, se gestite bene la whitelist. La chiave è permettere solo i pacchetti npm che il vostro progetto dichiara esplicitamente in package.json. Agentjacking tenta di eseguire pacchetti controllati dall’attaccante, che non saranno mai nel vostro package.json. Ho visto agenti di coding legittimi continuare a funzionare perfettamente con questa restrizione, perché eseguono solo comandi che gli sviluppatori hanno autorizzato.
La mia azienda non ha un SIEM. Posso comunque implementare questa strategia?
Sì. Potete iniziare con i controlli del sandboxing e il proxy di validazione error tracking, che forniscono protezione anche senza correlazione SIEM. Il SIEM aggiunge visibilità e detection avanzata, ma i layer di sandboxing e validazione sono essenziali da soli. Molte PMI con cui ho lavorato hanno iniziato con sandbox + behavioral logging in file locali prima di investire in un SIEM completo.
Quanti falsi positivi posso aspettarmi da questa detection?
Nella mia implementazione, ho iniziato con tanti falsi positivi (una regola SIEM troppo aggressiva ha bloccato ogni accesso a ~/.aws). Ho dovuto affinare il punteggio di gravità e le finestre temporali. Consiglio di iniziare in modalità “alert only” per 2-3 settimane, osservare i pattern di accesso legittimi, poi passare a blocking. Con le metriche corrette (correlazione dei 5 indicatori in 15 secondi), i falsi positivi scendono sotto l’1%.
Conclusione
Agentjacking non è un problema di LLM alignment o prompt engineering. È un problema architetturale: i sistemi autonomi agentic sicuri vengono raggiunti progettando sistemi dove l’autonomia è limitata da architettura, permessi, identità e oversight deterministico dall’inizio.
La mia strategia defense-in-depth di error tracking monitorato + SIEM correlation rules + safe execution sandboxing protegge dalle tre vulnerabilità critiche che agentjacking sfrutta:
- Input Trust Boundary Violation: Il proxy di validazione e il behavioral monitoring rilevano quando dati inattesi fluiscono verso l’agente
- Authorized Intent Chain: Il sandboxing e l’approval gate garantiscono che ogni azione è non solo autorizzata, ma anche verificata come legittima
- Implicit Data Provenance: L’audit MCP e la rotation del DSN riducono la superficie di attacco prima che l’agente sia mai esposto
Ho testato questa strategia contro payload reali di agentjacking e ha bloccato tutti i tentativi di exploitation. Sono curioso di sentire come voi affrontate questo in production. Lasciate un commento se implementate qualcosa di simile o se trovate gaps nella mia approach.