ByteStash 1.6.0 on AWS User Guide
Overview
This guide covers launching and verifying ByteStash 1.6.0 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Node.js & Developer and API Tools AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other products in the listing are described on the listing's guide hub.
ByteStash is a self-hosted code-snippet manager. You store reusable snippets, each with one or more files, a description in Markdown and categories, and find them again with full-text search, syntax highlighting (the Monaco editor) and Markdown and Mermaid rendering. Snippets are private to their owner unless the owner marks one public or creates a share link. Every account can create API keys for the REST API and for ByteStash's remote MCP endpoint, so AI assistants such as Claude can search, read and save snippets.
The image runs ByteStash 1.6.0, built by cloudimg from the official 1.6.0 source with Node.js 22.23.4. ByteStash 1.6.0 and the releases before it fixed three security problems that earlier images carried: 1.5.14 stops a signed-in user from overwriting another user's snippet, and 1.6.0 stops recycled (deleted) public snippets from being served by the public API and marks the session cookie Secure behind HTTPS. The build also updates the libraries npm audit flags in the production tree, so the server has no known advisory (details below). The base is a fully patched Ubuntu 24.04 LTS, and unattended security upgrades stay on for your instance.
What is on the image:
- ByteStash 1.6.0 (
bytestash.service) running as the unprivilegedbytestashuser on127.0.0.1:5000only, with its SQLite database in/var/lib/bytestash - nginx publishing ByteStash over HTTPS on port 443 with a certificate created on your instance's first boot; port 80 redirects to HTTPS
- One administrator account,
admin, with a password created on your instance's first boot, and a JSON Web Token (JWT) signing secret unique to your instance bytestash-set-hostname, a helper to serve ByteStash under your own DNS name, andcloudimg-bytestash-probe, a sign-in round-trip test- The build record (
BUILD-INFO.txt), thenpm auditresults, the licences and the complete ByteStash 1.6.0 source archive in/usr/share/doc/cloudimg-bytestash/
Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3
Security posture
- No open sign-up and no first-visitor race. A fresh ByteStash install lets anybody who reaches it register the first account. On this image first boot creates the database and the
adminaccount before ByteStash is allowed to start for the first time, so registration is already closed when the instance first answers. Sign-up stays closed (ALLOW_NEW_ACCOUNTS=false), anonymous mode is off, andadminis the only account until you add more (see Maintenance). A check before every start refuses to run ByteStash or nginx until first boot has finished. - Per-instance secrets. The
adminpassword, the JWT signing secret that protects every session and API sign-in, and the TLS key are generated on your instance's first boot. No database, password, secret or key ships in the image. The password is in/root/bytestash-credentials.txt(mode 0600, root only); ByteStash stores passwords only as bcrypt hashes. - Loopback only. ByteStash listens on
127.0.0.1. nginx on 443 is the only way in. - HTTPS on first boot. nginx serves ByteStash with a TLS key and self-signed certificate created on your instance (TLS 1.2 and 1.3 only). Port 80 only redirects to HTTPS. The session cookie is
Secure,HttpOnlyandSameSite=Lax. - Fixed host name. ByteStash is served under a name taken from the instance metadata at first boot (or one you set), never from the request. Requests for any other host name are dropped, and nginx refuses cross-site form posts and WebSocket upgrades. API and MCP clients, which send no browser
Originheader, are not affected. - Real client addresses. nginx passes the connecting address to ByteStash and ignores any
X-Forwarded-Forheader a client sends, and ByteStash trusts forwarded headers only from nginx on the same instance. - No metadata access. ByteStash fetches an identity provider's configuration when you turn on single sign-on, so
bytestash.serviceandnginx.serviceare blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). Amazon Time Sync keeps working. - Least privilege. ByteStash runs with no Linux capabilities and a read-only system (
ProtectSystem=strict) except its own data directory; the application files are owned by root. - No scheduled firmware-metadata refresh. The
fwupdfirmware updater stays installed, but its LVFS metadata refresh (fwupd-refresh.timerand.service) is masked, so an LVFS outage never marks your instance unhealthy at boot; runsudo fwupdmgr refreshyourself if you use it. - IMDSv2 required. The AMI is registered with
ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests. - Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.
What anyone who can reach the instance can read
ByteStash serves a few pages without signing in, by design. Keep them in mind when you decide who may reach port 443:
- Public snippets.
/public/snippetsin the web UI and/api/public/snippetsin the API list and serve the snippets a user has explicitly marked public, including each file's raw text. Private snippets are never listed there, and since 1.6.0 neither are snippets in the recycle bin. - Share links. A share link (
/s/<id>, and its embed/embed/<id>for other web pages) shows one snippet to whoever has the link, unless the owner ticked Require authentication when creating it. Share links can expire. - API reference.
/api-docsserves ByteStash's OpenAPI documentation.
Everything else (your snippets, API keys, settings and the admin pages) needs a signed-in session or an API key.
Known advisories and how the build handles them
- Server dependencies.
npm auditon the upstream 1.6.0 lockfile lists five advisories in the server's production tree. This build updates, within their own release lines,body-parser(a denial-of-service on every JSON request),zod(used by the MCP tool definitions), andhono,fast-uriandip-address(used by the MCP library). After the changesnpm audit --omit=devreports no advisory for the server; the result ships inserver-npm-audit-production.json. - Web interface. The browser bundle is built with
dompurify3.4.17, which fixes a critical sanitiser bypass that the upstream lockfile's version has. One low advisory remains: KaTeX, which renders maths in Mermaid diagrams, can be steered by an existing prototype-pollution bug elsewhere (GHSA-238p-pmpm-9mq7); the fix needs Mermaid 12, a major release ByteStash has not adopted yet. It runs only in the browser, never on the server; the result ships inclient-npm-audit-production.json. - Build tools. The web interface is compiled at build time; its development tools (including
vite) and the compiler used for the native modules are removed afterwards and never ship.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- A subnet that gives the instance a public IPv4 address (or private connectivity from where you will use ByteStash)
Recommended instance type: m5.large. ByteStash is a single Node.js process with SQLite storage; a smaller burstable type also works for a small team.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| ByteStash 1.6.0 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened Node.js & Developer and API Tools AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose ByteStash 1.6.0 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Security group: allow SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address and 80/443 to the people and API clients that use ByteStash.
- Choose your VPC and a subnet that gives the instance a public IP, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this on your own workstation. It creates a security group that allows SSH and HTTPS from your management address only, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR with your own:
SG=$(aws ec2 create-security-group --group-name bytestash \
--description "ByteStash" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
for p in 22 80 443; do
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port "$p" --cidr <your-mgmt-cidr>
done
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--associate-public-ip-address \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bytestash}]'
You do not need --metadata-options: the AMI already makes IMDSv2 required.
Step 2: Connect and read your credentials
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot finishes within a minute of launch. It creates the certificate, the JWT signing secret, the database and the admin account, then lets ByteStash and nginx start. Your credentials are in a root-only file:
sudo cat /root/bytestash-credentials.txt
# ByteStash - generated on this instance's first boot by bytestash-firstboot.service.
# Unique to this instance; nothing here shipped in the image. Store it somewhere safe.
BYTESTASH_URL=https://ec2-54-81-216-2.compute-1.amazonaws.com/
BYTESTASH_USERNAME=admin
BYTESTASH_ADMIN_PASSWORD=<BYTESTASH_ADMIN_PASSWORD>
#
# Open BYTESTASH_URL and sign in as admin (self-signed certificate minted on this instance; port 80 redirects).
# Sign-up is closed: nobody can register an account. After first login change this password in the user menu
# (your account name, top right) -> Change password. REST API and MCP clients use an API key: user menu -> API Keys.
# Change the name ByteStash is served as: sudo bytestash-set-hostname <your-dns-name>
# Restrict 80/443 in the security group to the people and clients that need them.
# Guide: https://www.cloudimg.co.uk/guides/bytestash-1-6-0-on-ubuntu-24-04-aws/
BYTESTASH_ADMIN_PASSWORD holds your instance's own password. Keep it somewhere safe, then change it after your first sign-in (see Maintenance).
Step 3: Confirm the services are running
First boot, ByteStash and nginx are active, ByteStash runs as its own unprivileged user, and the start guard (which runs before every start of ByteStash and nginx) passes:
systemctl is-active bytestash-firstboot.service bytestash.service nginx.service
ps -o user:12=,comm= -p "$(systemctl show -p MainPID --value bytestash.service)"
sudo /usr/local/sbin/bytestash-guard
active
active
active
bytestash node
GUARD_OK
If a service ever refuses to start, run the guard: it names the exact reason.
These are the TCP ports the instance listens on:
sudo ss -Hltn | awk '{print $4}' | sort -u
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:5000
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
[::]:443
[::]:80
ByteStash (5000) listens on loopback only; nginx publishes it on 443. The loopback port 53 entries are Ubuntu's local stub resolver (systemd-resolved).
The versions and the build record:
node --version
grep -E '^(ByteStash|source|tag commit|audit|database)' /usr/share/doc/cloudimg-bytestash/BUILD-INFO.txt
v22.23.4
ByteStash 1.6.0 on Ubuntu 24.04 - cloudimg build record
source https://github.com/jordan-dalby/ByteStash/archive/refs/tags/v1.6.0.tar.gz
source sha256 6e534ea3b82e01e043ab55d042cb1ebab78e204d63ac35f5822bfd414c96f8f6
tag commit da3a7afe93cf06c7c01d3cd09e4a2ea52aff4e6a
audit server: 0 advisories; web UI bundle: katex + mermaid LOW only (GHSA-238p-pmpm-9mq7)
database SQLite (/var/lib/bytestash/snippets/snippets.db); PostgreSQL mode off
Step 4: Sign in
Browse to the BYTESTASH_URL from Step 2, for example https://ec2-54-81-216-2.compute-1.amazonaws.com/. The certificate is self-signed, so your browser warns once; accept it, or install your own certificate (Step 8). ByteStash shows its sign-in page, without a link to register:

