What nable touches,
and what it never can.

Every outbound call, every file it writes, every permission it asks for. Written to be checked rather than believed: the source is Apache-2.0, and the one command below prints the same list from the copy running on your machine.

$ nable scan --dry-run
# every API call and IAM permission a scan makes, without running one

Outbound calls

The complete list. Everything else in the codebase pointing at a URL is a documentation link printed for you to click, not a request nable makes.

DestinationWhenWhat is sent
your cloud provider APIs every scan / query Read-only calls to your accounts using credentials already on the machine. AWS, Azure, GCP, and any SaaS or AI provider you connect. This is the work; nothing here reaches us.
pypi.org version check A GET for the latest published version number. No identifiers. Off with FINOPS_NO_UPDATE_CHECK=1 or air-gap mode.
us.i.posthog.com only if you opt in Anonymous usage events. Off by default. Requires NABLE_TELEMETRY=1. See the section below for the exact fields.
bench.nable.dev only if you opt in Anonymised peer benchmarks. Off by default. Requires NABLE_BENCHMARKING_ENABLED=true. Ratios only, never dollar amounts; account IDs hashed with a per-install salt.
api.anthropic.com only if you set a key The optional analyst chat. If you never set ANTHROPIC_API_KEY, nable never calls a model. Scans, detections and the overnight brief are fully deterministic and never call one regardless.
Slack / Teams / Jira / Linear only if configured Alerts and tickets you asked for, to the webhook or workspace you supplied.
Air-gap mode. FINOPS_AIRGAP=1 blocks every non-provider outbound call in one switch: no version check, no telemetry, no benchmarks. Provider APIs still work, because that is the job.

What it writes, and where

Everything is local. There is no nable-hosted database in the open-source product, so there is nowhere else for it to be.

One detail worth stating in the right order: the master key is read from the file first and the keyring second, not the other way round. On macOS a keychain read from an unsigned interpreter can pop a permission dialog, and uvx builds a fresh interpreter on every release, so a keychain-first vault prompted people on every upgrade. Reading a 0600 file is silent. Set FINOPS_VAULT_KEYCHAIN_ONLY=1 to invert it and take the prompts.

PathContentsProtection
~/.finops/ SQLite database: cost snapshots, findings, budgets, the savings ledger. Override with FINOPS_DATA_DIR. Directory 0700, owner only
~/.finops/audit.log Every tool call made against your account, locally, for your own review. Owner only
~/.finops/cache.db Cached provider responses, so repeat questions do not re-bill metered APIs. Inside the 0700 directory
~/.finops/vault.db The credentials you hand nable, encrypted. This is where they live: not in the keyring, not in a config file, not in an environment file anything else can read. Fernet (AES-128-CBC + HMAC)
~/.finops/vault.key The master key that decrypts the vault, cached as a file so a read is silent. File 0600
OS keyring A durable second copy of that master key, never the credentials themselves. macOS Keychain, Windows Credential Manager, libsecret. OS-managed
~/.config/finops/ Install ID (a random UUID), and first-run markers. No credentials. Files 0600

Telemetry, in full

Off by default. It used to be opt-out. That was the wrong default for a tool that runs on your machine against your cloud account, and it changed after someone asked the question plainly on r/selfhosted.

Sent, if you opt inNever sent
A random install UUID
Event names (e.g. scan started)
Which tools were invoked, by name
Count of connected providers
nable version, Python major.minor, install method, OS family
Error class and exception type on failure
Cost or billing figures
Cloud account IDs or ARNs
Resource names or IDs
File paths
Query content
Credentials
IP addresses (dropped server-side)
Enforced, not promised. A test walks every telemetry call site in the package and fails the build if a key that looks like money ever appears in an event. It was added because one did: an upgrade nudge briefly carried a savings figure, which the module's own policy forbade.

Turn on: NABLE_TELEMETRY=1  ·  Guarantee off: NABLE_NO_TELEMETRY=1 (wins over everything, so if you set it once in a dotfile it stays off)

Least privilege, per provider

