Security
The browser talks to the same-origin proxy server, and the proxy server is what reaches the graph database. That means the proxy server must have network access to the target database, as described in the architecture overview. The one exception is a direct connection, where the browser sends requests to the database itself.
The Docker image serves HTTPS by default. It generates a self-signed certificate at startup when HOST is set, or uses certificates you provide, as described in HTTPS Connections. Supported Hosting lists the setups Graph Explorer supports.
Supported Hosting
Section titled “Supported Hosting”A supported deployment of Graph Explorer has both of these:
- HTTPS. Graph Explorer serves it with its own certificate, as described in HTTPS Connections, or a reverse proxy in front of Graph Explorer terminates TLS.
- Access control you provide. Authentication in front of Graph Explorer, or network rules that limit who can reach it. See Access Control.
Graph Explorer can serve plain HTTP in two cases:
- Behind a reverse proxy that terminates TLS, when only the reverse proxy can reach Graph Explorer. See Serving HTTP Behind a Reverse Proxy.
- To try Graph Explorer on your own machine, when no other machine can reach it, such as with its port published on
127.0.0.1, or withpnpm startorpnpm dev, which listen only on this machine by default. See Development.
Plain HTTP that other machines can reach isn’t supported, because database connection URLs, query text, and query results cross the network unencrypted.
Access Control
Section titled “Access Control”Graph Explorer performs no authentication and no authorization. The proxy server never checks who the caller is.
Anyone who can reach Graph Explorer can read and modify any data in the connected graph database, using whatever credentials the deployment holds. Never expose Graph Explorer publicly without an access control layer in front of it.
You own who can reach the deployment and what authenticates in front of it. Graph Explorer owns behaving correctly once a request arrives.
Access control for this application can live in one of two places. You can control who can reach the proxy server at all, through network placement and network rules you provide. Confirm your rules apply to the port you publish, because container platforms can publish ports in a way host firewalls don’t filter. You can also put something in front of it that authenticates callers.
Of the four documented deployment paths, one runs on a platform that already provides an authentication layer.
| Deployment | Platform provides an authentication layer |
|---|---|
| Docker | No |
| Amazon EC2 | No |
| ECS Fargate | No |
| Amazon SageMaker | Yes |
SageMaker is the exception because the notebook’s Jupyter proxy requires a signed-in AWS principal, and it reaches the Graph Explorer container over loopback.
The deployment guides are examples, not recommended production configurations. Review a guide’s network, access control, and IAM choices against your own security requirements before you run it in production. For the hosting setups Graph Explorer supports, see Supported Hosting. For general guidance on securing a production workload, see the Security Pillar of the AWS Well-Architected Framework.
Self-hosted Gremlin Server
Section titled “Self-hosted Gremlin Server”In Apache TinkerPop 3.x, Gremlin Server uses the gremlin-groovy script engine by default. It can run arbitrary code on the server, not only Gremlin traversals. TinkerPop treats this as intended and leaves restricting it to the server operator. Other databases built on Gremlin Server may also enable Groovy by default.
When Graph Explorer connects to a server with Groovy enabled:
- Restrict access to Graph Explorer as strictly as access to the Gremlin Server host.
- Keep Graph Explorer on an isolated network behind an authenticating layer, and never make it publicly reachable.
- Let only Graph Explorer’s proxy server reach Gremlin Server, with network rules you provide. A direct connection needs each user’s browser to reach Gremlin Server instead, which also lets every other site open in that browser reach it, even when the server listens only on
localhost. Prefer a proxy connection, and choose a direct connection only for a server where you accept that risk.
For server-side options, read Security and Protecting Script Execution in the TinkerPop reference documentation. Amazon Neptune doesn’t use the Groovy script engine.
Reference for access control layers
Section titled “Reference for access control layers”How Graph Explorer behaves on the wire, for anyone adding an access control layer in front of it.
Request headers. The proxy server reads graph-db-connection-url, aws-neptune-region, service-type, db-query-logging-enabled, and queryid. Without graph-db-connection-url, every query fails with a 400 whose body names the missing header. The proxy server never reads a client Authorization header or cookie, and never forwards either one to the database.
Responses. The proxy server passes the database’s response status and body back to the client as they arrive, error responses included. Of the database’s response headers, it passes on only content-type and content-encoding. The proxy server doesn’t follow a redirect from the database, see HTTP Redirects.
Paths. The UI lives under /explorer. It calls the API on its own origin, under the same prefix it was loaded from, with the /explorer segment cut out. A layer that renames that segment breaks the UI, see Reverse proxy misconfigured. The API paths sit next to /explorer, not under it, so a layer that forwards only /explorer/* serves a UI that loads and then fails every query.
| Path | Method |
|---|---|
/defaultConnection |
GET |
/sparql |
POST |
/gremlin |
POST |
/openCypher |
POST |
/summary |
GET |
/pg/statistics/summary |
GET |
/rdf/statistics/summary |
GET |
/logger |
POST |
/status |
GET |
Client logs. POST /logger takes a JSON body with level and message and logs it on the server. The message is limited to 10,000 characters.
Path matching. The proxy server matches paths case-insensitively, so /gremlin and /GREMLIN reach the same handler. A path rule in a layer that matches case-sensitively covers only the casing it names. The /explorer segment is the exception. The UI finds it in its own URL with a case-sensitive match, so a layer that changes its casing breaks the UI the same way renaming it does.
Health check. GET /status is the health check endpoint. It returns a fixed string and does not contact the database. GET /defaultConnection returns the default connection configuration, including the database endpoint, region, service type, and whether IAM signing is enabled.
Timeouts. Schema sync against a large graph can run for minutes. A connection has no fetch timeout unless Set a fetch timeout is checked in its Advanced options. The Default Connection uses a fetch timeout of 240000 ms. A layer with a shorter read timeout ends those requests first.
Request bodies. The proxy server accepts request bodies up to 50 MB.
Protocols. All traffic is plain HTTP request and response. Graph Explorer uses no WebSocket or SSE connections.
Origins. The UI calls the API on the origin it was served from. PROXY_SERVER_CORS_ORIGIN only matters for a different web application calling the API from its own origin, see CORS.
HTTPS Connections
Section titled “HTTPS Connections”Graph Explorer serves over HTTPS by default using a self-signed certificate. The HOST environment variable controls the hostname used in the certificate’s Subject Alternative Name (SAN). When HOST is set, the entrypoint script generates a fresh self-signed certificate when a new container starts. When HOST is not set, the entrypoint reuses the certificate files already in cert-info and stops the container if any are missing. HOST has no default for certificates. The server falls back to localhost only for the URL it prints at startup.
Certificate files
Section titled “Certificate files”The proxy server reads certificates from the following location inside the container:
/graph-explorer/packages/graph-explorer-proxy-server/cert-info/├── rootCA.key # Root CA private key├── rootCA.crt # Root CA certificate├── server.key # Server private key├── server.csr # Server certificate signing request├── server.crt # Server certificate├── csr.conf # CSR configuration template└── cert.conf # Certificate extensions configurationIf HTTPS is enabled and any of these files are missing, the server will exit with an error listing the missing files.
Keeping the certificate across container replacements
Section titled “Keeping the certificate across container replacements”When HOST is set, every new container generates its own self-signed certificate. Replacing the container, for example on an image upgrade, invalidates the certificate you trusted by hand. Restarting the same container keeps its certificate.
To keep one certificate across replacements, mount cert-info as a named volume and set HOST only on the first run:
# First run generates the certificate into the volumedocker run -p 443:443 \ --env HOST=localhost \ -v graph-explorer-certs:/graph-explorer/packages/graph-explorer-proxy-server/cert-info \ public.ecr.aws/neptune/graph-explorer
# Later containers reuse it because HOST is unsetdocker run -p 443:443 \ -v graph-explorer-certs:/graph-explorer/packages/graph-explorer-proxy-server/cert-info \ public.ecr.aws/neptune/graph-explorerThe certificate is valid for the HOST value and dates from the first run. Delete the volume to generate a new one.
Using your own certificates
Section titled “Using your own certificates”To use your own certificates instead of the self-signed ones, mount your certificate files into the cert-info directory. All five certificate files must be present (rootCA.key, rootCA.crt, server.key, server.csr, server.crt).
Important
Do not set the HOST environment variable, otherwise the entrypoint will overwrite your files with a new self-signed certificate.
docker run -p 443:443 \ -v /path/to/your/server.key:/graph-explorer/packages/graph-explorer-proxy-server/cert-info/server.key \ -v /path/to/your/server.crt:/graph-explorer/packages/graph-explorer-proxy-server/cert-info/server.crt \ -v /path/to/your/rootCA.crt:/graph-explorer/packages/graph-explorer-proxy-server/cert-info/rootCA.crt \ -v /path/to/your/rootCA.key:/graph-explorer/packages/graph-explorer-proxy-server/cert-info/rootCA.key \ -v /path/to/your/server.csr:/graph-explorer/packages/graph-explorer-proxy-server/cert-info/server.csr \ public.ecr.aws/neptune/graph-explorerServing HTTP Behind a Reverse Proxy
Section titled “Serving HTTP Behind a Reverse Proxy”When a reverse proxy in front of Graph Explorer terminates TLS, Graph Explorer can serve plain HTTP to it. Set PROXY_SERVER_HTTPS_CONNECTION=false in your environment or .env file. The proxy server then listens on PROXY_SERVER_PORT, port 80 by default, on the address set by PROXY_SERVER_LISTEN_ADDRESS: every network interface in the Docker image, and 127.0.0.1 when running from source.
Let only the reverse proxy reach that port:
- When the reverse proxy runs on the same host, publish the port on the loopback interface, such as
-p 127.0.0.1:80:80. Running from source, keep the default127.0.0.1and point the reverse proxy athttp://127.0.0.1:<port>. - When it runs on another host, limit the port to the reverse proxy with network rules you provide. Running from source, also set
PROXY_SERVER_LISTEN_ADDRESSto the address of the interface the reverse proxy connects to.
Confirm your rules apply to the port you publish. Container platforms can publish ports in a way host firewalls don’t filter.
Warning
Over HTTP, database connection URLs, query text, and query results travel in plain text between the reverse proxy and the proxy server. A client that reaches the port directly also skips any authentication the reverse proxy applies.
To try Graph Explorer on your own machine without a reverse proxy, publish the port on 127.0.0.1 so no other machine can reach it. With pnpm start or pnpm dev, the defaults already do this. See Supported Hosting.
Trusting the self-signed certificate
Section titled “Trusting the self-signed certificate”Trusting the certificate by hand suits a deployment only you use. Every user would have to repeat these steps on their own machine, and a user who clicks through the browser warning can’t confirm they reached your server. For a deployment other people use, serve a certificate their browsers already trust, either your own certificate or one on a reverse proxy that terminates TLS.
When using the default self-signed certificate, your browser will show a security warning. You can bypass this by trusting the certificate:
- Download the certificate directly from the browser. For example, if using Google Chrome, click the “Not Secure” section on the left of the URL bar and select “Certificate is not valid” to show the certificate. Then click Details tab and click Export at the bottom.
- Once you have the certificate, you will need to trust it on your machine. For MacOS, you can open the Keychain Access app. Select System under System Keychains. Then go to File > Import Items… and import the certificate you downloaded in the previous step.
- Once imported, select the certificate and right-click to select “Get Info”. Expand the Trust section, and change the value of “When using this certificate” to “Always Trust”.
- You should now refresh the browser and see that you can proceed to open the application. For Chrome, the application will remain “Not Secure” due to the fact that this is a self-signed certificate. If you have trouble accessing Graph Explorer after completing the previous step and reloading the browser, consider running a docker restart command and refreshing the browser again.
Removing the “Not Secure” warning on Chrome
Section titled “Removing the “Not Secure” warning on Chrome”For browsers like Safari and Firefox, trusting the certificate from the browser (steps above) is enough to bypass the “Not Secure” warning. However, Chrome treats self-signed certificates differently. To remove the warning on Chrome, you need to trust the root CA certificate rather than the server certificate. See the Chrome Root Store FAQ for details on how Chrome integrates with platform trust stores.
- Copy the root certificate from the running container to your local machine:
If Graph Explorer is running on a remote host (e.g., EC2), copy the file to the remote host first, then usedocker cp graph-explorer:/graph-explorer/packages/graph-explorer-proxy-server/cert-info/rootCA.crt ./rootCA.crt
scpto transfer it to your local machine. - Trust the root certificate on your machine. For macOS, open the Keychain Access app, select System under System Keychains, then go to File > Import Items… and import
rootCA.crt. - Once imported, select the certificate and right-click to select “Get Info”. Expand the Trust section, and change the value of “When using this certificate” to “Always Trust”.
- Refresh the browser. The “Not Secure” warning should be gone.
By default, the proxy server does not allow cross-origin requests. The browser reaches the proxy server’s API from the same origin it served the UI from, so CORS is not needed for the UI itself. A direct connection is different: the browser calls the database from the Graph Explorer page’s origin, so the database must allow cross-origin requests from it. In development mode, the Vite dev server proxies API requests to the Express server to maintain same-origin behavior.
PROXY_SERVER_CORS_ORIGIN is for a different case: some other web application, running at its own origin, calling the proxy server’s API directly (not through the Graph Explorer UI). Set it to the origin you want to allow.
PROXY_SERVER_CORS_ORIGIN=https://my-app.example.comTo allow multiple origins, separate them with commas:
PROXY_SERVER_CORS_ORIGIN=https://my-app.example.com,https://other-app.example.comNote
CORS headers only affect browser-initiated requests — direct API calls from scripts or other servers are not restricted by CORS. CORS is a defense-in-depth layer, not a substitute for authentication or network-level access controls. Ensure the proxy server is not exposed to untrusted networks.
Database Origin Allowlist
Section titled “Database Origin Allowlist”By default, the proxy server forwards requests to any database URL specified by the client, except a reserved cloud service or IPv6 link-local address (see Reserved Cloud Service Addresses). A host name is refused when one of its DNS answers is one of those reserved addresses, or when every answer is IPv6 link-local, unless an environment proxy is on. This default is an accepted risk, not a guarantee. You can restrict which database origins the proxy will contact by setting PROXY_SERVER_ALLOWED_DB_ORIGINS. Requests targeting an unlisted origin receive a 403 response.
On a host with AWS credentials, such as an EC2 instance profile or ECS task role, the proxy server signs requests with the host’s identity, which every user of the deployment shares. Scope that identity’s database policy first, because Neptune enforces it no matter what sends the request. Set the allowlist as well to add a second boundary in the proxy server, which also limits requests to databases that don’t use IAM authentication. See Permissions.
Note
This check applies only to requests routed through the proxy server. It doesn’t apply to direct connections, because the browser sends their requests to the database itself.
Reserved Cloud Service Addresses
Section titled “Reserved Cloud Service Addresses”The proxy server refuses a database URL whose host is an address that cloud providers reserve for their own services, or an IPv6 link-local address, and returns a 403 response. These addresses are never graph databases. The server log names the refused address, and the 403 message says the address is reserved, where an allowlist refusal names PROXY_SERVER_ALLOWED_DB_ORIGINS. To reach a refused database, use its IP address in the Database URL, or fix the host name’s DNS records. IPv6 forms that carry a refused IPv4 address are refused too.
Other link-local addresses keep working, such as a database on the host machine reached through rootless Podman’s host.containers.internal, or one behind a VPC Lattice service.
On networks that use a NAT64 prefix, a few databases can be refused because of how their translated address reads. Using the database’s IPv4 address in the URL reaches them when the proxy server’s host can reach IPv4 directly.
The proxy server checks the host as written. For a host name it checks every address the name resolves to, unless an environment proxy is on. One answer in a reserved range refuses the host, whatever its other answers are. IPv6 link-local answers are skipped and the host’s other answers are used, and the host is refused only when no other answer remains. The refusal applies whether or not the Database Origin Allowlist is set, and an allowlist entry can’t override it.
This refusal doesn’t replace the allowlist. To limit the proxy server to your databases, set PROXY_SERVER_ALLOWED_DB_ORIGINS.
With an environment proxy
Section titled “With an environment proxy”When Node’s environment proxy is on, with NODE_USE_ENV_PROXY=1 or the --use-env-proxy flag (including through NODE_OPTIONS), database requests go through the forward proxy that HTTP_PROXY or HTTPS_PROXY names. Node also reads the lowercase http_proxy and https_proxy, which take precedence. The forward proxy resolves host names, so the proxy server checks only IP literals for those requests, and refusing reserved addresses for those host names is up to the forward proxy.
Hosts listed in no_proxy, or in NO_PROXY when no_proxy is unset or empty, connect directly and get the full check.
The proxy server builds its database agents when it starts, so the .env and .env.local files in the configuration folder can’t set NODE_USE_ENV_PROXY, the proxy URLs, or no_proxy. Set them in the environment the proxy server starts with.
HTTP Redirects
Section titled “HTTP Redirects”The proxy server does not follow HTTP redirects from the database. If the database responds with a redirect (3xx status), the proxy returns an error to the client instead of following it. This prevents a compromised or misconfigured database endpoint from redirecting the proxy to an unrelated internal service.
Permissions
Section titled “Permissions”Graph Explorer enforces no permissions. It sends every query to the database as written, including queries that modify data. Permissions belong to whoever runs the deployment, at two separate layers:
- Who may use Graph Explorer is controlled by the access control layer in front of it.
- What the database allows is configured in the database by its provider or administrator.
Neither layer substitutes for the other. A read-only database policy does not stop an unauthenticated visitor from reading the graph. An access control layer does not stop a signed-in user from running mutations.
Amazon Neptune IAM database authentication is one way to control the database layer. The proxy server signs a request when it carries the aws-neptune-region header, using the AWS credentials of the proxy server’s host, such as an EC2 instance profile or ECS task role. That role is the identity Neptune checks, and every user of the deployment shares it. A request without the header goes out unsigned, and the policy does not apply to it.
The proxy server signs a request only when its Database URL starts with https://, because Neptune accepts only HTTPS connections, as described in Encrypting connections to your Amazon Neptune database with SSL/HTTPS. A request with the aws-neptune-region header and an http:// Database URL gets a 400 response, and the proxy server neither signs nor sends it. To fix it, change the Database URL to https://, or turn off IAM authentication for that Connection. Requests without the header aren’t affected, so a database served over http://, such as Gremlin Server, still works without IAM authentication.
Because the identity is shared, any user can reach any database its policy allows, not only the one the deployment was set up for. Two controls limit that, and you can use either or both:
- The identity’s database policy decides which databases and actions a signed request may use. Scope its
Resourceto the databases you intend, and its actions to the ones Graph Explorer uses: read, write, and delete data via query, cancel query, and get graph summary. Leave out write and delete to make the deployment read-only, at the cost of mutations in the query editor. Sample IAM Policies has read-only and read-write examples. - The Database Origin Allowlist decides which database origins the proxy server contacts at all, for signed and unsigned requests alike.
The policy is the stronger control, because Neptune enforces it no matter what sends the request. The allowlist adds a second boundary in the proxy server, and covers databases that don’t use IAM authentication. The proxy server also doesn’t follow redirects, so a database response can’t send it on to another destination.
Graph Explorer doesn’t limit which databases it signs for by default. Many deployments serve one person who chooses their own databases, and refusing would break them for a risk the identity’s policy already controls. This default is an accepted risk, not a guarantee. Whoever runs a shared deployment decides how far to limit it.
For the minimum Neptune permissions Graph Explorer needs, see Sample IAM Policies. The sample SageMaker lifecycle script sets the allowlist to your Neptune endpoint. That doesn’t replace the notebook instance’s IAM role policy, which Neptune checks on every signed request, so scope it as described in Minimum Database Permissions.
Caution
By default, a Neptune Notebook will have full read & write access to Neptune data.