Git Guardrails
Enforce baselines for git-ecosystem tooling that lives in the repository: pre-commit config and hook hygiene, gitattributes EOL normalization, and submodule pinning. Reads data from the `git` collector.
git to your lunar-config.yml:uses: github://earthly/lunar-lib/policies/git@v1.0.5
Included Guardrails
This policy includes 7 guardrails that enforce standards for your devex build and ci.
pre-commit-config-exists
Universal check — fails when no .pre-commit-config.yaml is found in
the repository. Pre-commit is a low-friction way to enforce
lint/format/secret-scan hygiene; every component opted into this
policy should have a config file. Reads .git.pre_commit.
pre-commit-pinned-refs
Each repo entry in .pre-commit-config.yaml must have a rev pinned
to an immutable ref (tag or commit SHA), not a floating branch like
main, master, or HEAD. Floating refs let upstream hook changes
land in the developer machine without review. Skips when no
pre-commit config is present (paired with pre-commit-config-exists).
pre-commit-secret-scan-hook
Recommends that the pre-commit config include at least one
secret-scanning hook (gitleaks, detect-secrets, trufflehog, or
similar). Pre-commit is the cheapest place to catch credentials
before they reach the remote. Skips when no pre-commit config is
present (paired with pre-commit-config-exists). The accepted hook
ID list is configurable via the secret_scan_hook_ids input.
pre-commit-ci-skip-empty
Flags non-empty ci.skip lists in .pre-commit-config.yaml. The
ci.skip setting is a pre-commit.ci escape hatch that disables
hooks in the hosted CI service while still showing them locally —
a common way to silently bypass enforcement. Skips when no
pre-commit config is present (paired with pre-commit-config-exists).
gitattributes-exists
Universal check — fails when no .gitattributes file is found in
the repository. Even a minimal one (* text=auto) prevents the
cross-platform line-ending churn that bloats diffs and breaks CI.
Reads .git.attributes.
gitattributes-eol-normalized
Requires .gitattributes to declare EOL normalization (e.g.
* text=auto, or text / eol= directives covering tracked text
files). Without this, Windows checkouts can produce CRLF diffs that
derail reviews. Skips when no .gitattributes is present (paired
with gitattributes-exists).
submodules-no-floating-branches
Flags submodules that declare a branch field in .gitmodules,
since that signals the submodule is intended to track a floating
ref via git submodule update --remote (defeating the
always-pinned-by-SHA default). Skips when no .gitmodules is
present — submodule-free repos are not penalized.
How Guardrails Fit into Lunar
Lunar guardrails define your engineering standards as code. They evaluate data collected by integrations and produce pass/fail checks with actionable feedback.
Policies support gradual enforcement—from silent scoring to blocking PRs or deployments—letting you roll out standards at your own pace without disrupting existing workflows.
Learn How Lunar Works →Required Integrations
This policy evaluates data gathered by one or more of the following integration(s).
Make sure to enable them in your lunar-config.yml.
Configuration
Configure this policy in your lunar-config.yml.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
secret_scan_hook_ids
|
Optional |
gitleaks,detect-secrets,trufflehog,detect-aws-credentials,detect-private-key
|
Comma-separated list of pre-commit hook IDs that count as secret scanners |
Documentation
View on GitHubGit Guardrails
Enforce baselines for git-ecosystem tooling — pre-commit, gitattributes, and submodules.
Overview
This policy enforces healthy practices for git-ecosystem tooling that lives in the repository. It covers pre-commit hook hygiene, .gitattributes EOL normalization, and submodule pinning. Reads data from the git collector. Each "exists" check is universal and fails when the corresponding config is absent; dependent checks (e.g. pre-commit-pinned-refs) skip when no config is present so the existence check catches the absent case alone.
Policies
This plugin provides the following policies (use include to select a subset):
| Policy | Description |
|---|---|
pre-commit-config-exists |
Universal check — fails when no .pre-commit-config.yaml is present |
pre-commit-pinned-refs |
Every repo entry must have rev pinned to a non-floating ref (not main, master, HEAD) |
pre-commit-secret-scan-hook |
At least one secret-scanning hook (gitleaks, detect-secrets, trufflehog, etc.) is configured |
pre-commit-ci-skip-empty |
ci.skip is empty — no hooks are silently disabled in pre-commit.ci |
gitattributes-exists |
A .gitattributes file is present in the repository root |
gitattributes-eol-normalized |
.gitattributes declares EOL normalization (e.g. * text=auto) |
submodules-no-floating-branches |
No submodule declares a branch field that would make git submodule update --remote track a floating ref |
Required Data
This policy reads from the following Component JSON paths:
| Path | Type | Provided By |
|---|---|---|
.git.pre_commit |
object | git collector (pre-commit sub-collector) |
.git.attributes |
object | git collector (gitattributes sub-collector) |
.git.submodules |
object | git collector (gitmodules sub-collector) |
Note: Ensure the git collector is configured before enabling this policy. Each sub-collector writes nothing when its config file is absent — this policy's existence checks rely on object presence as the signal.
Installation
Add to your lunar-config.yml:
policies:
- uses: github://earthly/lunar-lib/policies/git@v1.0.0
on: ["domain:your-domain"]
enforcement: report-pr
# include: [pre-commit-config-exists, gitattributes-eol-normalized]
# with:
# secret_scan_hook_ids: "gitleaks,detect-secrets,trufflehog"
Examples
Passing Example
A component with a pre-commit config (all repos pinned, secret scanner present, empty ci.skip), a .gitattributes with EOL normalization, and no submodules tracking a floating branch:
{
"git": {
"pre_commit": {
"valid": true,
"repos": [
{"repo": "https://github.com/gitleaks/gitleaks", "rev": "v8.18.0", "hooks": [{"id": "gitleaks"}]}
],
"hook_ids": ["gitleaks"],
"ci_skip": [],
"all_pinned": true
},
"attributes": {
"valid": true,
"eol_normalized": true,
"lfs_patterns": ["*.psd"]
},
"submodules": {
"valid": true,
"modules": [{"name": "vendor/foo", "path": "vendor/foo", "url": "https://github.com/example/foo.git", "branch": null}]
}
}
}
Failing Example
A repo with floating-pinned pre-commit hooks, missing .gitattributes, and a submodule tracking main:
{
"git": {
"pre_commit": {
"valid": true,
"repos": [
{"repo": "https://github.com/pre-commit/pre-commit-hooks", "rev": "main", "hooks": [{"id": "trailing-whitespace"}]}
],
"hook_ids": ["trailing-whitespace"],
"ci_skip": ["gitleaks"],
"all_pinned": false
},
"submodules": {
"valid": true,
"modules": [{"name": "vendor/bar", "path": "vendor/bar", "url": "https://github.com/example/bar.git", "branch": "main"}]
}
}
}
Failure messages:
pre-commit-pinned-refs:"Repo 'https://github.com/pre-commit/pre-commit-hooks' uses floating ref 'main' — pin to a tag or commit SHA"pre-commit-ci-skip-empty:"ci.skip disables 1 hook(s) in pre-commit.ci: gitleaks"gitattributes-exists:"No .gitattributes file found"submodules-no-floating-branches:"Submodule 'vendor/bar' tracks branch 'main' — remove the branch directive to keep the submodule pinned by SHA"
Remediation
When these policies fail, you can resolve them by:
pre-commit-config-exists— Add a.pre-commit-config.yamlto the repository root. Runpre-commit sample-config > .pre-commit-config.yamlfor a starting point.pre-commit-pinned-refs— Replace floatingrev: mainwith a tagged release:rev: v4.5.0. Runpre-commit autoupdateto bump every hook to its latest stable tag.pre-commit-secret-scan-hook— Add a secret-scanning hook (e.g.gitleaks) to your config.pre-commit-ci-skip-empty— Either remove entries fromci.skip(re-enabling enforcement in pre-commit.ci) or remove the hook from the config entirely.gitattributes-exists— Add a.gitattributesfile. A minimal one (* text=auto) prevents most cross-platform line-ending issues.gitattributes-eol-normalized— Add* text=auto(or equivalenttext/eol=directives) to.gitattributes.submodules-no-floating-branches— Remove thebranch = …line from the submodule's.gitmodulesblock. Submodules track by SHA by default; thebranchfield only matters forgit submodule update --remote, which most repos shouldn't be using.
Open Source
This policy is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.
Common Use Cases
Explore how individual guardrails work with specific integrations.
Ready to Automate Your Standards?
See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 200+ built-in guardrails.