Gald
← BLOG
ENGINEERING · 18 SEP 2026

If your site deploys from git, your repo is your web root

Automatic deploys are worth having. They also publish every file you never thought about: the README, the build scripts, the notes to yourself. Checking yours takes a minute.

This site deploys itself. A push to the main branch is live on gald.ai within seconds, with nobody uploading anything. That is a good trade, and we would set it up again.

What we did not ask, until someone opened the wrong URL, was what exactly gets copied. The answer was: everything in the repository. The README came back with a 200. So did the Python scripts that build the pages, the page templates, and the workflow files.

What ends up published

A static site deployed from a repository has no separate build output. The repository is the web root. Every file sitting next to your HTML is a URL, whether or not anything links to it.

  • The README, which usually explains your architecture, your hosting and what you have not finished yet.
  • Build scripts, which name internal paths, environment variables and the decisions behind them.
  • Page templates and fixtures, which often contain test data written when nobody was watching.
  • CI workflow files, which name every secret your pipeline uses, even though the values stay on the CI side.
  • Anything a tool dropped in the folder: editor settings, a credits file, an export you meant to delete.
WITHOUT
  • The repository is the web root
  • Anything on disk is served
  • Rules added at the end of the config
  • Checked once, when it was set up
WITH
  • The server refuses repository paths
  • Only the site is served
  • Refusal before the catch-all rule
  • Re-checked when a folder is added
Two ways to answer the same request

In our case nothing secret was exposed. We scanned every tracked file for tokens, keys and passwords and found one placeholder inside a comment, and the host was already refusing the .git folder. That was luck, not design.

Fixing it without breaking the site

The fix is a few lines of server config, and the order matters more than the lines. Most configs have a rule near the top that says: if the request matches a file that exists, serve it and stop. A refusal written after that rule never runs, because the file does exist. The refusal has to come first.

A push is a deploy now. A server config with a typo in it is live everywhere in seconds, so test it before it leaves your machine.

We tested ours against a local copy of Apache before pushing: every real page still had to return 200 with its headers intact, and every repository path had to be refused. The first run failed everything, including the homepage, because macOS would not let Apache read the project folder at all. Serving a copy from a temporary directory gave a clean result, and running the old rules beside the new ones proved the test matched what the live server does.

The minute it takes to check yours

  • Open your domain followed by /README.md. Then /package.json, /.env, /.git/config, and the name of your build folder.
  • Try one file you know is in the repository but not part of the site. A test fixture is a good choice.
  • Anything that answers with content instead of an error is published, and has been since your first deploy.
  • Do it again the next time you add a folder to the project, because the answer changes when the repository does.

None of this is an emergency if your repository holds no secrets, and no repository should. It is still worth a minute: the README you wrote for yourself reads differently when a stranger can open it.

Have a project in mind?

Start a project