RoastMyOpsec

Vulnerabilities

.gitmodules vs .git/HEAD on Production Origins

January 3, 2024

.gitmodules vs .git/HEAD on a production origin is a submodule inventory file versus proof the git directory is on the CDN. A reachable /.git/HEAD with a ref: signature is a VCS dump class finding — the roast already scores it. A reachable .gitmodules donates submodule URLs and paths, sometimes including private remotes. Serving either is the same class of miss as leftover VCS crumbs next to index.html. RoastMyOpsec may not fetch .gitmodules today. Still deny it at the edge. Do not Disallow git paths in robots.txt; that advertises the working copy.

The practical difference

.git/HEAD is the pointer into a full history tree if objects are also public. .gitmodules is a small INI map of extra remotes. History is worse. The map is still a map. Neither belongs on a static host.

GitHub Pages documents that submodule URLs in .gitmodules should be https for builds it can fetch — that is a Pages concern, not a reason to serve .gitmodules from your marketing bucket.

How to choose based on what you shipped

If .git/HEAD hit: deny the .git prefix at the CDN, treat it as a source-and-history incident, rotate secrets that may have been in that repo. If only .gitmodules is in the artifact: still take it off the origin and stop publishing the git working copy. Fail CI when .git or .gitmodules appear in a static export.

OptionWhen it winsWatch-outTakeaway
/.git/HEADNever on the CDNVCS dump signatureRoast signatures this path today
/.gitmodulesNever on the CDNSubmodule URLs and pathsSame class of miss; deny the name
Submodules in a private repoCode sharing you intendedDo not deploy the working copy as the siteKeep git metadata off the origin
robots.txt DisallowNot for .gitTreasure mapEdge deny the prefix

When each git file wins (never as a public object)

.git/HEAD wins only inside a real git directory on a machine or in a host you do not publish. .gitmodules wins in a private repository that actually uses submodules. Neither wins next to index.html.

If directory listing is on, the map gets louder. Turn listing off and still deny VCS names at the edge.

Common mistakes

The first mistake is deploying the git working copy because the build output lived in the same folder.

The second mistake is blocking /.git/HEAD and still serving .gitmodules from the artifact root.

The third mistake is Disallowing /.git in robots.txt and calling the dump closed.

What a URL roast can prove

Bounded GET to /.git/HEAD looking for a ref: signature. .gitmodules is not a scored path today. Still deny it. No recursive object walk, no history dump, no exploit payload. A miss on HEAD is not proof every git metadata file is gone.

Free audit the URL you own

RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.

Free audit now

FAQ

Is a public .gitmodules a vulnerability?
It is an information leak and a sign the git tree hitchhiked onto the origin. Remove it, deny git metadata at the edge, and check whether .git/HEAD is also reachable.
Does RoastMyOpsec fetch .gitmodules?
Not as a scored path today. It does probe /.git/HEAD. Deny .gitmodules at the CDN anyway. Same class of miss.
Is .gitmodules worse than .git/HEAD?
Usually no. HEAD plus objects is the history dump. .gitmodules is the submodule map. Fix both. Do not debate the trophy.

Sources

Related guides