Altair GraphQL Client 8.5.11 on AWS User Guide
Overview
This guide covers launching and using the Altair GraphQL Client 8.5.11 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Altair GraphQL Client is an open source GraphQL IDE: you build, send and inspect GraphQL queries, mutations and subscriptions, with autocompletion and inline documentation from live schema introspection, custom headers, environments, pre-request and post-request scripts and a searchable history. The self-hosted build is a static web application: every GraphQL request is sent by your browser, straight to the endpoint you enter. The server only hands out the application files, so there is no backend, no database and nothing to keep running except a web server.
Upstream self-hosted Altair is a public web page with no login. The cloudimg image turns it into a private IDE for your team:
- Release 8.5.11. Altair 8.5.7 and earlier are affected by GHSA-752m-7p75-49gf, where a crafted plugin sandbox URL could run attacker JavaScript in the web site that serves Altair. 8.5.11, the newest release, carries the fix.
- Plugin sandbox kept off your instance. As extra protection, the instance does not serve Altair's plugin sandbox page at all. Altair then runs any plugin you choose to add in the Altair project's own hosted sandbox, a separate web origin, so plugin code can never run in your instance's origin.
- HTTPS from the first boot. nginx serves Altair on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A password unique to your instance. HTTP Basic Auth is required for the user
admin. The password is generated on first boot and written to/var/lib/cloudimg/altair-credentials.txt(root only); nginx stores only a bcrypt hash of it. No password is baked into the image. - Fail closed. nginx refuses to start until first boot has generated the certificate and the password, so the IDE is never served without a login.
- Hardened defaults. Requests for any host name the instance does not serve are dropped, only
GETandHEADare accepted, cross site requests and framing are refused, nginx runs in a systemd sandbox and is blocked from the instance metadata service, and the AMI requires IMDSv2. The optional Altair cloud account is switched off, so no account token is ever stored for your instance's address. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Altair GraphQL Client 8.5.11 (the project's own altair-static build in /var/www/altair, checked file by file against the published package), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. There is no Node.js runtime.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (Altair) and TCP 80 (redirects to HTTPS) from your own networks only.
- The GraphQL APIs you want to work with must be reachable from your browser, and must allow requests from your Altair address (CORS), because the browser sends them.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Altair GraphQL Client 8.5.11 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Altair itself needs very little, because the work happens in your users' browsers), your key pair, and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=altair}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Altair GraphQL Client 8.5.11 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot generates the certificate and the password, then nginx starts:
systemctl is-active nginx altair-graphql-firstboot
sudo test -f /var/lib/cloudimg/altair-graphql-firstboot.done && echo FIRSTBOOT_DONE
Both units are active and the first boot marker is present:
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network:
sudo ss -lntp | awk 'NR>1 {print $4}' | grep -v '^127\.0\.0\.5' | sort
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
[::]:22
[::]:443
[::]:80
Step 4: Get your URL and password
sudo cat /var/lib/cloudimg/altair-credentials.txt
The file is readable by root only and holds the URL, the user name and this instance's password:
ALTAIR_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
ALTAIR_USERNAME=admin
ALTAIR_PASSWORD=<ALTAIR_PASSWORD>
The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve Altair under your own DNS name, see Step 9.
Step 5: Sign in from your browser
Open the ALTAIR_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 9). Sign in as admin with the password from Step 4. The browser remembers the login for the session.
Altair opens as a full GraphQL IDE: the URL bar with the HTTP method at the top, the query editor on the left with tabs for Query, Pre-request, Post-request and Auth, and the Result pane on the right. The left rail holds headers, request handlers, history, extensions and collections; the cog at the top right opens the settings. A banner may offer the optional Altair AI plugin; you can close it.

Step 6: Send a query
Enter your GraphQL endpoint in the URL bar. This walk through uses the public demo API https://countries.trevorblades.com/. Altair introspects the schema, so the editor autocompletes field names. Type a query and press Send Request (or Ctrl+Enter):
query CountryLookup {
country(code: "GB") {
name
native
capital
currency
languages { name }
}
}
The result appears on the right with the HTTP status and the time taken:

Docs opens the schema explorer built from introspection: the root types, every field with its arguments and description, and a search box:

Step 7: Environments and scripts
Pre-request scripts run in your browser before each request. Tick Enable pre-request script on the Pre-request tab. Here a script sets the environment value code, and the query uses it as country(code: "{{code}}"), so the same query now returns Japan:
altair.helpers.setEnvironment("code", "JP");
altair.log("cloudimg pre-request script ran");

Use environments (the No environment menu at the top) to keep base URLs and tokens for each of your APIs, and the Auth tab or Set Headers to send an Authorization header. Your queries, environments and history are stored in your own browser, for this instance's address only; nothing is stored on the server.
Step 8: Check the security posture from the command line
Without a login the IDE is refused, port 80 redirects to HTTPS, the health check stays open for load balancers, and a request for a host name the instance does not serve is dropped without an answer:
H=$(sudo grep '^ALTAIR_URL=' /var/lib/cloudimg/altair-credentials.txt | sed -E 's#^ALTAIR_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'health -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/healthz"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve "evil.example:443:127.0.0.1" "https://evil.example/" || true
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/" | grep -o '401 Authorization Required'
http:// -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
no login -> HTTP 401
health -> HTTP 200
unknown host -> 000
401 Authorization Required
000 means nginx closed the connection without answering, which is how it treats any host name it does not serve.
With the password, Altair is served, and the plugin sandbox page is not (HTTP 404, including the address used by the GHSA-752m-7p75-49gf proof of concept). The password is passed to curl on standard input, so it never appears in the process list or your shell history:
sudo bash -c '
F=/var/lib/cloudimg/altair-credentials.txt
P=$(grep "^ALTAIR_PASSWORD=" $F | cut -d= -f2-)
H=$(grep "^ALTAIR_URL=" $F | sed -E "s#^ALTAIR_URL=https://([^/]+)/.*#\1#")
auth() { printf "user = \"admin:%s\"\n" "$P"; }
auth | curl -K - -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w "with login -> HTTP %{http_code}\n" "https://$H/"
auth | curl -K - -sk --resolve "$H:443:127.0.0.1" "https://$H/" | grep -o "<title>[^<]*</title>"
auth | curl -K - -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w "plugin sandbox -> HTTP %{http_code}\n" "https://$H/iframe-sandbox/?sandbox_type=plugin"
auth | curl -K - -skI --resolve "$H:443:127.0.0.1" "https://$H/" | grep -iE "^(x-frame-options|content-security-policy):"
'
with login -> HTTP 200
<title>Altair</title>
plugin sandbox -> HTTP 404
x-frame-options: SAMEORIGIN
content-security-policy: frame-ancestors 'self'
nginx keeps only a bcrypt hash of the password; the plain text is in the root only credentials file:
sudo sed -E 's/\$2y\$12\$.*/$2y$12$<bcrypt-hash>/' /etc/nginx/.altair-htpasswd
sudo stat -c '%a %U:%G %n' /etc/nginx/.altair-htpasswd /var/lib/cloudimg/altair-credentials.txt
admin:$2y$12$<bcrypt-hash>
640 root:www-data /etc/nginx/.altair-htpasswd
600 root:root /var/lib/cloudimg/altair-credentials.txt
The certificate was generated on this instance and names its own addresses:
H=$(sudo grep '^ALTAIR_URL=' /var/lib/cloudimg/altair-credentials.txt | sed -E 's#^ALTAIR_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
nginx cannot reach the instance metadata service, and it will not start until first boot has finished:
systemctl show nginx -p IPAddressDeny | grep 169.254.169.254
systemctl cat nginx | grep -E '^ExecStartPre=/usr/bin/test'
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
ExecStartPre=/usr/bin/test -f /var/lib/cloudimg/altair-graphql-firstboot.done
ExecStartPre=/usr/bin/test -s /etc/nginx/.altair-htpasswd
nginx only serves files: it forwards nothing, so a request that tries to use it as a proxy towards the metadata address gets no answer at all, never instance metadata:
curl -s -o /dev/null -w 'proxy request for 169.254.169.254 -> %{http_code}\n' -x http://127.0.0.1:80 http://169.254.169.254/latest/meta-data/ || true
proxy request for 169.254.169.254 -> 000
nginx also refuses a request whose Origin is another web site (HTTP 403), because browsers resend Basic Auth credentials on cross site requests:
H=$(sudo grep '^ALTAIR_URL=' /var/lib/cloudimg/altair-credentials.txt | sed -E 's#^ALTAIR_URL=https://([^/]+)/.*#\1#')
curl -sk --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' -d x=1 "https://$H/" | grep -o '403 Forbidden'
403 Forbidden
Step 9: Your own DNS name and certificate
Point a DNS record at the instance, then set the name Altair is served as. This mints a new self signed certificate for the name, updates nginx and the URL in the credentials file, and keeps the name across reboots:
sudo altair-set-hostname <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running altair-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/ssl/altair/altair.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/altair/altair.key
sudo nginx -t && sudo systemctl reload nginx
Your browser keeps Altair's saved queries and environments per address, so they do not carry over when you change the name.
Step 10: Change the password or add users
The login is an nginx Basic Auth file. To set a new password for admin, hand it to htpasswd on standard input (printf is a shell builtin, so the password never appears in the process list; start the line with a space so most shells keep it out of your history), then reload nginx:
printf '%s\n' '<new-password>' | sudo htpasswd -i -B -C 12 /etc/nginx/.altair-htpasswd admin
sudo systemctl reload nginx
Running sudo htpasswd -B -C 12 /etc/nginx/.altair-htpasswd <user> without -i prompts for the password instead. Use it with another user name to give a colleague their own login, and sudo htpasswd -D /etc/nginx/.altair-htpasswd <user> to remove one. Then update or delete ALTAIR_PASSWORD in /var/lib/cloudimg/altair-credentials.txt so it does not hold an old password.
Step 11: Server components
| Component | Path |
|---|---|
| Altair static build | /var/www/altair |
Bootstrap (AltairGraphQL.init) |
/var/www/altair/cloudimg-init.js |
| Licence, version record and file checksums | /usr/share/doc/altair-static/ |
| nginx site | /etc/nginx/sites-available/cloudimg-altair |
| Basic Auth file | /etc/nginx/.altair-htpasswd (bcrypt) |
| Certificate and key | /etc/ssl/altair/ |
| First boot script | /usr/local/sbin/altair-graphql-firstboot.sh |
| Host name helper | /usr/local/sbin/altair-set-hostname |
| Credentials | /var/lib/cloudimg/altair-credentials.txt (mode 0600) |
The published files match the altair-static 8.5.11 package byte for byte:
head -1 /usr/share/doc/altair-static/VERSION
cd /var/www/altair && grep -v 'index.html.upstream' /usr/share/doc/altair-static/altair-static-8.5.11.sha256sums | sha256sum -c --quiet - && echo "all files match the 8.5.11 package"
cat /var/www/altair/cloudimg-init.js | grep AltairGraphQL
command -v node || echo "no node runtime"
nginx -v 2>&1
altair-static 8.5.11
all files match the 8.5.11 package
AltairGraphQL.init({ baseURL: "/", disableAccount: true });
no node runtime
nginx version: nginx/1.24.0 (Ubuntu)
Step 12: Security recommendations
- Allow ports 22, 80 and 443 in the security group from your own networks only.
- Keep the per instance password secret, and rotate it if it may have been shared (Step 10).
- Use your own DNS name and a certificate your clients trust (Step 9).
- Altair sends requests from the browser of whoever is signed in, so it can reach anything that browser can reach. Only add plugins you trust; they run in the Altair project's hosted sandbox and load their code from the npm registry.
- Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
Altair GraphQL Client is open source under the MIT licence; there is no per user or per CPU licence fee. The licence text is in /usr/share/doc/altair-static/LICENSE. The AMI price covers cloudimg's hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA