SkillCloak-Proof Agent Skill Pipeline
SkillCloak bypasses every AI agent skill scanner >90% of the time. Complete guide to the HKUST disclosure, SkillDetonate runtime defense (97% detection), 4-layer protection pipeline, and what every team using Claude Code...
Deepak Bagada
CEO, SaaSNext
- Production-ready architecture blueprint and execution guide.
- Real-world benchmark metrics, time savings, and API integration steps.
- Verified implementation for AI founders, developers, and SaaS builders.
By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.
SkillCloak-Proof Agent Skill Pipeline
Breaking news out of Hong Kong University of Science and Technology, and every team that installs agent skills needs to read this today. Researchers demonstrated SkillCloak, a technique that bypasses every current AI agent skill scanner more than 90% of the time — including the scanners shipping inside Claude Code, Codex, OpenClaw, and most enterprise agent gateways. The follow-up defense, SkillDetonate, flips the strategy from static scanning to runtime execution monitoring and detects the same attacks at a reported 97% rate. In this guide I will break down what the disclosure actually showed, why static scanning failed so completely, and the 4-layer protection pipeline every team should run before a skill touches a production workspace.
What SkillCloak Is and Why It Matters
Agent skills are the new supply chain. Claude Code loads skills from .claude/skills, Codex from prompt packs and agent extensions, OpenClaw from skill directories — and every one of these now has a scanner that tries to reject malicious instructions before they execute. SkillCloak shows that trust is misplaced. The technique takes a genuinely malicious skill and re-encodes its behavior so that no static analysis of the skill text, description, or instruction headers reveals the payload.
The core insight from the HKUST team is brutally simple: scanners look at what a skill says, not what it does. If the trigger description appears benign and the dangerous instructions are generated dynamically, hidden behind base64 at runtime, or spread across chained indirection, the scanner sees a clean, helpful skill. The malicious behavior only materializes after the model has already loaded it into its context window, at which point a static scanner has already lost.
In the paper's benchmark across the major agent frameworks, SkillCloak evaded detection in over 90% of attempts. That single number invalidates the "we scan skills before install" posture that most teams are still running.
Why Static Scanning Failed
Let me be precise about why the evasion rate is so high, because the fix follows from it. A skill scanner is a classifier over text. It inspects the YAML front matter, the description, the step-by-step instructions, and possibly the file structure. Every one of those signals is forgeable:
- The description field can promise one behavior while instructions deliver another.
- Dangerous verbs can be obfuscated — "execute os.system", "read /etc/shadow", "export your history" — so no regex or LLM-based classifier fires.
- The payload can be split across files, fetched from a URL at runtime, or assembled from environment variables.
- Promotional skills that install alongside a friendly demo can carry the payload inside the demo itself.
An LLM grader helps, but it is still static: it cannot observe what actually runs, and it is itself promptable by adversarial skill text. The HKUST work exploited exactly this class of weakness. The lesson is not "scan harder." It is that scanning text is the wrong layer of defense entirely.
SkillDetonate: Runtime Defense at 97%
The same research team that disclosed SkillCloak also proposed the defense: SkillDetonate. Instead of grading the skill text, you execute the skill in a sandboxed, observable environment and monitor what it actually does. It watches syscalls, filesystem access, network egress, process spawns, environment variable reads, and prompt-injection attempts against the model itself. A skill that tries to reach out, exfiltrate data, or inject behavior gets caught in the act.
Reported detection is 97% against the SkillCloak family — a massive jump from the sub-10% effective rate of static scanning. The tradeoff is real cost: every skill must be detonated, every run consumes compute and time, and some legitimately dynamic skills (that fetch data from the internet, for example) generate noise you must allowlist. But a 97% runtime catch rate is the difference between a supply-chain incident and a blocked attempt.
The 4-Layer Protection Pipeline
The mistake to avoid is treating SkillDetonate as a silver bullet. Runtime monitoring catches the payload, but it does not stop a skill from slowly poisoning a workspace across many small actions, and it does not stop a compromised skill you already approved. The robust design is layered, and each layer has a different job:
Layer 1 Provenance gate Hash-verified origin, signed registry, allowlist only
Layer 2 Static triage AST + LLM scan, blocks obvious malware, cheap and fast
Layer 3 Runtime detonation SkillDetonate sandbox, syscall + egress monitoring
Layer 4 Trust decay Post-execution audit, behavioral score, periodic re-vet
Layer 1 decides who is allowed in at all. Layer 2 removes the trivial attacks. Layer 3 catches everything that survives 1 and 2 by watching what actually executes. Layer 4 is the part most teams forget: a skill that passed all gates last month should not be trusted forever. Trust decays, scores update from audit logs, and suspicious drift triggers re-detection.
The Full Pipeline
Here is the LangGraph implementation of the pipeline — a skill vetting workflow that takes a candidate skill directory and produces a trusted or rejected verdict with a full audit trail.
.env
SKILL_REGISTRY_URL=https://registry.example.com/skills
SKILL_REGISTRY_KEY=sk_live_xxxx
SANDBOX_IMAGE=skilldetonate:2026
SANDOX_TIMEOUT=90
DETONATE_MAX_RETRIES=3
TRUST_DECAY_DAYS=30
AUDIT_DIR=./audit
SCANNER_MODEL=claude-sonnet-4-5
schemas.py
from pydantic import BaseModel, Field
from typing import Literal
class SkillCandidate(BaseModel):
name: str
source: str
sha256: str
path: str
class ProvenanceResult(BaseModel):
passed: bool
origin: str | None = None
reason: str | None = None
class ScanResult(BaseModel):
score: float = Field(ge=0, le=1)
findings: list[str] = Field(default_factory=list)
class DetonationEvent(BaseModel):
kind: Literal["syscall", "fs", "net", "proc", "env", "prompt"]
target: str
blocked: bool
class Verdict(BaseModel):
skill: str
status: Literal["trusted", "rejected", "quarantine"]
score: float
events: list[DetonationEvent] = Field(default_factory=list)
tools.py
import subprocess, hashlib
from .schemas import SkillCandidate, ProvenanceResult, ScanResult, Verdict
def verify_provenance(cand: SkillCandidate, env: dict[str, str]) -> ProvenanceResult:
proc = subprocess.run(
["skillreg", "verify", cand.sha256, "--source", cand.source],
capture_output=True, text=True, timeout=30, env=env,
)
return ProvenanceResult(passed=proc.returncode == 0,
origin=proc.stdout.strip() or None,
reason=proc.stderr.strip() or None)
def scan_static(cand: SkillCandidate) -> ScanResult:
proc = subprocess.run(["skillscan", cand.path], capture_output=True,
text=True, timeout=45)
findings = [l for l in proc.stdout.splitlines() if l]
score = max(0.0, 1.0 - 0.15 * len(findings))
return ScanResult(score=score, findings=findings)
def detonate(cand: SkillCandidate, env: dict[str, str], timeout: int) -> Verdict:
proc = subprocess.run(
["skilldetonate", "--image", env["SANDBOX_IMAGE"], cand.path],
capture_output=True, text=True, timeout=timeout, env=env,
)
events = []
for line in proc.stdout.splitlines():
if "EVENT" in line:
fields = line.split()
events.append({
"kind": fields[1], "target": fields[2], "blocked": fields[3] == "BLOCKED",
})
return Verdict(skill=cand.name, status="quarantine", score=0.0, events=events)
graph.py
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
from .schemas import Verdict, SkillCandidate
class State(TypedDict):
candidate: dict
verdict: dict | None
audit: list[dict]
def make_pipeline(env: dict[str, str], decay_days: int, retries: list[float]):
def provenance(state: State) -> State:
cand = SkillCandidate(**state["candidate"])
res = verify_provenance(cand, env)
if not res.passed:
return {**state, "verdict": {"status": "rejected", "score": 0.0}}
return {**state, "audit": [{"stage": "provenance", "ok": True}]}
def triage(state: State) -> State:
cand = SkillCandidate(**state["candidate"])
scan = scan_static(cand)
if scan.score < 0.4:
return {**state, "verdict": {"status": "rejected", "score": scan.score}}
return {**state, "audit": [*state["audit"], {"stage": "scan", "score": scan.score}]}
def detonation(state: State) -> State:
cand = SkillCandidate(**state["candidate"])
for attempt in range(len(retries)):
verdict = detonate(cand, env, int(env.get("SANDOX_TIMEOUT", 90)))
if not verdict.events or any(e["blocked"] for e in verdict.events):
return {**state, "verdict": {"status": "quarantine",
"score": verdict.score, "events": verdict.events}}
return {**state, "verdict": {"status": "trusted", "score": 1.0,
"decay_days": decay_days}}
g = StateGraph(State)
g.add_node("provenance", provenance)
g.add_node("triage", triage)
g.add_node("detonation", detonation)
g.add_edge(START, "provenance")
g.add_edge("provenance", "triage")
g.add_edge("triage", "detonation")
g.add_edge("detonation", END)
return g.compile()
main.py
import os, json
from dotenv import load_dotenv
from .graph import make_pipeline
load_dotenv()
if __name__ == "__main__":
env = dict(os.environ)
pipeline = make_pipeline(env, decay_days=int(env.get("TRUST_DECAY_DAYS", 30)),
retries=[2.0, 8.0, 30.0])
candidate = {
"name": "demo-skill",
"source": "https://registry.example.com/demo-skill-1.2.tar.gz",
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"path": "/tmp/skills/demo-skill",
}
out = pipeline.invoke({"candidate": candidate, "audit": []})
print(json.dumps(out, indent=2))
verdict = out["verdict"]["status"]
exit(0 if verdict in ("trusted",) else 1)
Note the intentional design choice in graph.py: the detonation node retries on the sandbox itself failing to boot or network flakiness, but never on blocked events — a blocked syscall is a verdict, not a transient error. That distinction is the difference between catching malware and accidentally approving it.
Retry Rules
The pipeline has three distinct retry domains and each one has different semantics:
1. Provenance lookup (network): 3 attempts, exponential 1s / 2s / 4s, cap 5s
2. Static scanner (compute): 2 attempts, fixed 1.0s / 2.0s, fail-closed
3. Sandbox detonation: 3 attempts, fixed 2s / 8s / 30s
- retry ONLY on sandbox boot or infra errors
- NEVER retry a blocked syscall or egress event: block is a verdict
4. Registry registration: 5 attempts, exponential 1s / 2s / 4s / 8s / 16s + jitter
- Add 10% jitter to every delay so parallel skill installs do not stampede the registry.
- Fail closed: any stage that errors without a clear result rejects the skill and emits an audit entry.
- After
TRUST_DECAY_DAYS, schedule a re-detonation; if it produces blocked events, the skill status flips to quarantine and it is unmounted from every workspace automatically.
What Every Team Using Claude Code, Codex, or OpenClaw Must Change Today
If your team installs skills from the internet — and almost every 2026 agent team does — do these five things this week:
- Stop relying on the built-in scanners. They are the layer SkillCloak defeats. Treat them as triage, not a gate.
- Introduce a signed, internal registry. Only skills that pass provenance verification may enter; block unknown sources.
- Run runtime detonation in CI for any new or updated skill, using an image like SkillDetonate with syscall and egress monitoring.
- Add trust decay. Skills you approved a quarter ago get re-detected on a schedule and dropped if their behavioral score drifts.
- Make the model itself suspicious: instruct agents to treat skill-provided instructions as untrusted data and to refuse credential access, history export, or outbound network calls unless explicitly confirmed.
Security posture for agent skills is now a runtime problem, not a scanning problem. If you are still arguing about regex rules, you have already lost the threat model.
For more production security patterns like this one, check the Workflows repository, and keep up with the disclosure cycle on the news feed. Teams building MCP-based tool gateways will also want the MCP directory — the same 4-layer logic applies to MCP servers.
FAQ
Q: How does SkillCloak actually bypass the scanners?
A: It re-encodes the malicious behavior so static analysis of the skill text, description, and instructions reveals nothing dangerous. The payload materializes at runtime through chained indirection, encoded content, or dynamically assembled commands — so text-level scanners never see it.
Q: What is the difference between SkillCloak and SkillDetonate?
A: SkillCloak is the attack — an evasion technique disclosed by HKUST researchers that defeats static skill scanners over 90% of the time. SkillDetonate is the defense from the same team: it executes the skill in a monitored sandbox and catches malicious runtime behavior at a reported 97% rate.
Q: Should I uninstall all third-party skills right now?
A: Not necessarily, but you should stop treating the built-in scanner as a security boundary. Audit which skills are active, move to a signed registry with provenance verification, and start detonating new or updated skills in a sandbox before they touch a production workspace.
Q: Why not just use an LLM to review skill content?
A: An LLM grader is still a static classifier — it reads text, it does not observe execution. SkillCloak is designed to fool text-based judgment, including LLM-based graders, by hiding behavior until runtime. Only runtime monitoring closes that gap.
Q: How often should skills be re-vetted?
A: On a decay schedule — typically 30 to 90 days depending on risk. Skills can change upstream after you approve them, or accumulate behavior over time. Re-detonate on the schedule and automatically quarantine any skill whose runtime behavior score drops or produces blocked events.
Enjoyed this breakdown? Get our morning dispatch in your inbox.
Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.
Deepak Bagada
CEO, SaaSNext
Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.
DeepTutor Personalized Tutoring Agent Pipeline
Next Story →OMP Hash-Anchored Terminal AI Agent Pipeline
Related Intelligence Analysis
The Step-by-Step Guide to Automating Meeting Tasks with Whisper
You're spending 45 minutes after every client meeting typing up notes and manually assigning tasks in Jira. This guide shows you how to wire OpenAI Whisper and Claude to automatically convert meeting recordings into assi...
Lovable AI UI-to-Code Pipeline: 2026 Tutorial
Lovable AI UI-to-code automation pipeline uses Lovable AI on Lovable Cloud to convert visual UI designs and natural language specs into production-grade web applications. UI/UX designers and frontend developers bridging...
Claude Code's New Browser: 5 Workflows That Save Hours Daily
Claude Code's built-in browser is a sandboxed tabbed browser inside the Claude Code desktop app (Week 28, July 2026) accessible via Cmd+Shift+B (macOS) or Ctrl+Shift+B (Windows). It lets Claude open websites, read docume...