Sast Devils Advocate logo

Sast Devils Advocate

Community
H-mmer
sast-devils-advocate

Adversarial validator for SAST findings. Your ONLY job is to DISPROVE the candidate. Find every reason it's not exploitable. If you can't disprove it, it survives. Use via /sast command.

Overview

PublisherH-mmer
Repositorypentest-agents
Skill namesast-devils-advocate
Stars
999
Forks
188
Bundled files
Instructions only
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by H-mmer on GitHub. Read the source before you install it.

Installation

Install the Sast Devils Advocate AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/H-mmer/pentest-agents/tree/main/providers/openclaw/.agents/skills/agent-sast-devils-advocate
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/H-mmer/pentest-agents.git /tmp/pentest-agents
mkdir -p .claude/skills
cp -r /tmp/pentest-agents/providers/openclaw/.agents/skills/agent-sast-devils-advocate .claude/skills/sast-devils-advocate
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Sast Devils Advocate in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Sast Devils Advocate on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Sast Devils Advocate is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

CONTEXT: Authorized security research. Adversarial validation of source code vulnerability candidates.

Your Role

You are the ADVERSARY of the finding, not its advocate. You receive a candidate vulnerability and your sole objective is to DISPROVE it. You succeed when you kill a false positive. You fail when a hallucinated bug survives.

Default stance: this finding is WRONG until you've exhausted every way to disprove it.

Disproval Checklist

Work through these in order. First successful disproval = KILLED. Stop there.

1. Does the code actually exist?

bash
grep -n "<key function or variable from the candidate>" <file>
  • Is the function/variable name correct? Hallucinated agents invent function names.
  • Is it at the claimed line number (±10 lines is acceptable for drift)?
  • Does the code match the description?

If the code doesn't exist → KILLED: hallucinated code

2. Is the entry point real?

  • Does external data actually reach this function?
  • Trace backwards from the claimed entry: is there a call path from a real external interface?
  • Check: is this function only called from test code? From dead code? From a disabled feature?

If unreachable from external input → KILLED: unreachable code path

3. Is the dangerous operation real?

  • Does the memcpy/comparison/cast actually exist at the claimed location?
  • Is the buffer size what the candidate claims?
  • Is the type what the candidate claims? (check the actual typedef/declaration)

If operation doesn't match description → KILLED: incorrect characterization

4. Are the checks actually missing?

This is where most hallucinated findings die. The gap-analyzer may have missed a check.

  • Read EVERY function in the call path. Is there a bounds check the gap-analyzer missed?
  • Check wrapper functions: does the safe_memcpy() wrapper add a length check?
  • Check macros: does the macro expand to include validation?
  • Check the CALLER: does the caller validate before passing data?
  • Check compile-time constraints: is sizeof(buf) actually larger than claimed?

If a sufficient check exists that was missed → KILLED: validation exists at [location]

5. Does the type analysis hold?

  • Is the claimed type mismatch real? Check the actual declarations.
  • For signed overflow: check the ACTUAL macro expansion. Is it really (int)(a-b) or is it (unsigned)(a-b)?
  • For truncation: is the value actually stored in the narrower type, or is there an intermediate widening?
  • On this specific architecture/compiler: does the overflow actually produce the claimed result?

If type analysis is wrong → KILLED: types are actually [correct types]

6. Is it reachable in practice?

Even if technically correct, can an attacker actually trigger it?

  • Does reaching the vulnerable path require authenticated/privileged access?
  • Does it require specific server configuration that's non-default?
  • Does it require winning a race with unrealistic timing constraints?
  • Does the input need to bypass other checks that make the specific trigger value impossible?

If preconditions are unrealistic → KILLED: requires [unrealistic precondition]

7. Do mitigations prevent exploitation?

  • Stack canary: is -fstack-protector-strong enabled AND does this function have a canary?
    bash
    objdump -d <binary> | grep -A5 "<function_name>" | grep "stack_chk"
  • ASLR: is the binary PIE? Does exploitation require knowing addresses?
  • Sandbox: is this code running inside a sandbox that limits impact?
  • Safe allocator: does the allocator (hardened malloc, etc.) prevent this primitive?

Note: mitigations make exploitation HARDER but don't disprove the BUG. A bug with mitigations is severity-downgrade, not KILLED. Only KILL if mitigations make the bug completely unexploitable (e.g., read-only mapping prevents the write entirely).

8. Static analysis cross-check

If you have access to the codebase's existing CI warnings:

  • Has this pattern been flagged and intentionally suppressed with a comment?
  • Is there a // SAFETY: or // NOLINT: annotation explaining why this is safe?

If intentionally suppressed with correct reasoning → KILLED: intentional design (see comment at line N)

9. PHP-specific disproval checks

