The products
Three products, one deployment engine. The separation between them is a posture block and an emitter target, not three codebases — which is the whole reason the spec was built the way it was, and the reason the security tier cannot drift away from the thing it secures.
Nerve Backend
Build and simplify your backend
A complete working backend in the customer’s own cloud account: the part people see, the part that does the work, scheduled jobs, a database and file storage. HTTPS forced, TLS 1.2 floored, anonymous storage access off from the first deployment.
posture.mode: open · all five architectures · nerve-backend.html
Nerve Secure
Secure your backend with provisioned IP addresses
The same backend with the network closed. Private endpoints on every service that has one, public network access denied at the resource, shared-key authentication disabled and resource locks on. Only the addresses you name get in, including us. This is the posture that passed penetration testing in a federal environment.
posture.mode: closed · four flags differ from open · nerve-secure.html
Nerve AI Blocker
Secure your business from AI attacks
The same private endpoints and network rules, pointed at the AI services a department stood up on its own. Every AI attack is a remote attack first: prompt injection, jailbreaks and model extraction all have to reach the endpoint before they can be any of those things. It is not a prompt filter — it removes the surface instead.
posture.mode: closed, AI roles · model, index and documents closed together · nerve-ai-blocker.html
AWS and Google support is an emitter, not a fourth product. The spec contains no provider nouns, so another cloud is a new emitter and nothing above it changes. Selling it as its own line would be selling you a file format.
The architecture library
There is no single secure architecture, because there is no single thing people build. Pick the shape you are actually building; the posture applies to all five equally.
| Architecture | For | Pieces |
|---|---|---|
| classic | A site people visit, an API behind it, somewhere to put the data | network, frontend, backend, functions, database, storage |
| api | A mobile app or partner integration. No public web tier at all | network, backend, database, storage |
| containers | You already build images and want somewhere to run them | network, cluster, registry, database, storage |
| events | Work arrives, gets queued, and something picks it up | network, queue, functions, database, storage |
| ai | A model, an index of your documents, and the documents themselves | network, model endpoint, search index, document store |
Details that fail silently
Each of these emits a template that deploys clean and then does not work. All are covered by tests, and all were found by building the library rather than by reading documentation.
- A delegated subnet will not accept cluster nodes. The application subnet is delegated to App Service only when the architecture actually contains one. The delegation cannot be removed once anything sits in the subnet.
- A function app on a shared key cannot use closed storage. The usual
AzureWebJobsStorageconnection string is a shared key, and the closed posture disables shared-key access on the very account it points at. The wiring is identity-based instead. - Private link is Premium-only on a container registry and on a Service Bus namespace. Standard plus a private endpoint is rejected at deploy time, so the posture picks the tier — and says so, because it changes the bill.
enablePrivateClusterandauthorizedIPRangesare mutually exclusive. Closed gets the private cluster; open gets the address ranges.- A Cognitive Services IP rule carries only
value, and noaction, unlike storage. The two look interchangeable and are not.
What the posture changes
Four flags. That is the entire difference between the base product and the upgrade, which is what makes the upgrade auditable: you read one table instead of diffing two deployments.
| Flag | Open | Closed | What it shuts |
|---|---|---|---|
| privateEndpoints | NO | YES | Databases, storage, registries and queues become reachable only over a private link inside your network. |
| denyPublicNetworkAccess | NO | YES | Set at the resource, not at a firewall in front of it, so a rule elsewhere cannot re-open the door. |
| disableSharedKeyAuth | NO | YES | The credential nobody rotates stops working. Access goes through identity, which can be revoked. |
| resourceLocks | NO | YES | Deleting a production estate stops being one click by one person in the wrong subscription. |
Four more are on in both, because they are defaults people regret rather than choices people make: HTTPS only, TLS 1.2 floored, anonymous blob access off, FTPS disabled.
Closing a network breaks things that were quietly depending on being open. Every service that would lose reachability is named before anything changes, so the decision is made with its cost on screen rather than discovered afterwards.
What it closes
Three parts, and they hold the same material. Any product that closes one and leaves the other two is selling a feeling rather than a posture.
| Piece | What it is | What closing it does |
|---|---|---|
| aiModel | The model endpoint your application sends prompts to. Azure OpenAI, Cognitive Services, Bedrock or Vertex. | Private endpoint, publicNetworkAccess: Disabled, local authentication
off. An automated sweep for exposed endpoints does not find one. |
| aiIndex | A vector or search index holding your documents in a second, readable form, and answering queries of its own. | Closed in the same step as the model, so going around it is not an option. |
| documents | The originals the index was built from. They outlive any one model. | Private endpoint, anonymous access off, shared keys disabled. |
Not a prompt filter
It does not read your prompts, score them, or sit in the path of an answer. Anything that does is one more thing to bypass, and it has to be reachable to work at all. This removes the surface instead: an attack fails at the network before it gets to be an AI problem.
One schema detail that fails silently. A Cognitive Services
IpRule carries only value, and no action, unlike
storage. The two look interchangeable, are not, and a template carrying the wrong one is
rejected outright.
How it connects
Nerve does not hold a credential of its own. It runs on your machine and uses the command line tool you have already signed in to, which means three things follow.
Nerve never sees your password. You sign in on Microsoft's, Amazon's or Google's own domain. The token lives where that provider's CLI puts it, and Nerve reads your account through the CLI rather than being handed a key.
There is no Nerve server in the path. Nothing about your estate is sent anywhere. The scan runs locally and the picture is drawn locally.
Revoking access is the provider's job, not ours. Sign out of the CLI, or remove the role, and Nerve can read nothing. There is no separate integration to disconnect.
Install the CLI for your provider before opening Nerve. If it is missing, Nerve will tell you which one and the command that installs it, rather than failing silently.
Amazon Web Services
1. Install and sign in
brew install awscli # macOS
winget install Amazon.AWSCLI # Windows
aws configure sso # or: aws configure
aws sts get-caller-identity # confirms you are signed in
2. Grant the permission
Nerve reads configuration, never contents. The policy below is the complete set it uses; attach it to the role or user you signed in as.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "NetworkTopology",
"Effect": "Allow",
"Action": [
"ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeRouteTables",
"ec2:DescribeInternetGateways", "ec2:DescribeNatGateways",
"ec2:DescribeSecurityGroups", "ec2:DescribeSecurityGroupRules",
"ec2:DescribeNetworkAcls", "ec2:DescribeNetworkInterfaces",
"ec2:DescribeVpcEndpoints", "ec2:DescribeVpcPeeringConnections",
"ec2:DescribeAvailabilityZones", "ec2:DescribeRegions"
],
"Resource": "*"
},
{
"Sid": "DataResourceExposure",
"Effect": "Allow",
"Action": [
"rds:DescribeDBInstances", "rds:DescribeDBClusters",
"rds:DescribeDBSubnetGroups", "dynamodb:ListTables",
"dynamodb:DescribeTable", "elasticache:DescribeCacheClusters",
"redshift:DescribeClusters", "es:ListDomainNames",
"elasticfilesystem:DescribeFileSystems"
],
"Resource": "*"
},
{
"Sid": "S3ExposureConfigurationOnly",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets", "s3:GetBucketLocation",
"s3:GetBucketPolicy", "s3:GetBucketPolicyStatus",
"s3:GetBucketPublicAccessBlock", "s3:GetEncryptionConfiguration",
"s3:GetAccountPublicAccessBlock"
],
"Resource": "*"
}
]
}
The application carries this policy verbatim, so what it asks for and what is documented here cannot drift apart.
Why not just use an AWS managed policy?
This is the first question every reviewer asks, and the honest answer is that none of the three obvious ones is right.
| Managed policy | Why not | |
|---|---|---|
| ReadOnlyAccess | NO | Grants s3:GetObject and dynamodb:Scan. That reads
data, not configuration. Nerve needs neither and will not ask for a policy
that includes them. |
| ViewOnlyAccess | NO | Covers 38 of 69 calls. S3 and EFS discovery fail entirely, so the map has holes exactly where data lives. |
| SecurityAudit | NO | Covers 61 of 69, but includes full iam:Get* and iam:List*.
Nerve never reads IAM, and asking for it is what turns a one-day review into a
three-week one. |
Google Cloud
1. Install and sign in
brew install --cask google-cloud-sdk # macOS
winget install Google.CloudSDK # Windows
gcloud auth login
gcloud projects list # confirms you can enumerate
Nerve reads one project at a time. If your account holds more than one, it asks which, and lists every project you did not pick as an unread part of the map rather than leaving it off. An account with 74 projects and one scanned is not a clean account, it is a picture of somewhere else.
2. Grant the permission
Project-level roles/viewer covers everything Nerve calls. To grant less than
that, these are the individual roles for the six things it reads:
| Role | What it lets Nerve read |
|---|---|
| roles/compute.networkViewer | VPC networks, subnets, firewall rules |
| roles/cloudsql.viewer | Cloud SQL instances and their public IP settings |
| roles/storage.legacyBucketReader | Bucket configuration and public access |
| roles/secretmanager.viewer | Which secrets exist, never their values |
| roles/run.viewer | Cloud Run services and their ingress settings |
| roles/redis.viewer | Memorystore instances |
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="user:you@example.com" \
--role="roles/viewer"
What Nerve actually runs
gcloud compute networks list --project PROJECT --format=json
gcloud sql instances list --project PROJECT --format=json
gcloud storage buckets list --project PROJECT --format=json
gcloud secrets list --project PROJECT --format=json
gcloud run services list --project PROJECT --format=json
gcloud redis instances list --region=- --project PROJECT --format=json
Every call passes --project explicitly rather than relying on
whatever gcloud config is set to. The tool's current project is a machine
preference; the project you chose is a decision, and those are not the same thing.
BigQuery is not on the map. It is read with the bq tool
rather than gcloud, so Nerve lists it as a known gap instead of implying a
project has no warehouse. Nothing to do if you do not use BigQuery.
The roles above are derived from the calls Nerve makes, not from a locked-down account we have tested against. If one is insufficient in your organisation, Nerve will name the exact permission the API asked for.
Microsoft Azure
1. Install and sign in
brew install azure-cli # macOS
winget install Microsoft.AzureCLI # Windows
az login
az account show # confirms the subscription
If your organisation requires a second factor, Nerve says so and runs the command Azure asks for rather than failing with the provider's raw error.
2. Grant the permission
The built-in Reader role. Nothing custom, nothing to define, every call an HTTP GET.
az role assignment create \
--assignee you@example.com \
--role "Reader" \
--scope /subscriptions/SUBSCRIPTION_ID
Reader is role definition
acdd72a7-3385-48ef-bd42-f606fba81ae7. Its action set is literally
["*/read"].
What Reader buys you
- Every VNet, subnet, NSG and its rules, route table, peering and service endpoint
- Private endpoints, private link services, private DNS zones and their VNet links
- SQL, Storage, Cosmos, Redis, Key Vault and App Service network configuration
- Public exposure and the evidence for it, on every data resource
What it costs you
NSG rules are read per-NSG and composed by Nerve, rather than asking Azure which rules actually apply to a given interface. Where the composition is ambiguous, Nerve says so rather than guessing.
Optional: exact effective rules
Two extra operations remove that ambiguity. Both are POSTs, and both mutate nothing.
Microsoft.Network/networkInterfaces/effectiveNetworkSecurityGroups/action
Microsoft.Network/networkInterfaces/effectiveRouteTable/action
With these, Nerve reports the rules Azure itself says apply to an interface, and the effective routes, so "can this actually reach that" stops being an inference. Grant them through a custom role alongside Reader. A reviewer reading an HTTP verb will see POST, so it is worth saying up front that neither changes anything.
What Nerve never does
This list is enforced by the application not containing the code, not by policy.
| Never called | Why |
|---|---|
| s3:GetObject | Reads object contents. Nerve needs a bucket's exposure, never its contents. |
| dynamodb:Scan | Reads table contents. Same reasoning. |
| iam:Get*, iam:List* | Nerve does not map identity. Requesting it stalls reviews for no benefit. |
| ec2:CreateFlowLogs | A mutation. Flow logs would improve the analysis and Nerve will not turn them on. |
| resource-explorer-2:* | Its complete-results path creates an index as a side effect of searching. A discovery tool that provisions infrastructure while looking around is not read-only, whatever the verb is called. |
| *:Create*, *:Update*, *:Delete*, *:Put* | Nerve contains no code path that mutates anything during discovery. |
The one exception, and it is explicit. Pressing Build this backend and then Build it creates real resources in your account and bills to whoever owns the subscription. Everything up to and including the preview changes nothing. The preview is generated without contacting your provider's control plane at all.
When it cannot read something
Nerve draws what it could not read as a hole in the map, next to what it could. A picture with a quiet gap in it is more dangerous than no picture, because you will believe the missing part was clean.
| It says | It means |
|---|---|
| You are not allowed to read this | The permission above is missing for that service. Grant it and rescan. |
| Not in use in this account | The API is not enabled. Nothing to do if you do not use that service. |
| The cloud asked us to slow down | Throttled. Rescan in a few minutes. |
| This tool cannot read it yet | A known gap, such as BigQuery. Listed rather than omitted. |
| You picked a different one | Another subscription, project or region you own and did not scan. Not a failure. |
An empty list and a refused question are never collapsed into one answer. "You have nothing here" and "I could not look" are different sentences, and Nerve will always tell you which one it means.