Skip to content

Instantly share code, notes, and snippets.

@ssokolow
Created July 27, 2026 03:24
Show Gist options
  • Select an option

  • Save ssokolow/87fc077dac29ad396a6ec87062633759 to your computer and use it in GitHub Desktop.

Select an option

Save ssokolow/87fc077dac29ad396a6ec87062633759 to your computer and use it in GitHub Desktop.
Example of how to tightly sandbox miniserve as a companion to a private Mumble server
[Unit]
Description=HTTPS Server to assist mumble-service
After=network.target
Wants=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/mumble_companion_https.sh
# `miniserve-sys` to avoid confusion with any `cargo install`-ed copy
BindReadOnlyPaths=/usr/local/bin/miniserve-sys /var/www-mumble_companion
CapabilityBoundingSet=
CPUQuota=20%
DevicePolicy=closed
DynamicUser=yes
Environment=
ExecPaths=/usr/local/bin
ExitType=cgroup
IPAddressDeny=link-local multicast
KeyringMode=private
# Store your username-password credentials in /etc/mumble_companion_auth
LoadCredential=auth:/etc/mumble_companion_auth
LoadCredential=fullchain.pem:/etc/letsencrypt/live/YOUR.DOMAIN.NAME/fullchain.pem
LoadCredential=privkey.pem:/etc/letsencrypt/live/YOUR.DOMAIN.NAME/privkey.pem
LockPersonality=yes
MemoryDenyWriteExecute=yes
MemoryMax=512M
Nice=10
NoNewPrivileges=yes
PassEnvironment=
PrivateDevices=yes
PrivateIPC=yes
ProcSubset=pid
ProtectProc=invisible
PrivateMounts=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=strict
ReadOnlyPaths=/usr/local/bin/miniserve-sys
ReadOnlyDirectories=/var/www-mumble_companion
RemoveIPC=yes
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SocketBindDeny=any
# Set this to the port you want to listen on
SocketBindAllow=tcp:8088
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service
SystemCallFilter=~@chown @privileged @resources @setuid connect
StandardOutput=journal
TemporaryFileSystem=/usr/local/bin:ro
UMask=0077
# All irrelevant paths not covered by Private* options that the FHS says we
# should be able to rely on existing (systemd will error out if one doesn't)
InaccessiblePaths=/boot /etc /media /mnt /opt /srv /usr/include /usr/share /usr/src
[Install]
WantedBy=multi-user.target
#!/bin/sh
# Customize as desired. See miniserve's --help output for details.
# I recommend changing the port to make port-scanning bots work to find it.
#
# See miniserve's README for how to generate the file for --auth-file
#
# This set of headers helps to limit the risk if some HTML with shady stuff in
# it makes its way into your server, and tries to minimize the odds that random
# bots wandering the web become aware of your server.
#
# While the spec calls for shared caching to be disabled as a side-effect of
# --auth-file, there are tons of broken proxies and caches out there, so the
# pile of Cache-Control directives, combined with Pragma: no-cache are
# intended to hedge against that without going the nuclear option of disabling
# the user's local cache with `no-store`. Feel free to remove the
# `no-transform` directive if you're OK with your users potentially having
# files silently recompressed by something like a mobile phone ISP proxy.
#
# The Permissions-Policy header denies everything that had a defined rule as of
# this writing, so, if you're trying to experiment with making some kind of
# fancy web app and something doesn't work, it's probably that.
#
# Note that you probably want to test without the Strict-Transport-Security
# header first since, if you get things wrong, you have to ask your friends to
# follow a guide to get their browser to forget they saw it. (It tells the
# browser that your site should only ever be loaded over https:// so that, if
# they connect over shady coffee-shop WiFi, a "downgrade attack" will fail.)
#
# (Also, be aware that it imposes an un-bypassable block on self-signed certs)
exec /usr/local/bin/miniserve-sys --verbose --port=8088 \
--index=index.html --pretty-urls --title=Resources --dirs-first \
--hide-version-footer --hide-theme-selector \
--auth-file="$CREDENTIALS_DIRECTORY/auth" \
--tls-cert="$CREDENTIALS_DIRECTORY/fullchain.pem" \
--tls-key="$CREDENTIALS_DIRECTORY/privkey.pem" \
--header="Strict-Transport-Security: max-age=31536000" \
--header='Referrer-Policy:no-referrer' \
--header='Cache-Control:private, no-cache, no-transform, max-age=0, must-revalidate, must-understand, proxy-revalidate' \
--header='Pragma:no-cache' \
--header='X-DNS-Prefetch-Control:off' \
--header='X-Content-Type-Options:nosniff' \
--header='X-Frame-Options:DENY' \
--header="Content-Security-Policy:default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';" \
--header='Cross-Origin-Embedder-Policy:require-corp' \
--header='Cross-Origin-Opener-Policy:same-origin' \
--header='Cross-Origin-Resource-Policy:same-origin' \
--header 'X-Permitted-Cross-Domain-Policies:none' \
--header='Origin-Agent-Cluster:?1' \
--header='X-Robots-Tag:noarchive, nofollow, noimageindex, noindex, nopagereadaloud, nositelinkssearchbox, nosnippet, notranslate, noyaca' \
--header='Permissions-Policy:accelerometer=(), ambient-light-sensor=(), aria-notify=(), attribution-reporting=(), autoplay=(), browsing-topics=(), captured-surface-control=(), ch-ua-high-entropy-values=(), compute-pressure=(), bluetooth=(), camera=(), cross-origin-isolated=(), deferred-fetch=(), deferred-fetch-minimal=(), display-capture=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), language-detector=(), local-fonts=(), local-network=(), local-network-access=(), loopback-network=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), on-device-speech-recognition=(), payment=(), picture-in-picture=(), private-state-token-issuance=(), private-state-token-redemption=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), storage-access=(), translator=(), summarizer=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()' \
/var/www-mumble_companion/http-root
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment