Palisade
A linter for LLM security. Palisade statically detects prompt-injection vulnerabilities in Python codebases — untrusted input flowing through an LLM into a dangerous sink — in CI, before they ship.
untrusted input → LLM → exec / shell / raw SQL (no sanitizer) ⇒ finding
No API key. No signup. No network calls. Pure static analysis.
uvx palisade-sec scan .
HIGH app.py:31 [PI-EXEC] Prompt injection reaching code execution
↳ source: question = request.json["question"] (app.py:31)
↳ llm: resp = client.chat.completions.create( (app.py:32)
↳ sink: exec(code) (app.py:40)
No sanitizer on path. Confidence: HIGH
Attack: crafted input makes the model emit Python that executes on your server.
Fix: never exec model output; sandbox + strict allowlist (denylists are bypassable).
Refs: CVE-2024-12366 (PandasAI); CVE-2025-3248 (Langflow, CISA KEV)
Why
This exact pattern is behind real, exploited CVEs: Langflow (CVE-2025-3248, on CISA KEV, exploited in the wild), PandasAI (CVE-2024-12366, CVSS 9.8), Vanna.ai (CVE-2024-5565), LangChain PAL/LLMMath chains (CVE-2023-36258, CVE-2023-29374). Almost nobody defends it at the code level: existing tools are runtime proxies (paid, in the traffic path) or guardrail libraries you have to know to wire in. Palisade is the missing piece — free, static, LLM-dataflow-aware, and CI-native, like ruff or semgrep but for the OWASP LLM Top-10 #1 risk.
What it detects (v1)
| Rule | Path | Real-world precedent |
|---|---|---|
PI-EXEC |
input → LLM → exec / eval / compile / PythonREPL |
PandasAI, Langflow, LangChain PAL |
PI-SHELL |
input → LLM → os.system / subprocess(shell=True) |
Open Interpreter (by design) |
PI-SQL |
input → LLM → raw non-parameterized SQL | Vanna.ai |
Palisade runs taint analysis, not grep: it only reports a complete
source → LLM → sink data-flow path with no sanitizer in between.
- Constant developer prompt → LLM →
exec? Silent — no untrusted source. subprocess.run([...])with an arg list? Silent — safe sink shape.- Parameterized
cursor.execute(q, params)? Silent. - Allowlist / pydantic validation on the path? Silent — sanitized.
- Denylist or human-confirmation gate? Flagged MED "risky" — real CVEs were exploited despite exactly those defenses. That is deliberate.
Install & run
# one-shot, no install
uvx palisade-sec scan path/to/project
# or
pipx run palisade-sec scan .
# or as a dev dependency
uv add --dev palisade-sec
Useful flags:
palisade-sec scan . --all # also show MED/LOW findings
palisade-sec scan . --json # stable machine-readable output
palisade-sec scan . --report # write palisade-report.md
palisade-sec scan . --rules ./my-rules # add your own YAML rules
CI
Gate pull requests on new findings only — adopt Palisade on an imperfect codebase without a wall of pre-existing failures:
palisade-sec baseline . # once; commit .palisade/baseline.json
palisade-sec scan . --ci --baseline .palisade/baseline.json
--ci exits non-zero only if a new HIGH finding appears. Fingerprints are
line-number independent, so refactors don't churn the baseline.
GitHub Actions:
- uses: astral-sh/setup-uv@v5
- run: uvx palisade-sec scan . --ci --baseline .palisade/baseline.json
Configuration
pyproject.toml:
[tool.palisade]
paths_ignore = ["migrations/*", "sandbox/*"]
include_tests = false # tests/** and conftest.py are skipped by default
max_hops = 3 # inter-procedural depth bound
Or the same keys in .palisade.toml.
Custom rules
Rules are plain YAML validated by a pydantic schema — sources, LLM call
signatures, sinks, sanitizers, partial defenses. Adding coverage for a new
framework is a small PR with no engine changes. See
src/palisade_sec/rules/README.md for
the 5-minute guide.
Architecture
source ──▶ language frontend (Python: stdlib ast) ──▶ normalized taint IR
│
language-agnostic engine ─┤ taint propagation,
sanitizer resolution, confidence scoring
│
YAML rules ──▶ findings ──▶ baseline diff ──▶ terminal / json / md
The frontend/IR split is the scalability story: JS/TS/Go land later as new frontends (tree-sitter) with zero engine changes.
Safety of the tool itself
- Palisade never executes, imports, or evaluates scanned code — it only
parses source text with
ast.parse. scanmakes no network calls and needs no API key or account.- No telemetry. Nothing leaves your machine.
An honest note on scope
Palisade is one layer of defense against one class of vulnerability. A clean scan means no detected injection-to-sink path — it does not mean your application is secure. Keep your runtime guardrails, permissions boundaries, and sandboxes; Palisade complements them, before merge.
License
MIT
Release files for palisade-sec 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| palisade_sec-0.1.0.tar.gz | 59.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| palisade_sec-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 98.5 kB
Release files / palisade_sec-0.1.0.tar.gz
| Download URL | palisade_sec-0.1.0.tar.gz |
|---|---|
| Size | 59.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cb92eabe2f89f77ba94977bbb33d8cf227edf818b177c0d32f0550a25d036cec
|
|
BLAKE2b-256 checksum How to use checksums |
03acad35ceba7ddf362ac82d18c33409fc1dace3e5f7bf2fa9533ae86408c5ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / palisade_sec-0.1.0-py3-none-any.whl
| Download URL | palisade_sec-0.1.0-py3-none-any.whl |
|---|---|
| Size | 38.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ec7f101b2fb4f234936c5434d284f8412bfd4ff63315249612ae8ecf1d9d559b
|
|
BLAKE2b-256 checksum How to use checksums |
3cf5b1e32a60b80090ad1f92c880b9da4d3b7abce83d8ba5362a82deaac5d1c3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|