Sign in as admin with BYTESTASH_ADMIN_PASSWORD. Your snippet list opens; a new instance has none yet. The screenshot below is from an instance with a few snippets added. The Private and Public switch under the name toggles between your own snippets and the public ones, and the small vdev label next to the name is ByteStash's version tag, which upstream fills in only for its own container builds (the version is in the build record above):

The image ships a round-trip test that signs in through nginx as a browser would. It proves that the API refuses anonymous requests, that sign-up and a wrong password are refused, and that your admin password returns an administrator session that can read your snippets:
sudo /usr/local/sbin/cloudimg-bytestash-probe
PROBE_OK anonymous API 401, sign-up 403, wrong password 401, admin JWT (is_admin), verify valid, authenticated API 200 at https://ec2-54-81-216-2.compute-1.amazonaws.com/
You can confirm sign-up is closed from the command line too. Port 80 only redirects to HTTPS, ByteStash reports that new accounts are not allowed, a registration attempt is refused, and the account table holds only admin. The block stops with an error if any answer is not the expected one:
H=$(sudo sed -n 's/^set $bs_host "\(.*\)";$/\1/p' /etc/nginx/cloudimg-bytestash-host.conf)
C=(--cacert /etc/ssl/cloudimg/bytestash.crt --resolve "$H:443:127.0.0.1" --resolve "$H:80:127.0.0.1")
R=$(curl -s "${C[@]}" -o /dev/null -w '%{http_code} %{redirect_url}' "http://$H/"); echo "$R"
A=$(curl -s "${C[@]}" "https://$H/api/auth/config" | jq -c '{allowNewAccounts, hasUsers, disableAccounts}'); echo "$A"
G=$(curl -s "${C[@]}" -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -d '{"username":"someone","password":"Some-Password-123"}' "https://$H/api/auth/register"); echo "register: $G"
U=$(sudo sqlite3 "file:/var/lib/bytestash/snippets/snippets.db?mode=ro" "SELECT username FROM users;"); echo "$U"
[ "$R" = "301 https://$H/" ] && [ "$A" = '{"allowNewAccounts":false,"hasUsers":true,"disableAccounts":false}' ] && [ "$G" = 403 ] && [ "$U" = admin ]
301 https://ec2-54-81-216-2.compute-1.amazonaws.com/
{"allowNewAccounts":false,"hasUsers":true,"disableAccounts":false}
register: 403
admin
Step 5: Create and share a snippet
Choose New snippet, give it a title, add one or more files with their language, optional categories and a Markdown description, and save. Open a snippet to read it with syntax highlighting, copy a file, or edit it:

A snippet is private to you unless you tick Make snippet public, which lists it under Public snippets for anybody who can reach the instance. To show one snippet to someone without making it public, open its share menu and choose Create Share Link: the link can Require authentication and can expire (for example 2d). Move to Recycle Bin keeps a snippet for 30 days before it is deleted, and from 1.6.0 a recycled snippet is no longer served publicly.
Step 6: Use the REST API with an API key
Create an API key from the user menu (your account name, top right), API Keys, and copy it; ByteStash shows it once. Scripts send it in an x-api-key header. On your instance you can try it like this (put your key in a file readable only by you first, so it never appears on a command line):
umask 077; printf 'x-api-key: %s\n' '<your-api-key>' > ~/.bytestash-key
curl -s --cacert /etc/ssl/cloudimg/bytestash.crt -H @$HOME/.bytestash-key \
"https://<your-dns-name>/api/snippets?search=python" | jq '.data[] | {id, title}'
The full API is described at https://<your-dns-name>/api-docs.
Step 7: Connect an AI assistant over MCP
ByteStash serves a remote MCP (Model Context Protocol) endpoint at https://<your-host>/mcp. Add it to an MCP client (Claude, ChatGPT, Perplexity or any client that supports remote MCP servers over HTTP) with your API key as the bearer token, for example in a client configuration file:
{
"mcpServers": {
"bytestash": {
"url": "https://<your-dns-name>/mcp",
"headers": { "Authorization": "Bearer <your-api-key>" }
}
}
}
The assistant can then list, search, read, create, update and recycle your snippets with the six ByteStash tools. The endpoint refuses requests without a valid key. Your client must trust the instance's certificate, so install your own certificate first (Step 8). An assistant has the same access as your account, so give it a key of its own and delete the key under API Keys in the user menu when you no longer need it.
Step 8: HTTPS certificate and host name
The certificate was created on your instance's first boot for the instance's public DNS name, public IP, private IP and localhost. The key, the JWT secret, the credentials file and the data directory are readable only by their owners:
sudo openssl x509 -in /etc/ssl/cloudimg/bytestash.crt -noout -subject -ext subjectAltName
sudo stat -c '%a %U:%G %n' /etc/ssl/cloudimg/bytestash.key /etc/bytestash/jwt-secret /root/bytestash-credentials.txt /var/lib/bytestash
subject=CN = ec2-54-81-216-2.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-54-81-216-2.compute-1.amazonaws.com, IP Address:54.81.216.2, IP Address:172.31.81.44, IP Address:127.0.0.1, DNS:localhost
600 root:root /etc/ssl/cloudimg/bytestash.key
640 root:bytestash /etc/bytestash/jwt-secret
600 root:root /root/bytestash-credentials.txt
750 bytestash:bytestash /var/lib/bytestash
To serve ByteStash under your own DNS name (for example snippets.example.com, pointed at this instance), run the helper. It records the name, creates a new self-signed certificate for it, updates nginx and rewrites the URL in the credentials file:
sudo bytestash-set-hostname snippets.example.com
If the instance's public address changes after a stop and start, run the helper again. To use a certificate from your own certificate authority (recommended before others sign in or you connect an AI assistant), replace /etc/ssl/cloudimg/bytestash.key (keep it 0600 root) and /etc/ssl/cloudimg/bytestash.crt, then run sudo systemctl reload nginx.
Step 9: Outbound connections and metadata
ByteStash makes no outbound connection unless you turn on single sign-on. ByteStash and nginx cannot reach the instance metadata service, while the Amazon Time Sync Service at 169.254.169.123 keeps working, and the firmware-metadata refresh is masked:
systemctl show -p IPAddressDeny bytestash.service nginx.service
chronyc -n sources | grep 169.254
FW=$(systemctl is-enabled fwupd-refresh.timer fwupd-refresh.service || true); echo "$FW"
[ "$(sort -u <<<"$FW")" = masked ] && [ "$(systemctl show -p IPAddressDeny --value bytestash.service nginx.service | grep -c 169.254.169.254)" = 2 ]
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
^* 169.254.169.123 3 4 37 11 +5785ns[ -39us] +/- 522us
masked
masked
Maintenance
- Your password: change it from the user menu (top right), Change password, then update
/root/bytestash-credentials.txtor delete it. - More accounts: sign-up is closed. To let colleagues create their own accounts, set
ALLOW_NEW_ACCOUNTS=truein/etc/bytestash/bytestash.env, runsudo systemctl restart bytestash, let them register, then set it back tofalseand restart again. Anyone who can reach the instance can register while it istrue, so restrict the security group first. Accounts are administrators only if their name is inADMIN_USERNAMES(defaultadmin). Administrators manage users, snippets, API keys and share links in the Admin panel (user menu). - Single sign-on (OIDC): set
OIDC_ENABLED=trueandOIDC_ISSUER_URL,OIDC_CLIENT_IDandOIDC_CLIENT_SECRETfrom your identity provider in/etc/bytestash/bytestash.env, registerhttps://<your-dns-name>/api/auth/oidc/callbackas the redirect URL with the provider, and restart ByteStash. The file is readable by root only. - Do not set
DISABLE_ACCOUNTS=true. It turns every visitor into one shared anonymous user with full access; the start guard refuses it. - Backups: ByteStash keeps everything in the SQLite database in
/var/lib/bytestash/snippets. Stop ByteStash before you copy it (sudo systemctl stop bytestash), start it again afterwards, and keep the backup as safe as the credentials: it holds your snippets, your API keys and the password hashes. Never write to the database while ByteStash is running. - Upgrades: to move to a newer release, launch the newer version from the listing when it is published and restore your backup.
- Logs:
sudo journalctl -u bytestash -n 100 --no-pager, and nginx's access log in/var/log/nginx/access.log. - Security patches: unattended-upgrades remains enabled so the OS keeps receiving security updates.
Security recommendations
- Keep the security group tight. Open 22 only to your management address and 80/443 only to the people and API clients that use ByteStash. Public snippets and share links are readable by anyone who can reach port 443.
- Use your own certificate before other people sign in or you connect an AI assistant, so they trust it without exceptions.
- Keep ByteStash on loopback. Reach it through nginx; nginx is what keeps forwarded headers honest and refuses cross-site requests.
- Give each script and assistant its own API key, and delete keys you no longer use.
Support and licensing
ByteStash is developed by Jordan Dalby and contributors and distributed under the GNU General Public License v3.0. This image ships ByteStash 1.6.0 built from the unmodified 1.6.0 source with two configuration-level edits (ByteStash listens on 127.0.0.1 and trusts forwarded headers only from the local nginx) and the dependency updates listed above; the complete 1.6.0 source archive, the licence and the build record are in /usr/share/doc/cloudimg-bytestash/, and the changed package manifests and lockfile are in /opt/bytestash. Node.js is distributed under the MIT License. cloudimg provides the packaging, the hardening, first-boot HTTPS and per-instance credentials, and 24/7 support.
cloudimg is not affiliated with or endorsed by the ByteStash project or its authors. Product names are marks of their owners and are used here only to identify the software.
For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.