nable only ever reads. But a credential you hand it can be more powerful than the reading it does, and that is the part worth checking. Some providers offer a billing-only permission and nable asks for exactly that. Some offer nothing narrower than your whole account. A table that graded all of them "least privilege" would be wrong in the one place it matters, so each row says which.

$ nable connect --scopes
# the same table, printed by the copy on your machine, before any credential is read
ProviderTightest credentialGradeNotes
AWS nable scan --dry-run --json prints the exact policy billing only Every action in it is a Describe, List or Get. Cost Explorer is not included unless you pass --spend, because AWS bills $0.01 per request.
Cloudflare API token, Account > Billing > Read billing only One permission. The token cannot read DNS, edit zones or touch Workers.
Datadog App key scoped to usage_read + billing_read billing only An unscoped app key inherits everything you can do in Datadog. Scoped to those two, it reads cost.
MongoDB Atlas Organization Billing Viewer billing only Cannot read a cluster, a database, or a single document.
Twilio Restricted API key, Usage read billing only Your Auth Token can send messages and buy numbers, so nable takes a Restricted API key instead when you set one.
Azure Cost Management Reader via a role Built-in read-only role. Assign it at the billing or subscription scope, not at tenant root.
GCP roles/billing.viewer + BigQuery Data Viewer via a role The second can be narrowed to the single billing-export dataset.
Snowflake A role with IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE via a role Not ACCOUNTADMIN. Reaches two metering views and nothing in your own databases.
New Relic User key minted by a read-only user via a role New Relic has no billing-only key scope. The key inherits its user's role.
Langfuse Project key pair via a role Scoped to the one project it was created in.
Databricks Account admin account-wide The billable-usage download API is account-admin only. A narrower path exists through system.billing.usage and nable does not query it yet; that gap is recorded in the manifest, not hidden.
OpenAI Admin key (sk-admin-…) account-wide The Costs API accepts nothing less, and an admin key can also manage keys and members. That is OpenAI's design, not a choice nable makes, and it is the broadest credential nable accepts.
Anthropic Admin key plus organization ID account-wide Same shape: the cost endpoint is admin-gated.
Vercel Access token, team-scoped account-wide No per-permission scopes exist. Narrow it to one team and set the shortest expiry you can live with.
OpenRouter, Modal,
Together, Replicate,
Cohere, Mistral
API key account-wide None of them offer a billing-only scope today. For OpenRouter, a standard key still returns your credit balance if the provisioning key is more power than you want to hand over.
The table is a build artifact, not a page. It lives in the source as a manifest, and the setup prompts quote its permission strings rather than retyping them, so what you are told to create and what is published here cannot drift. Tests fail the build if a new connector ships without an entry, if a row claims a billing-only scope without naming one, or if a known gap is quietly dropped. The AWS policy is generated from the same kind of manifest, and a separate test fails on any mutating verb appearing in it.

What it can and cannot do

Can

  • Read cost, usage and billing data
  • Read resource metadata and CloudWatch metrics
  • Write findings to your local database
  • Draft a change as commands you run yourself
  • Open a pull request, if you enable it
  • Send an alert to a channel you configured

Cannot

  • Delete, stop, resize or terminate anything
  • Purchase a reservation or commitment
  • Modify IAM, security groups or networking
  • Apply a change to any cloud, ever
  • Send your cost figures to us
  • Accept a secret as a tool argument
That last one is specific. An MCP tool argument passes through the model provider, so a tool that accepted a secret would route your key through a third party and break the no-egress guarantee. Credentials are read from the local environment or keyring instead, and a test enforces it.

The delete path is not a policy either. It was removed: an earlier version had a cleanup tool that could terminate resources, and a test now walks every file in the package and fails on any call that would change cloud infrastructure.

Verifying any of this

$ nable scan --dry-run          # every call and permission, nothing executed
$ nable scan --dry-run --json   # the same, as a pasteable IAM policy
$ nable connect --scopes        # the tightest credential each provider can be given
$ FINOPS_AIRGAP=1 nable scan    # provider APIs only, nothing else outbound

Source: github.com/getnable/finopsmcp, Apache-2.0. Security contact and disclosure policy: /security. If something here does not match what the code does, that is a bug and we want the report.