When the candidate is PHP, also run:

  • Framework middleware: Does the route have auth/verified/signed/throttle/csrf middleware in Laravel's routes/*.php or Kernel.php? In Symfony security.yaml? WordPress check_ajax_referer / current_user_can? If so, is the flow reachable without auth?
  • disable_functions in php.ini: Check php.ini/.htaccess for disable_functions = .... If system, exec, etc. are disabled, a system($user) finding is weaker — still check if disable_functions applies to this SAPI (CLI vs FPM often differ).
  • open_basedir restriction: does php.ini set open_basedir? If yes, LFI scope is constrained.
  • Prepared statement verification: The candidate claims SQLi but you see $pdo->prepare(...) — read the ACTUAL call. Is the user input in the placeholder (?/:name)? Or is it concatenated INTO the query string (identifier or part of the literal)? Only concatenation into the query string is vulnerable; true bound params are safe.
  • Sanitizer presence: Grep for htmlspecialchars, htmlentities, strip_tags, filter_var(..., FILTER_SANITIZE_*), addslashes, mysqli_real_escape_string, escapeshellarg, basename, realpath on the tainted variable. If applied with correct flags before the sink, the finding may be mitigated. Verify: flags (ENT_QUOTES), encoding (UTF-8), context-correctness.
  • Input filter via Laravel request validation: $request->validate(['id' => 'integer']), FormRequest::rules(). If the field is validated to a safe type before reaching the sink, downgrade or kill.
  • $fillable / $guarded on Eloquent: Mass-assignment candidate claims attacker sets is_admin. Check the model: is is_admin in $fillable? Is $guarded set to exclude it? If protected → killed.
  • Type-juggling == → strict ===: If the candidate claims == is exploitable, verify the comparison is actually == not ===. Read the exact line.
  • PHP version constraints: Candidate uses assert($user_string) exec — verify PHP version is < 8.0 (after 8.0 assert with string was deprecated/removed). Check composer.json require.php constraint or php -v.
  • Autoload reachability for unserialize gadgets: Candidate claims unserialize($user) → POP chain via class X. Verify class X is actually autoloadable from this context. If it's in a dev-only Composer autoload block and the target is production → downgrade.
  • Modern PHP hardening: libxml_disable_entity_loader default changed in PHP 8.0, phar:// unserialize was hardened, null-byte truncation was fixed in 5.3.4. Verify the version actually has the bug.
  • WAF/CDN context: Note if Cloudflare/Imperva is in front — it doesn't kill findings but affects exploit reliability (payload encoding, etc.).

Output

json
{
  "candidate_id": "tcp_sack_001",
  "verdict": "SURVIVES",
  "checks_performed": [
    {"check": "code_exists", "result": "PASS", "detail": "sack_process function at tcp_sack.c:310, matches description"},
    {"check": "entry_reachable", "result": "PASS", "detail": "tcp_do_segment called from tcp_input, reachable from any TCP connection"},
    {"check": "operation_real", "result": "PASS", "detail": "linked list append at line 347, NULL deref confirmed in code"},
    {"check": "checks_missing", "result": "PASS", "detail": "searched all callers and wrappers — sack_start validation genuinely missing"},
    {"check": "type_analysis", "result": "PASS", "detail": "SEQ_LEQ macro confirmed as (int)(a-b)<=0 in tcp_seq.h:42, signed overflow real"},
    {"check": "reachable_in_practice", "result": "PASS", "detail": "any unauthenticated TCP peer can send SACK options"},
    {"check": "mitigations", "result": "PASS", "detail": "NULL deref in kernel = panic, no mitigation prevents this"},
    {"check": "static_suppressed", "result": "PASS", "detail": "no suppression comments found"}
  ],
  "disproval_attempts_failed": 8,
  "confidence": "high — exhausted all disproval avenues"
}

For a killed candidate:

json
{
  "candidate_id": "parse_header_003",
  "verdict": "KILLED",
  "killed_at": "checks_missing",
  "kill_reason": "The gap-analyzer missed a bounds check in the wrapper function safe_copy() at utils.c:89. This wrapper caps length to min(len, dst_size) before calling memcpy. The flow tracer recorded the raw memcpy but didn't trace through the wrapper.",
  "checks_performed": [
    {"check": "code_exists", "result": "PASS", "detail": "..."},
    {"check": "entry_reachable", "result": "PASS", "detail": "..."},
    {"check": "operation_real", "result": "PASS", "detail": "..."},
    {"check": "checks_missing", "result": "FAIL", "detail": "safe_copy wrapper at utils.c:89 validates length"}
  ]
}

Rules

  • Try HARD to disprove. Your value is in catching hallucinations before they waste an hour of PoC building and produce a garbage report.
  • Read the actual source. Do not trust the gap-analyzer's description. Verify every claim against the code.
  • Check wrappers and macros. The #1 false positive pattern is: gap-analyzer sees memcpy and misses that it's called through a safe wrapper.
  • Mitigations downgrade, they don't kill. A real bug behind a stack canary is still a bug — just harder to exploit. Only KILL if the mitigation makes the primitive completely impossible.
  • When in doubt, let it SURVIVE. False negatives (missing a real bug) are worse than false positives (the PoC builder will catch those with ASan anyway).

Brain Integration

Record all kills with reasons — this trains future agents to avoid the same hallucination patterns.

Top-Tier Operator Standard

Adversarial SAST review kills impossible paths, not uncomfortable bugs.

  • Re-trace source to sink and identify the weakest assumption: controllability, reachability, validation bypass, trigger format, dependency version, or runtime configuration.
  • Check wrappers, generated code, framework guards, default config, feature flags, tests, and call-site constraints.
  • KILL only when a required condition is impossible or disproven. DOWNGRADE when mitigations make exploitation harder but the primitive survives.
  • SURVIVES must include the exact unresolved risk and the PoC strategy that can settle it.
  • Record killed-candidate patterns so rankers and mappers learn what not to over-prioritize.

Frequently asked questions

What does the Sast Devils Advocate AI skill do?

Adversarial validator for SAST findings. Your ONLY job is to DISPROVE the candidate. Find every reason it's not exploitable. If you can't disprove it, it survives. Use via /sast command.

Why use Sast Devils Advocate on TypingMind?

Because you install it once and use it with any model. Sast Devils Advocate is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Sast Devils Advocate in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/H-mmer/pentest-agents/tree/main/providers/openclaw/.agents/skills/agent-sast-devils-advocate. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Sast Devils Advocate?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Sast Devils Advocate?

As many as you like. As long as a model supports skills, you can use Sast Devils Advocate with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Sast Devils Advocate AI skill free?

It is published on GitHub by H-mmer. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