Make serve_header.py listen on localhost and limit its CORS header (#5564)

Without a bind address in serve_header.yml, the server listened on all
interfaces, so any machine on the network could fetch the header and
trigger make runs in the working trees. It now listens on localhost
unless configured otherwise; bind: null restores the old behavior.

The header was also sent with Access-Control-Allow-Origin: *, letting
any web page read it. CORS is only needed because Compiler Explorer
downloads #include <https://...> headers in the browser, so the header
now goes only to https://godbolt.org and https://compiler-explorer.com,
configurable with cors_origins.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-24 17:02:39 +02:00
committed by GitHub
parent 918da64657
commit bc066c1838
3 changed files with 33 additions and 6 deletions

View File

@@ -60,6 +60,10 @@ int main() {
`serve_header.py` will try to read a configuration file `serve_header.yml` in the top level or project root directory, and will fall back on built-in defaults if the file cannot be read.
An annotated example configuration can be found in `tools/serve_header/serve_header.yml.example`.
By default, the server listens on `localhost` only, and only web pages from Compiler Explorer (`https://godbolt.org` and `https://compiler-explorer.com`) may read the header.
Set `bind` to serve other machines as well; anyone who can reach the server can then trigger `make` runs in your working trees.
Set `cors_origins` to allow other web pages.
## Serving `json.hpp` from multiple project directory instances or working trees
`serve_header.py` was designed with the goal of supporting multiple project roots or working trees at the same time.

View File

@@ -26,6 +26,10 @@ HEADER = 'json.hpp'
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
# origins whose pages may read the served header from a browser; Compiler
# Explorer downloads #include <https://...> headers client-side
DEFAULT_CORS_ORIGINS = ['https://godbolt.org', 'https://compiler-explorer.com']
JSON_VERSION_RE = re.compile(r'\s*#\s*define\s+NLOHMANN_JSON_VERSION_MAJOR\s+')
class ExitHandler(logging.StreamHandler):
@@ -247,6 +251,8 @@ class WorkTrees(FileSystemEventHandler):
self.observer.join()
class HeaderRequestHandler(SimpleHTTPRequestHandler): # lgtm[py/missing-call-to-init]
cors_origins = DEFAULT_CORS_ORIGINS
def __init__(self, request, client_address, server):
"""."""
self.worktrees = server.worktrees
@@ -310,8 +316,11 @@ class HeaderRequestHandler(SimpleHTTPRequestHandler): # lgtm[py/missing-call-to-
# set content length
super().send_header('Content-Length', length)
# CORS header
self.send_header('Access-Control-Allow-Origin', '*')
# CORS header; only for the configured origins
origin = self.headers.get('Origin')
if origin in self.cors_origins:
self.send_header('Access-Control-Allow-Origin', origin)
self.send_header('Vary', 'Origin')
# prevent caching
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
self.send_header('Pragma', 'no-cache')
@@ -383,8 +392,15 @@ if __name__ == '__main__':
# find and monitor working trees
worktrees = WorkTrees(config.get('root', '.'))
# start web server
infos = socket.getaddrinfo(config.get('bind', None), config.get('port', 8443),
# origins allowed to read the header from a browser
cors_origins = config.get('cors_origins', DEFAULT_CORS_ORIGINS)
if isinstance(cors_origins, str):
cors_origins = [cors_origins]
HeaderRequestHandler.cors_origins = cors_origins
# start web server; only reachable from this machine unless configured
# otherwise (bind: null listens on all interfaces)
infos = socket.getaddrinfo(config.get('bind', 'localhost'), config.get('port', 8443),
type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE)
DualStackServer.address_family = infos[0][0]
HeaderRequestHandler.protocol_version = 'HTTP/1.0'

View File

@@ -10,6 +10,13 @@
# cert_file: localhost.pem
# key_file: localhost-key.pem
# address and port for the server to listen on
# bind: null
# address and port for the server to listen on; by default, only this machine
# can connect. Binding to a network address, or to null for all interfaces,
# lets other machines connect, and every request runs make in a working tree.
# bind: localhost
# port: 8443
# origins whose web pages may read the header (CORS)
# cors_origins:
# - https://godbolt.org
# - https://compiler-explorer.com