Skip to main content

LlamaIndex tools integration for EnigmAgent — resolve {{PLACEHOLDER}} secrets at the LLM boundary so models never see real API keys

Project description

llama-index-tools-enigmagent

CI PyPI version PyPI downloads Python License: MIT LlamaIndex EnigmAgent GitHub stars

Last week I asked a LlamaIndex agent to push a fix to a private GitHub repo. To do that, the agent needed my personal access token. I had three options, and all three were terrible: paste the token into the prompt (and into the provider's logs forever), give the agent a long-lived token it could reuse on its own at 3 a.m., or give up and do it by hand.

llama-index-tools-enigmagent is option four.

Your LlamaIndex agent emits {{GITHUB_TOKEN}}. The placeholder leaves the model and travels through the prompt, the QueryPipeline, the tool inputs, the trace — and only at the moment your tool actually needs the credential does EnigmAgent intercept the call, decrypt the real token locally with AES-256-GCM, and inject it. The plaintext exists for one event-loop tick. The model never sees it. The provider never sees it. Your trace never sees it.

pip install llama-index-tools-enigmagent

In another terminal, next to your app:

npx enigmagent-mcp --mode rest --port 3737

That's the entire install. The Python package talks to the local EnigmAgent REST server over loopback; secrets stay in the encrypted vault on disk.

Star the main project if you've ever pasted a token you regretted.


The problem (in LlamaIndex terms)

When you build a LlamaIndex agent that needs to authenticate against a real API — GitHub, OpenAI, Stripe, your own backend — you face the same impossible choice every framework faces:

Option What happens
Put the secret into the prompt It lands in the trace, in the model's context, possibly in provider logs
Bake the token into the tool at construction time The model can call the tool with arbitrary inputs and exfiltrate the secret indirectly
Use a separate HSM / vault per tool Works but every tool has to be rewritten

llama-index-tools-enigmagent is option D. Your prompt, your QueryPipeline, your trace all carry only {{PLACEHOLDER}} strings. The real value is resolved at the boundary, by a process the model cannot see, against a vault on the user's machine.


How it works

+------------------+  emits {{GITHUB_TOKEN}}  +---------------------+
| LlamaIndex agent | -----------------------> |  Tool input / call  |
|  (any LLM)       |                          |  (github.com / ...) |
+------------------+                          +----------+----------+
                                                         | before invoke (intercepted)
                                                         v
                                          +-------------------------+
                                          |      EnigmAgent         |
                                          |  detects placeholder,   |
                                          |  checks origin match,   |
                                          |  decrypts -> ghp_xxx    |
                                          +----------+--------------+
                                                     | real value
                                                     v
                                          +-------------------------+
                                          |  HTTP request to the    |
                                          |  upstream API           |
                                          +-------------------------+

The model emits a placeholder. The placeholder lives in the prompt, the pipeline, and the trace. A BaseToolSpec (or FnComponent) in your pipeline sees the placeholder right before the request leaves your process and asks the local EnigmAgent REST server to swap it for the real value — but only if the request's origin matches the domain that secret was bound to. Wrong domain → the resolver refuses.


Three usage patterns

1. EnigmAgentToolSpec — agent-callable tools (recommended)

Expose vault operations as first-class LlamaIndex tools the agent can call:

from llama_index.core.agent import ReActAgent
from llama_index.llms.openai import OpenAI
from llama_index.tools.enigmagent import EnigmAgentClient, EnigmAgentToolSpec

spec = EnigmAgentToolSpec(
    client=EnigmAgentClient(),
    default_origin="https://api.github.com",
)

agent = ReActAgent.from_tools(
    spec.to_tool_list(),
    llm=OpenAI(model="gpt-4o-mini"),
    verbose=True,
)

# The agent emits {{GITHUB_TOKEN}}. The tool resolves it at call time.
# The model NEVER sees the value.
agent.chat("Use the GitHub API with header Authorization: Bearer {{GITHUB_TOKEN}} to list my repos")

EnigmAgentToolSpec exposes three operations:

Tool Purpose
resolve_placeholder(name, origin) Decrypt one secret
substitute_placeholders(text, origin) Walk a string, swap every {{NAME}}
list_placeholders(text) Extract placeholder names without resolving (useful for dry-runs)

2. enigma_substitute — QueryPipeline transformer

Drop into a QueryPipeline so every value flowing through gets {{PLACEHOLDER}} resolved before the LLM stage:

from llama_index.core.query_pipeline import QueryPipeline, FnComponent
from llama_index.llms.openai import OpenAI
from llama_index.tools.enigmagent import EnigmAgentClient, enigma_substitute

sub = FnComponent(
    fn=enigma_substitute(EnigmAgentClient(), origin="https://api.openai.com"),
)

pipe = QueryPipeline(chain=[sub, OpenAI(model="gpt-4o-mini")])

# {{OPENAI_KEY}} is resolved right before OpenAI is invoked.
pipe.run(input="Bearer {{OPENAI_KEY}}")

3. enigma_secret — drop-in SecretStr replacement

For LlamaIndex components that take an API key directly, resolve once at construction time:

from llama_index.llms.openai import OpenAI
from llama_index.tools.enigmagent import enigma_secret

api_key = enigma_secret("OPENAI_KEY", origin="https://api.openai.com")

llm = OpenAI(api_key=api_key.get_secret_value())

The plaintext lives only inside the SecretStr and only inside the OpenAI instance — never in your source, never in your env, never in the prompt.


Configuration

EnigmAgentClient defaults to http://localhost:3737. Override:

client = EnigmAgentClient(
    base_url="http://127.0.0.1:9999",      # custom port
    timeout=5.0,                            # in seconds
    shared_secret="my-loopback-token",      # sent as X-EnigmAgent-Auth header
)

To run the EnigmAgent REST server with a shared secret:

npx enigmagent-mcp --mode rest --port 3737 --auth my-loopback-token

The vault

This package is a thin client. The real work — Argon2id key derivation, AES-256-GCM encryption, origin binding, audit logging — lives in EnigmAgent, the npm package that backs it. To create or edit your vault, see the main README. A typical workflow:

# Create a vault interactively (one-time)
npx enigmagent-mcp --new-vault ./my.vault.json

# Add a secret bound to a domain
npx enigmagent-mcp --vault ./my.vault.json --add GITHUB_TOKEN ghp_xxx --origin https://api.github.com

# Run as REST server next to your LlamaIndex app
npx enigmagent-mcp --mode rest --port 3737 --vault ./my.vault.json

Security model

  • Loopback only. The REST server binds to 127.0.0.1. Only processes on the same machine can reach it.
  • Origin binding. Every secret is bound to one or more origins (e.g. https://api.github.com). Resolving a secret for a different origin is refused.
  • Argon2id + AES-256-GCM. The vault file is encrypted at rest with a passphrase-derived key.
  • No plaintext in logs. Resolved values exist only in the memory of the process making the upstream HTTP call, for the duration of that call.
  • Optional shared secret. Pass --auth to require an X-EnigmAgent-Auth header on every REST call, so unauthorised local processes can't query the vault.

Full threat model: EnigmAgent THREAT_MODEL.md


Compatibility

  • Python: 3.10, 3.11, 3.12, 3.13
  • llama-index-core >= 0.11
  • pydantic >= 2
  • Any LLM provider (OpenAI, Anthropic, Mistral, local), any tool

Roadmap

  • Auto-rewrite tool inputs in the agent middleware (currently the agent must explicitly call substitute_placeholders or wrap calls in enigma_substitute)
  • Workflow event helper for the new LlamaIndex Workflow API
  • Sister package for LlamaIndex Settings (vault-backed default LLM keys)
  • Upstream proposal to LlamaHub once this package has real users

PRs welcome.


See also


License

MIT (c) 2026 Francisco Angulo de Lafuente

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

llama_index_tools_enigmagent-0.1.0.tar.gz (8.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

llama_index_tools_enigmagent-0.1.0-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

File details

Details for the file llama_index_tools_enigmagent-0.1.0.tar.gz.

File metadata

File hashes

Hashes for llama_index_tools_enigmagent-0.1.0.tar.gz
Algorithm Hash digest
SHA256 962ad72029632795f8bf5490b1766bc058bc339c42ca76e8e33f225f9755ffdd
MD5 fb731abc4b67b8c9d16127fc7516c542
BLAKE2b-256 b093dc807005ffb3b2ff57b12a7bc3d8aa551db91429829cc90039c5211f82d1

See more details on using hashes here.

File details

Details for the file llama_index_tools_enigmagent-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for llama_index_tools_enigmagent-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 85f9f9f5444be83ce9cd7cf15b94371d72911ff7544bc572dcfdf677f62c4c9b
MD5 7f8f7dce57969a1d32936cdc329bce04
BLAKE2b-256 88a46037a20edf86061a946549e6d7b5bc3b767f59b1c90577018363c366cdc3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page