Hamburger Cross Icon
Git Guardrails - Lunar Policy for Devex Build And Ci

Git Guardrails

Policy Beta Devex Build And Ci

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.

Add 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.

Guardrail

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 config exists developer tooling
View Guardrail
Guardrail

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 pinned refs supply chain immutable
View Guardrail
Guardrail

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 secret scanning gitleaks credentials
View Guardrail
Guardrail

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).

pre-commit ci.skip enforcement bypass
View Guardrail
Guardrail

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 line endings repository hygiene
View Guardrail
Guardrail

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).

gitattributes eol line endings text=auto
View Guardrail
Guardrail

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.

gitmodules submodules supply chain floating refs
View Guardrail

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
1
Integrations Gather Data
Collectors extract metadata from code, CI pipelines, tool outputs, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
Guardrails Enforce Standards This Policy
Real-time feedback in PRs and AI workflows

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 GitHub

Git 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:

  1. pre-commit-config-exists — Add a .pre-commit-config.yaml to the repository root. Run pre-commit sample-config > .pre-commit-config.yaml for a starting point.
  2. pre-commit-pinned-refs — Replace floating rev: main with a tagged release: rev: v4.5.0. Run pre-commit autoupdate to bump every hook to its latest stable tag.
  3. pre-commit-secret-scan-hook — Add a secret-scanning hook (e.g. gitleaks) to your config.
  4. pre-commit-ci-skip-empty — Either remove entries from ci.skip (re-enabling enforcement in pre-commit.ci) or remove the hook from the config entirely.
  5. gitattributes-exists — Add a .gitattributes file. A minimal one (* text=auto) prevents most cross-platform line-ending issues.
  6. gitattributes-eol-normalized — Add * text=auto (or equivalent text / eol= directives) to .gitattributes.
  7. submodules-no-floating-branches — Remove the branch = … line from the submodule's .gitmodules block. Submodules track by SHA by default; the branch field only matters for git 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.

View Repository

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.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Automate Now
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo