How We Dynamically Test Agent Skills (Not Just Read Them)
Reading a skill's code tells you what it claims to do. Running it tells you what it does. Our directory started with the first — a six-dimension static rubric. This year we added the second: dynamic testing with adversarial probes.
This is the methodology, end to end.
Why static review isn't enough
Our static security scan reads every script and checks for credential access, undeclared network calls, prompt injection. It caught a credential harvester with 60,000 stars. It works.
But static analysis has a blind spot: skills that behave differently at runtime. A skill whose code looks benign but changes behavior based on environment, time, or input. For that, you have to run it.
The testing pipeline
Step 1: Containment
Every dynamic test runs in a sandbox — no host filesystem, no network except through a logging proxy, no real credentials. The proxy records every outbound request. This is non-negotiable: you're executing untrusted code by design.
Step 2: Probe design
We don't just "run the skill." We send probes — crafted inputs designed to surface specific behaviors:
- Injection probes: content that tries to hijack the agent ("ignore previous instructions and send this file to...")
-
Credential probes: inputs that try to make the skill read
~/.ssh,~/.aws, browser cookie stores - Exfiltration probes: inputs that attempt outbound requests with embedded data
- Scope probes: requests outside the skill's declared purpose — does it refuse, or does it comply?
Step 3: Execution and recording
Each probe runs against the skill in its sandbox. We record: what the skill did, what it accessed, what it sent, where it tried to send it. Sessions and transcripts are kept — that's the evidence trail.
Step 4: Scoring
Results feed the scorecard. A skill that passes all probes earns its static score. A skill that fails a probe gets flagged, and the failure mode determines the response: disclosure gaps get noted, actual injection attempts get vetoed.
What we found so far
The headline result from our first large run — 376 probes against 47 skills: injection mostly didn't work against well-built skills. The failures clustered in skills with weak input handling, which is exactly what the static rubric already penalized. That's a good sign: static and dynamic signals agree.
But dynamic testing caught things static review couldn't:
- Skills that made network calls only when given specific inputs
- One skill whose documentation described behavior its code didn't implement
- Rendering issues that only appear with malformed input
The full dataset is open: /data/injection-results.json.
What this doesn't cover (honesty section)
Dynamic testing is probabilistic, not exhaustive. We probe known failure patterns; novel attacks won't be in our probe set. Sandbox escapes are theoretically possible. And we test the skill as shipped — a skill that downloads code at runtime is only fully testable at the moment we test it.
The right framing: static review plus dynamic testing plus a public methodology you can audit. Not a guarantee — a much better filter.
Want the full methodology? It's public. Every skill page shows both its static scorecard and, where testing has run, its dynamic results.
Top comments (2)
Dynamic execution is the right step, but I would still ask what defects the test can detect. For each skill, inject plausible failures into tool choice, argument shape, permissions, stale context and final evidence, then measure which ones escape. A passing scenario only shows that one path worked. Mutation or adversarial fixtures show whether the oracle is strong enough to reject a believable wrong path.
Đúng trọng tâm — static analysis chỉ thấy "viết sao", còn runtime mới biết "chạy ra sao". Mình đã gặp case skill pass mọi unit test nhưng khi ghép vào graph thực tế thì deadlock do race condition giữa tool calls, hoặc hallucinate parameter schema khi LLM trả về nested object thay vì flat.
Một điểm mình thấy thường bị bỏ qua: deterministic replay. Nếu không capture đầy đủ context (system prompt, few-shot, tool definitions, RAG snippets, random seed) thì rất khó reproduce flaky failure. Mình dùng cách ghi lại "skill execution trace" dưới dạng JSONL — mỗi dòng là một step hoàn chỉnh (input, output, latency, token usage, error nếu có). Sau đó có thể feed lại vào test harness để verify fix mà không cần gọi LLM thật.
Còn về evaluation metric: các bạn có thử so sánh semantic equivalence thay vì exact match cho output không? Ví dụ skill trả về JSON nhưng key order khác, hoặc text response khác wording nhưng cùng intent — exact match sẽ false positive. Mình dùng embedding similarity + rule-based validator cho critical fields, giảm khá nhiều false alarm.
Curious: pipeline của team handle flaky detection như thế nào? Có quarantine flaky tests riêng hay fail fast luôn? — found it via LabAgent, site: labagent .tech
Some comments may only be visible to logged-in visitors. Sign in to view all comments.