Metis is an open-source, agentic AI security framework for deep security code review, created by Arm's Product Security Team. It helps engineers detect subtle vulnerabilities, improve secure coding practices, and reduce review fatigue. This is especially valuable in large, complex, or legacy codebases where traditional tooling often falls short.
Metis is named after the Greek goddess of wisdom, deep thought and counsel.
-
Deep Reasoning Unlike linters or traditional static analysis tools, Metis doesn’t rely on hardcoded rules. It uses LLMs capable of semantic understanding and reasoning.
-
Deterministic Local Evidence Reviews and triage emphasize source-local analysis, language plugins, and deterministic evidence collection over broad retrieval.
-
Plugin-Friendly and Extensible Designed with extensibility in mind: support for additional languages, models, and new prompts is straightforward.
-
Issue validation Validates findings from its own analysis and third-party SAST tools, gathering evidence to reduce false positives.
-
Provider Flexibility Support for major LLM services and local models (OpenAI, Azure OpenAI, Anthropic, Gemini, AWS Bedrock, Bedrock Mantle, vLLM, Ollama, llama.cpp, LiteLLM etc.). See Set up LLM Provider.
Metis includes support for the following languages:
| Language | Review | Triage | Notes |
|---|---|---|---|
| C | CodeGraph Reachability with simple LLM fallback | Reachability with simple LLM fallback | Tree-sitter-backed CodeGraph; navigation-assisted fallback |
| C++ | CodeGraph Reachability with simple LLM fallback | Reachability with simple LLM fallback | Tree-sitter-backed CodeGraph; navigation-assisted fallback |
| Java | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| C# | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| Python | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| Rust | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| TypeScript | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| Terraform | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| Go | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| TableGen | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| Verilog | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| SystemVerilog | Language-plugin simple LLM review | Simple LLM triage | Verilog Tree-sitter code splitting; navigation-assisted triage |
| JavaScript | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| Kotlin | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| PHP | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| AArch64 Assembly | Language-plugin simple LLM review | Simple LLM triage | Tree-sitter code splitting; navigation-assisted triage |
| Jupyter Notebook | Language-plugin simple LLM review | Simple LLM triage | Python Tree-sitter code splitting; navigation-assisted triage |
For the current Reachability and simple LLM triage paths, see docs/triage-flow.md.
Metis uses a plugin-based language system, making it easy to extend support to additional languages.
It also supports multiple vector store backends, including PostgreSQL with pgvector and ChromaDB.
By default, Metis uses ChromaDB for local, no-setup usage. You can also use PostgreSQL (with pgvector) for scalable indexing and multi-project support.
After cloning the repository, you can either create a virtual environment or install dependencies system-wide.
To use a virtual environment (recommended):
uv venv
uv pip install .or install system wide using --system:
uv pip install . --systemTo install with PostgreSQL (pgvector) backend support:
uv pip install '.[postgres]'git clone https://github.com/arm/metis.git
cd metis
docker build -t metis .OpenAI (default)
Export your OpenAI API key before using Metis:
export OPENAI_API_KEY="your-key-here"Other providers
Set llm_provider.name in metis.yaml and install the matching extra:
| Provider | name |
Install | Guide |
|---|---|---|---|
| OpenAI / Azure OpenAI | openai / azure_openai |
included | — |
| Anthropic | anthropic |
uv pip install '.[anthropic]' |
docs |
| Google Gemini / Vertex | gemini |
uv pip install '.[gemini]' |
docs |
| AWS Bedrock | bedrock |
uv pip install '.[bedrock]' |
docs |
| Bedrock Mantle (Claude) | bedrock_mantle |
uv pip install '.[bedrock-mantle]' |
docs |
| vLLM | vllm |
included | docs |
| Ollama | ollama |
included | docs |
| llama.cpp | llamacpp |
included | docs |
Or install everything with uv pip install '.[all-providers]'.
Embeddings are only required when using the index tool. To use a different
provider for embeddings than for chat (e.g. Anthropic chat + OpenAI
embeddings), add a separate embedding_provider block — see
docs/providers/embedding-provider.md.
Run metis by also providing the path to the source you want to analyse:
uv run metis --codebase-path <path_to_src>
Run the security analysis across the codebase:
init
review_code
Go to your codebase path and run:
docker run --rm -it -v `pwd`:/metis metisTo pass environment variables use -e:
docker run --rm -it -v `pwd`:/metis -e "OPENAI_API_KEY=${OPENAI_API_KEY}" metisYou can pass arguments to metis:
docker run --rm -it -v `pwd`:/metis metis --non-interactive --command 'review_code' --output-file results/review_code_results.jsonMetis Configuration (metis.yaml)
Metis ships with src/metis/metis.yaml. A project may provide metis.yaml in
the working directory or select another file with --config PATH. Metis uses
the selected file as its configuration source; it does not merge that document
with the packaged YAML. When a selected file omits metis_engine.execution,
Metis uses the packaged execution graph. When it defines that section, the
section is the complete graph.
Configuration covers:
- LLM provider: chat model and provider connection settings
- Embedding provider: models and connection settings used by Index
- Engine behavior: max workers, max token length, similarity top-k
- Database connection: In the case of PostgreSQL: host, port, credentials, and schema name
- Index storage: backend-specific storage parameters for commands that still use the index.
- Capability settings: index-search and navigation limits
- Reachability: path selection, path length, sink limits, and domain hints.
Project configuration is optional when the packaged defaults are suitable.
Language Configuration
Language manifests, prompt templates, and splitter settings are packaged under
src/metis/plugins/. External language packages expose a lightweight manifest
through the metis.language_plugins entry-point group. See the
language plugin guide for the file layout, manifest
contract, and extension steps.
Metis also provides an interactive CLI with several built-in commands:
--custom-prompt PATH– optional.mdor.txtfile containing additional security-review guidance. If omitted, Metis uses.metis.mdfrom the project root when that file exists.--backend chroma|postgres– choose vector-store backend (defaultchroma).--project-schema/--chroma-dir– backend-specific knobs.--triage– afterreview_code,review_dir,review_file, orreview_patch, triage findings and annotate SARIF output.--include-triaged– include findings already triaged by Metis when running triage.--verbose– show the stages and nodes executed by the selected graph.--log-level– configure Python logging independently of graph progress.--quiet,--output-file,--output-files– control console and export output.
See the execution graph guide for stage, node, and output configuration. See adding a capability for shared runtime services and model-tool adapters.
Initializes Metis repository context. When repository memory is enabled, this
loads threat-model memory first: configured
metis_engine.threat_model.source_patterns are stored as authoritative project
data. The packaged configuration includes common SECURITY.* and threat-model
filename patterns.
Metis distills those files into repository memory with the configured LLM. The
packaged execution graph does not build the vector index. Add the index node
to initialize, or run the index command, when retrieval is needed. Optional
git-history memory can add advisory security-review lessons; it never defines
binding project scope.
Use --config PATH to select a configuration with a different threat-model
source set.
Builds the vector index used by ask, update, and nodes that use the Index
capability.
Runs the configured Review stage over the codebase. The packaged graph uses the CodeGraph and Reachability nodes. Files without CodeGraph support are skipped by Reachability and reviewed by the simple LLM fallback.
Runs the configured Review stage and limits findings to the selected directory.
Runs the configured Review stage and limits findings to the selected file.
Runs the configured Review stage for a diff. Patch analysis requires a graph
that selects simple_llm_review; the packaged Reachability node reports patch
requests as inconclusive.
Incrementally updates the index using a diff. Avoids full reindexing.
Ask questions against the indexed codebase.
Triages findings in a SARIF file and annotates each result with Metis triage metadata. You can use this command on SARIF generated by Metis or by other security/static-analysis tools. See docs/triage-flow.md for a short overview of how triage works.
Metis also supports a non-interactive mode, useful for automation, CI/CD pipelines, or scripted usage.
To use Metis in non-interactive mode, use the --non-interactive flag along with --command:
metis --non-interactive --command "<command> [args...]" [--output-file <file.json>]metis --codebase-path <path_to_src>If you prefer not to use the default ChromaDB backend, you can switch to PostgreSQL either using a local installation or the provided Docker setup.
To get started quickly, run:
docker compose up -dThis will launch a PostgreSQL instance with the pgvector extension enabled, using the credentials specified in your docker-compose.yml.
Then, run Metis with the PostgreSQL backend:
metis \
--project-schema myproject_main \
--codebase-path <path_to_src> \
--backend postgresFor embedding models above pgvector's normal-vector HNSW limit, such as
3072-dimensional embeddings, Metis automatically enables pgvector halfvec
storage for the PostgreSQL backend so HNSW indexes can still be created. You
can override this in metis.yaml:
metis_engine:
embed_dim: 3072
pgvector_use_halfvec: auto # auto, true, or false> review_file src/memory/remap.cVulnerable source code:
// Remap memory addresses from one region to another
for (uint32_t* ptr = start; ptr < end; ptr++) {
uint32_t value = *ptr;
if (value >= OLD_REGION_BASE && value < OLD_REGION_BASE + REGION_SIZE) {
value = value - OLD_REGION_BASE + NEW_REGION_BASE;
}
}Example output:
File: src/memory/remap.c
Identified issue 1: Address Remapping Loop Does Not Update Memory
Snippet:
for (uint32_t* ptr = start; ptr < end; ptr++) {
uint32_t value = *ptr;
if...
Why: In the remap_address_table function, the code is intended to adjust address references from an old memory region to a new one. However, the updated value stored in the local variable 'value' is never written back into memory at the pointer location (*ptr). This means the address entries remain unchanged, which can lead to unintended behavior if the system relies on those values being relocated correctly.
Mitigation: Update the loop so that after computing the new address, the value is written back. For example:
for (uint32_t* ptr = start; ptr < end; ptr++) {
uint32_t value = *ptr;
if (value >= OLD_REGION_BASE && value < OLD_REGION_BASE + REGION_SIZE) {
value = ((value - OLD_REGION_BASE) + NEW_REGION_BASE);
*ptr = value;
}
}
This ensures that each entry is properly updated to point to the relocated memory region.
Confidence: 1.0metis --non-interactive --command "review_code" --output-file results/full_review.jsonmetis --non-interactive \
--triage \
--command "review_patch changes.diff" \
--output-file results/review.json \
--output-file results/review.sarifmetis --non-interactive --command "triage results/review.sarif"metis --non-interactive \
--include-triaged \
--output-file results/retriaged.sarif \
--command "triage results/review.sarif"Metis is distributed under Apache v2.0 License.