Static Analysis for LLM Agent Skills
Project description
Razin - Static Analysis for LLM Agent Skills
Razin is a local scanner for SKILL.md-defined agent skills. It performs static analysis only (no execution) and writes deterministic JSON reports.
Table of Contents
- Requirements
- Install
- Usage
- Workflow
- Config File
- Detection Rules
- Output Formats
- Releasing
- Outputs
- Contributing
- Security
- License
Requirements
- Python
3.12+
Install
pip install razin
Verify:
razin --help
Usage
Basic scan:
razin scan -r . -o output/
Custom rules directory:
razin scan -r . -R ./enterprise-rules -o output/
Single rule file:
razin scan -r . -f ./enterprise-rules/net_unknown_domain.yaml -o output/
Multiple rule files:
razin scan -r . \
-f ./enterprise-rules/net_unknown_domain.yaml \
-f ./enterprise-rules/mcp_endpoint.yaml \
-o output/
Long-form equivalent (for scripts and clarity):
razin scan --root . --output-dir output/ --profile strict --no-cache
CLI flags:
-r,--root <path>: workspace root to scan-o,--output-dir <path>: output root for findings and summaries-c,--config <file>: optional config file path (defaults to<root>/razin.yaml)-m,--mcp-allowlist <domain-or-url>: optional repeatable MCP endpoint/domain allowlist override-p,--profile <strict|balanced|audit>: policy profile-R,--rules-dir <path>: load all custom*.yamlDSL rules from this directory-f,--rule-file <path>: load specific custom*.yamlDSL rule file (repeatable)-n,--no-cache: disable cache reads/writes-v,--verbose: show cache stats and diagnostics--rules-mode <replace|overlay>: rule composition mode (default:replace)--duplicate-policy <error|override>: duplicate rule_id handling in overlay mode (default:error)--max-file-mb <n>: skip files larger thannMB--output-format <formats>: comma-separated output formats:json,csv,sarif(default:json)--no-stdout: silence stdout output--no-color: disable colored output
Rules source behavior:
- Default mode (no custom flags): bundled rules under
src/razin/dsl/rules/ - Custom directory mode:
--rules-dirreplaces bundled rules for that scan - Custom file mode: one or more
--rule-filevalues replace bundled rules for that scan --rules-dirand--rule-fileare mutually exclusive- Invalid path, invalid extension, duplicate
rule_id, and invalid YAML fail fast
Rule composition with --rules-mode:
replace(default): custom source replaces bundled rules entirelyoverlay: bundled rules are loaded first, then custom rules are merged in
Overlay duplicate handling:
- By default (
--duplicate-policy error), a custom rule with the samerule_idas a bundled rule causes a clear error - With
--duplicate-policy override, the custom rule replaces the bundled rule
Overlay examples:
# Merge enterprise rules on top of bundled rules
razin scan -r . -R ./enterprise-rules --rules-mode overlay -o output/
# Override a specific bundled rule
razin scan -r . -f ./custom_auth.yaml --rules-mode overlay --duplicate-policy override -o output/
Workflow
Python (Primary)
Use the local Python/uv workflow for day-to-day development:
uv run razin scan -r . -o output/
uv run pytest -q
uv run ruff check src tests
uv run mypy src tests
Docker (Optional)
Prerequisites:
- Docker Desktop (macOS/Windows) or Docker Engine (Linux)
Build runtime image:
docker build -t razin:local .
Run scanner in Docker:
docker run --rm razin:local --help
docker run --rm razin:local scan --help
docker run --rm \
-v "$(pwd)":/work \
-w /work \
razin:local \
scan --root /work --output-dir /work/output/docker
Config File
Create razin.yaml in scan root (or pass with --config):
allowlist_domains:
- api.openai.com
denylist_domains:
- "*"
mcp_allowlist_domains:
- rube.app
mcp_denylist_domains:
- blocked.example.com
tool_prefixes:
- RUBE_
- MCP_
strict_subdomains: false
detectors:
enabled:
- NET_RAW_IP
- NET_UNKNOWN_DOMAIN
- NET_DOC_DOMAIN
- SECRET_REF
- EXEC_FIELDS
- OPAQUE_BLOB
- TYPOSQUAT
- BUNDLED_SCRIPTS
- MCP_REQUIRED
- MCP_ENDPOINT
- MCP_DENYLIST
- TOOL_INVOCATION
- DYNAMIC_SCHEMA
- AUTH_CONNECTION
- EXTERNAL_URLS
- PROMPT_INJECTION
- HIDDEN_INSTRUCTION
disabled: []
typosquat:
baseline:
- openai-helper
skill_globs:
- "**/SKILL.md"
max_file_mb: 2
By default, subdomain matching is enabled: allowlisting github.com also covers docs.github.com. Set strict_subdomains: true to require exact domain matches only.
Detection Rules
Razin ships 18 bundled DSL rules. Key rules by category:
Network and Supply Chain: NET_RAW_IP, NET_UNKNOWN_DOMAIN, NET_DOC_DOMAIN, EXTERNAL_URLS, MCP_REQUIRED, MCP_ENDPOINT, MCP_DENYLIST
Secrets and Execution: SECRET_REF, EXEC_FIELDS, OPAQUE_BLOB, BUNDLED_SCRIPTS
Tool and Schema: TOOL_INVOCATION, DYNAMIC_SCHEMA, AUTH_CONNECTION, TYPOSQUAT
LLM Threat Detection:
-
PROMPT_INJECTION(score 80, confidence medium): Detects prompt injection patterns using strong/weak hint classification with negation awareness. Strong hints include phrases like "ignore previous instructions", "you are now", "do not reveal". Requires at least 2 hints with at least 1 strong hint. Negation-prefixed phrases (e.g., "do not ignore previous instructions") are correctly excluded. -
HIDDEN_INSTRUCTION(score 90, confidence high): Detects content hidden from normal markdown rendering. Scans for zero-width Unicode characters (U+200B through U+2064), HTML comments containing injection phrases, embedded BOM characters in body text, and mixed-script/homoglyph tokens or domains. Leading BOM (encoding metadata) is ignored; only embedded occurrences are flagged.
Output Formats
JSON (default)
Per-skill JSON files are always written when --output-dir is set:
razin scan -r . -o output/
CSV
Export all findings as a single CSV file:
razin scan -r . -o output/ --output-format csv
Generates output/findings.csv with columns: id, skill, rule_id, severity, score, confidence, path, line, title, description, recommendation.
SARIF
Export findings as SARIF 2.1.0 for code-scanning integrations:
razin scan -r . -o output/ --output-format sarif
Generates output/findings.sarif.
Multiple formats
Generate all formats in one run:
razin scan -r . -o output/ --output-format json,csv,sarif
Outputs
Per skill, RAZIN writes:
output/<skill-name>/findings.jsonoutput/<skill-name>/summary.json
Global exports (when selected):
output/findings.csvoutput/findings.sarif
Cache file:
output/.razin-cache.json
Skill name derivation precedence:
- Frontmatter
name(if present) - Nearest folder containing
SKILL.md - Sanitized relative path from scan root
Contributing
See CONTRIBUTING.md for setup, quality checks, and PR guidelines.
Security
To report a vulnerability, see SECURITY.md.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file razin-1.1.1.tar.gz.
File metadata
- Download URL: razin-1.1.1.tar.gz
- Upload date:
- Size: 148.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
543d387a2c91054d047ef0e35c3bd12446f88f3cbb1188ff4650c8f71d47e238
|
|
| MD5 |
e12b92a0d1ef1bc1f32e65bd7603ee8d
|
|
| BLAKE2b-256 |
9d49a5a49323b03d756569a4d6260c0c8d23d4ea19eb9eacc53003768e2f5db8
|
Provenance
The following attestation bundles were made for razin-1.1.1.tar.gz:
Publisher:
release-pypi.yml on theinfosecguy/razin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
razin-1.1.1.tar.gz -
Subject digest:
543d387a2c91054d047ef0e35c3bd12446f88f3cbb1188ff4650c8f71d47e238 - Sigstore transparency entry: 953562892
- Sigstore integration time:
-
Permalink:
theinfosecguy/razin@a31901a87fb09e5affe6977ade70a38f53e5d404 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/theinfosecguy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@a31901a87fb09e5affe6977ade70a38f53e5d404 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file razin-1.1.1-py3-none-any.whl.
File metadata
- Download URL: razin-1.1.1-py3-none-any.whl
- Upload date:
- Size: 117.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebb036e9f2ba85a2836eddfd5eda0ab4b2c28bcc366cc3d9e6fcb1f061892229
|
|
| MD5 |
0c1ad0331a4b520fad0faae98f3e64b6
|
|
| BLAKE2b-256 |
a6c79b12016144e63517d2916ac4898bfffee6f064b72653bdf6df9af25972a0
|
Provenance
The following attestation bundles were made for razin-1.1.1-py3-none-any.whl:
Publisher:
release-pypi.yml on theinfosecguy/razin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
razin-1.1.1-py3-none-any.whl -
Subject digest:
ebb036e9f2ba85a2836eddfd5eda0ab4b2c28bcc366cc3d9e6fcb1f061892229 - Sigstore transparency entry: 953562893
- Sigstore integration time:
-
Permalink:
theinfosecguy/razin@a31901a87fb09e5affe6977ade70a38f53e5d404 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/theinfosecguy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@a31901a87fb09e5affe6977ade70a38f53e5d404 -
Trigger Event:
workflow_dispatch
-
Statement type: