NetQA — read-only AWS network discovery, knowledge graph, and Q&A
Project description
NetQA
Ask questions about your AWS network in plain English.
NetQA is an open-source, read-only CLI that discovers your AWS network (VPCs, subnets, route tables, security groups, NACLs, Transit Gateways), builds a local knowledge graph from it, and lets you interrogate it — with plain commands or with an LLM (Claude):
netqa ask "can the app subnet reach the database subnet?"
netqa ask "what breaks if I delete subnet-0a1b2c3d?"
netqa ask "what is attached to my transit gateway?"
netqa plan "isolate the staging VPC from the rest of the network"
Everything runs locally: your AWS credentials stay in your boto3 chain, state is a single JSON file on disk, and the LLM layer is optional — every check also works deterministically without an API key.
Why
AWS networking questions — "why can't these two subnets talk?", "who still references this security group?", "what's the blast radius of deleting this route?" — usually mean clicking through five consoles or writing one-off boto3 scripts. NetQA answers them from a typed graph of your real network, and the LLM answers are grounded in that graph: Claude can only call deterministic graph-query tools and narrate their results, so it can't hallucinate resources or paths that don't exist.
Features
- Read-only discovery — pulls network state with
ec2:Describe*only; never modifies anything in your account. - Knowledge graph — VPCs, subnets, route tables, SGs, NACLs, and Transit Gateways as a typed graph, persisted as one JSON file.
- Deterministic checks — reachability (same-VPC, and cross-VPC via Transit Gateway or VPC peering), dependency lookups, delete blast-radius reports. No LLM required.
- Natural-language Q&A —
ask(one-shot) andchat(multi-turn) answered by Claude calling the graph primitives as tools;--traceshows every query it ran. - Change-impact simulation — "what happens if I detach this TGW / remove this route / delete this subnet?" applied to an in-memory copy and diffed. The stored graph is never touched, and nothing is ever executed in AWS.
- Change planning — turn an intent into an ordered, approval-ready plan where every step's impact report comes from the simulator, not the model.
Install
Requires Python 3.11+.
uv tool install netqa # or: pipx install netqa
# from a source checkout:
uv tool install /path/to/netqa
Quick start
NetQA runs against your AWS account, read-only: discovery needs nothing
beyond ec2:Describe* and uses your standard boto3 credential chain — env
vars, ~/.aws/credentials, SSO, assumed roles. It never creates, modifies,
or deletes anything.
# 1. Discover your network and build the graph (read-only)
netqa discover --region us-east-1 --region us-west-2 \
--profile my-readonly-profile
# 2. Explore deterministically
netqa summary # what was discovered
netqa show vpc-0abc... # a resource and its relationships
netqa tgw tgw-0abc... # what's attached to this TGW?
netqa reach subnet-0123... subnet-0456... # cross-VPC reachability
netqa impact subnet-0123... # delete blast-radius
netqa consumers sg-0123... # who references this SG?
# 3. Ask in plain English (works offline via a keyword router; see below for LLM mode)
netqa ask "what is attached to my transit gateway?"
netqa ask "can the app subnet reach the db subnet?"
A minimal IAM policy for a dedicated read-only role:
{
"Version": "2012-10-17",
"Statement": [{ "Effect": "Allow", "Action": "ec2:Describe*", "Resource": "*" }]
}
LLM-powered Q&A with Claude
Set an Anthropic API key — in the environment or in a .env file (see
Configuration) — and ask / chat are answered by Claude
using the graph-query primitives as tools:
export ANTHROPIC_API_KEY=sk-ant-... # or put it in ~/.netqa/.env
netqa ask "why might the app tier not reach the data tier?"
netqa ask --trace "can the app subnet reach the data subnet?"
netqa chat # interactive: context carries across turns
askis one-shot;chatis a conversation — when the model needs to disambiguate ("there are four app subnets — which environment?"), just answer and the follow-up resolves against the full dialogue.--traceprints the exact graph queries the model ran to reach its answer, so every response is auditable.- Human names like "the app subnet" are resolved to resource IDs via a
search_resourcestool. - Without a key,
askfalls back to a deterministic keyword router; force a mode with--llm/--no-llm. - Default model is
claude-haiku-4-5(cheap and fast). Use--model claude-opus-4-8for the highest-quality answers and change plans, or setmodelin the config file.
Reasoning lives in code (
qa/engine.py), not in a prompt. Claude only chooses which primitive to call and narrates the structured result — keeping answers reproducible, auditable, and grounded in your actual network.
Change-impact analysis ("what happens if I…")
Predict the consequences of a change before making it. The engine applies the proposed change to an in-memory copy of the graph, re-runs reachability and dependency analysis, and diffs — reporting broken/new paths and dangling dependencies:
netqa simulate tgw-1 --op detach_tgw --vpc vpc-bbb
netqa simulate rtb-aaa --op remove_route --destination 10.1.0.0/16
netqa simulate subnet-a1 --op delete_resource
Change Impact: detach tgw-1 from vpc-bbb
Not recommended risk=high
Not recommended — 4 reachability path(s) would break.
✗ subnet-a1 → subnet-b1
✗ subnet-b1 → subnet-a1
...
Supported ops: delete_resource, remove_route, add_route, detach_tgw.
The LLM agent exposes this as a tool too, so
ask "what happens if I detach tgw-1 from vpc-bbb?" answers in natural
language. (Reachability is route-level; SG/NACL enforcement is on the
roadmap.)
Change planning from natural language
Give NetQA an intent and it drafts an approval-ready plan: an ordered list of structured change ops, each with a deterministic impact report. Claude must run the simulator on every candidate step — submissions with unsimulated steps are rejected in code, and accepted plans are re-simulated cumulatively (step N on top of steps 1..N-1). Nothing is executed; the plan is a draft for human review.
export ANTHROPIC_API_KEY=sk-ant-...
netqa plan "isolate vpc-b from the rest of the network"
netqa plan --trace "stop the app subnets reaching the data VPC"
Change Plan: isolate vpc-b from the rest of the network
Needs review risk=high steps=2 status=draft
1. remove route 10.1.0.0/16 from rtb-a medium
Drop vpc-a's route toward vpc-b before detaching.
...
2. detach tgw-core from vpc-b high
✗ subnet-b1 → subnet-a1
...
Use as a Python library
Everything the CLI does is available programmatically — pip install netqa
and import it. The same pipeline: discover → build graph → query.
from netqa import AwsDiscoverer, MockDiscoverer, build_graph, QueryEngine
# Discover from AWS (read-only) — or MockDiscoverer("fixture.json") for tests
inventories = AwsDiscoverer(regions=["us-east-1"]).discover()
graph = build_graph(inventories)
graph.save("graph.json") # reload later with NetworkGraph.load()
# Deterministic queries — no LLM involved
engine = QueryEngine(graph)
r = engine.can_subnet_reach("subnet-0123", "subnet-0456")
print(r.reachable, r.path, r.reasons)
rep = engine.delete_impact("subnet-0123") # blast radius
print(rep.recommended, rep.findings)
Simulate a change before making it:
from netqa import ProposedChange, analyze_change
impact = analyze_change(graph, ProposedChange(
op="detach_tgw", target="tgw-1", vpc_id="vpc-bbb"))
print(impact.risk, impact.summary, impact.broken_reachability)
And the LLM layer (needs ANTHROPIC_API_KEY in the environment):
from netqa import NetworkAgent, ChangePlanner
agent = NetworkAgent(engine) # optional: model="claude-opus-4-8"
result = agent.ask("what breaks if I delete subnet-0123?")
print(result.text) # grounded answer
print(result.trace) # every graph query the model ran
plan = ChangePlanner(engine).plan("isolate vpc-b from the network")
The supported surface is exactly what netqa.__all__ exports (discoverers,
graph, QueryEngine, impact analysis, agent, planner) — anything deeper is
internal and may change without notice. Pre-1.0, breaking changes to these
names bump the minor version.
Configuration
Everything is configurable via files, so day-to-day use needs zero flags.
Settings live in a TOML config; secrets live in a .env file.
Settings — ~/.netqa/config.toml (CLI flags always win;
override the location with NETQA_CONFIG):
model = "claude-haiku-4-5" # Claude model for ask/chat/plan
graph = "~/.netqa/graph.json"
regions = ["us-east-1", "us-west-2"] # AWS regions to discover
profile = "my-readonly-profile" # AWS profile (else boto3 default chain)
Every key is optional — anything missing falls back to the built-in default.
Secrets — a .env file, deliberately separate from config.toml so your
settings file is safe to share or commit. Loaded from ./.env or
~/.netqa/.env (override the location with NETQA_ENV):
# ~/.netqa/.env — then: chmod 600 ~/.netqa/.env
ANTHROPIC_API_KEY=sk-ant-...
Standard dotenv format: one KEY=VALUE per line; blank lines and #
comments are ignored; values may be quoted; an export prefix is allowed
(so the same file also works with source .env). Any variable can go here —
e.g. AWS_PROFILE or AWS_DEFAULT_REGION — but a variable already set in
your real environment is never overridden.
Precedence (highest first):
CLI flag > environment variable > ./.env > ~/.netqa/.env > config.toml > built-in default
The tool warns if the .env file is readable by other users. Check your
effective setup any time with netqa config — it shows every value
and where it came from (it never prints the key itself).
Privacy & security
- Read-only by design. The tool only calls
ec2:Describe*. It never creates, modifies, or deletes anything in your account — including insimulateandplan, which operate on an in-memory copy of the local graph. - Your credentials never leave your machine. AWS access goes through your local boto3 chain; nothing is proxied through any service.
- What the LLM sees. Only when you use
ask/chat/planwith an API key, your question and the results of the graph queries Claude requests (resource IDs, CIDRs, route/SG relationships) are sent to the Anthropic API. No AWS credentials are ever sent. Skip the API key and everything deterministic still works entirely offline. - Local state. The graph is a single JSON file (default
~/.netqa/graph.json). Delete it and the tool forgets everything.
How it works
Your AWS account (ec2:Describe* only) ┌─► QueryEngine (deterministic)
│ │ reach · deps · impact · simulate
└──► RawInventory ─► NetworkGraph ─────────────────┤
(boto3 describe_*) (NetworkX) └─► NetworkAgent (Claude tool-use)
ask · chat · plan
| Layer | Module | Responsibility |
|---|---|---|
| Discovery | discovery/ |
Pull raw network state from AWS via read-only describe_* calls. (A JSON-fixture discoverer with the identical RawInventory shape exists for tests.) |
| Graph | graph/ |
Turn raw describe_* responses into a typed NetworkX graph, serialized to JSON. |
| Q&A engine | qa/engine.py |
Deterministic graph-traversal primitives — reachability, dependencies, impact. The auditable source of truth. |
| LLM layer | qa/agent.py, qa/planner.py |
Claude calls the engine primitives as tools and narrates the results; the planner drafts simulated change plans. Optional — offline keyword fallback without a key. |
Nodes are AWS resources (keyed by resource ID); edges are typed relationships:
VPC --contains--> Subnet / RouteTable / SecurityGroup / NACL
RouteTable --associated_with--> Subnet
RouteTable --routes_to--> igw / nat / tgw / pcx / local-vpc / ...
SecurityGroup --allows--> SecurityGroup
NACL --protects--> Subnet
TransitGateway --attached_to--> VPC
VpcPeering --attached_to--> VPC (requester and accepter)
See docs/ARCHITECTURE.md for the full design.
Supported resources & roadmap
Today: VPC · Subnet · Route Table · Security Group · NACL · Transit Gateway (+ VPC attachments) · VPC Peering Connection. Other route targets (IGW/NAT/…) appear as lightweight nodes.
| Capability | Status |
|---|---|
| Read-only discovery (7 core resource types) | ✅ |
| Typed knowledge graph + JSON persistence | ✅ |
| Dependency / "who uses this" queries | ✅ |
| Route-level reachability (same-VPC & cross-VPC via TGW or VPC peering) | ✅ |
| Delete-impact (blast radius) report | ✅ |
LLM-backed natural-language Q&A + multi-turn chat |
✅ |
| Change-impact simulation ("what happens if I change X") | ✅ |
| NL change planning — intent → simulated, approval-ready plan | ✅ |
| SG + NACL enforcement layered onto reachability | 🔜 |
| ENI / Load Balancer / VPN / multi-account (Organizations) discovery | 🔜 |
| Graph diffing between discoveries ("what changed") | 🔜 |
Development
This project uses uv. One command creates the
virtualenv and installs everything from uv.lock:
uv sync
uv run pytest -q
Tests run the real builder and Q&A engine against
examples/sample_network.json, which is shaped exactly like live boto3
describe_* output — the offline path exercises the same code that runs
against AWS. The same fixture powers a no-AWS demo of the whole CLI
(testing/development only):
uv run netqa discover --fixture examples/sample_network.json
uv run netqa reach subnet-a1 subnet-b1 # cross-VPC via the sample TGW
uv run netqa ask "what is attached to tgw-1?"
To test at scale, scripts/generate_network.py emits a parameterized fixture
in the same boto3 shape — N VPCs on a central TGW, with the last --isolated
VPCs left unattached for guaranteed unreachable cases:
uv run python scripts/generate_network.py --vpcs 12 --subnets-per-vpc 4 \
--isolated 2 --out /tmp/network.json
uv run netqa discover --fixture /tmp/network.json
scripts/test_workflow.sh runs the whole tool end-to-end — generate →
discover → every CLI query → offline Q&A → change-impact simulation —
asserting on each step's output. It needs no AWS account or Anthropic key.
Contributing
Issues and pull requests are welcome. Good first areas: new resource types in
discovery, SG/NACL-aware reachability, and graph diffing between discoveries
(see the roadmap above). Please make sure uv run pytest -q and
scripts/test_workflow.sh pass before submitting.
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 netqa-0.1.0.tar.gz.
File metadata
- Download URL: netqa-0.1.0.tar.gz
- Upload date:
- Size: 50.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4ec91412ea708efd0e265201963bfdc3901263e557eee3c9b9c6c0f6b0fd8e6
|
|
| MD5 |
0c35cde6e148f16e7db8ac2b0b7c69c2
|
|
| BLAKE2b-256 |
d5f761cdb141c97c9aa0e5be1874b740c44274c29db24dc9e22ed72d2a9ed756
|
Provenance
The following attestation bundles were made for netqa-0.1.0.tar.gz:
Publisher:
release.yml on RayVenn/network-steward-oss
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netqa-0.1.0.tar.gz -
Subject digest:
f4ec91412ea708efd0e265201963bfdc3901263e557eee3c9b9c6c0f6b0fd8e6 - Sigstore transparency entry: 2083246378
- Sigstore integration time:
-
Permalink:
RayVenn/network-steward-oss@5c93f40925de81b16f32a1364ddeb5977b28e217 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/RayVenn
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c93f40925de81b16f32a1364ddeb5977b28e217 -
Trigger Event:
push
-
Statement type:
File details
Details for the file netqa-0.1.0-py3-none-any.whl.
File metadata
- Download URL: netqa-0.1.0-py3-none-any.whl
- Upload date:
- Size: 43.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e56fc7df25b5aaf4bccd6579589a8a191dcb22a32c6fb2abf1ae93c1f292eb3
|
|
| MD5 |
dc09993b00c9db16577746cbdc0fdffe
|
|
| BLAKE2b-256 |
e8377e21f17bf2fea7c522a6e9d5cb0c09c6c294d417e17ba820c8433d0eb659
|
Provenance
The following attestation bundles were made for netqa-0.1.0-py3-none-any.whl:
Publisher:
release.yml on RayVenn/network-steward-oss
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netqa-0.1.0-py3-none-any.whl -
Subject digest:
8e56fc7df25b5aaf4bccd6579589a8a191dcb22a32c6fb2abf1ae93c1f292eb3 - Sigstore transparency entry: 2083246427
- Sigstore integration time:
-
Permalink:
RayVenn/network-steward-oss@5c93f40925de81b16f32a1364ddeb5977b28e217 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/RayVenn
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c93f40925de81b16f32a1364ddeb5977b28e217 -
Trigger Event:
push
-
Statement type: