Signing, Trust, Secret Removal, fsck, Safe Directories, and Repository Integrity: Configuration, Design Choices, and Tradeoffs
Design Git security configuration around signing defaults/backends, narrow safe.directory trust, transfer/fetch/receive fsck validation, credential helpers, secret scanning, and untrusted-repository execution boundaries.
Learning objectives
- Configure commit/tag signing and choose OpenPGP, X.509, or SSH according to deployment capability.
- Keep safe.directory exceptions exact and restricted to protected configuration.
- Place transfer, fetch, and receive fsck controls at the correct repository boundary.
- Inspect credential-helper configuration without exposing stored secrets.
- Separate core Git integrity controls from hosted authorization and secret-scanning features.
1. Production trust policy starts by naming the security question
Signing defaults, safe-directory exceptions, object-validation settings, credential helpers, and secret scanners protect different boundaries. A policy should say which threat/control each setting addresses and who owns failures. Security configuration becomes dangerous when teams enable it mechanically and assume it covers unrelated risks.
2. Inspect effective configuration and source scope
git config --list --show-origin --show-scope
git config --show-origin --get commit.gpgSign
git config --show-origin --get tag.gpgSign
git config --show-origin --get user.signingKey
git config --show-origin --get gpg.format
git config --show-origin --get-all safe.directory
git config --show-origin --get transfer.fsckObjects
git config --show-origin --get fetch.fsckObjects
git config --show-origin --get receive.fsckObjects
System/global/local/worktree/command scopes can differ. Some
security-sensitive settings, notably safe.directory,
are only honored from protected configuration so an untrusted
repository cannot grant itself trust.
3. commit.gpgSign can make commit signing the default
git config --local commit.gpgSign true
This changes the default behavior of git commit, but it
does not distribute private keys, pinentry configuration, trust
stores, or server policy. In automation, default signing can fail
when the signing agent/backend is unavailable. Prefer an explicit CI
signing architecture rather than assuming developer desktop key
state exists on runners.
4. tag.gpgSign can make tag signing the default
git config --local tag.gpgSign true
Signed tags are useful release attestations, but the verifying environment still needs the appropriate public-key trust data and identity policy. An organization may choose signed release tags even if ordinary development commits are not all signed.
5. user.signingKey identifies signing material, not
authorization
For OpenPGP it can identify an OpenPGP key; for SSH signing it can reference an SSH public/private signing setup as supported by Git's backend. Selecting a key tells Git what material to use. It does not tell a receiving server that the key holder may update a protected production branch.
6. gpg.format chooses OpenPGP, X.509, or SSH
| Format | Typical external program | Trust dependency |
|---|---|---|
openpgp |
GnuPG-compatible gpg |
OpenPGP keyring/trust policy |
x509 |
gpgsm by default |
Certificate/CA policy |
ssh |
ssh-keygen by default |
Allowed-signers / SSH identity mapping |
OpenPGP is the current default. Choose a backend that your developer, CI, and release-verification environments can provision consistently.
7. SSH signatures require an explicit allowed-signers policy
git config --global gpg.format ssh
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
This is an example only; do not change your real global configuration just to follow the course. The allowed-signers file links principals to trusted SSH public keys. It should be distributed/generated through a trusted identity process, not taken blindly from repository content.
8. safe.directory should be exact and justified
Use it when repository ownership legitimately differs from the executing account and changing ownership is not appropriate—for example, a controlled shared build workspace. A narrow entry is easier to audit:
git config --global --add safe.directory /exact/reviewed/repository
safe.directory=* as a
troubleshooting shortcut.
The wildcard disables the ownership check for all repositories.
Review owner, path, symlinks/mounts, and execution identity first.
9. Why repository-local safe.directory cannot whitelist itself
Git only honors safe.directory from protected
configuration. If an untrusted repository could set it inside its
own .git/config, the ownership protection would be
self-defeating. This is a useful example of configuration precedence
being constrained by a trust boundary.
10. transfer.fsckObjects supplies a default for
transfer validation
git config --local transfer.fsckObjects true
When the more specific fetch/receive controls are unset, current Git uses this setting as their fallback. Object checks can reject malformed objects or invalid links during transfer, but they add CPU/I/O cost and do not substitute for secret scanning or signature policy.
11. fetch.fsckObjects validates objects received by
fetch
git config --local fetch.fsckObjects true
Current documentation says this defaults to false and otherwise
falls back to transfer.fsckObjects when unset. A
developer or CI clone can use it when the integrity cost/benefit
fits the threat model.
12. receive.fsckObjects is a
receiving-repository/server control
A bare repository receiving pushes can enable:
git -C central.git config receive.fsckObjects true
This is core Git receive-pack behavior, distinct from hosted branch protection. A hosted provider may already impose its own validation or may not expose this exact setting to customers.
13. Message-specific fsck policy must be configured at the right boundary
Current Git documents fsck.<msg-id>,
fetch.fsck.<msg-id>, and
receive.fsck.<msg-id>. The message-specific
fetch/receive settings do not automatically fall back to
ordinary fsck.<msg-id>. If a legacy exception is
truly needed, configure and document it separately for each intended
boundary instead of assuming inheritance.
14. Credential helpers are secret-handling infrastructure
git config --show-origin --get-all credential.helper
This safe inspection reveals helper configuration, not stored secrets. Do not dump helper databases, run commands that print tokens into transcripts, or embed credentials in remote URLs. Credential storage/SSO behavior varies significantly across Windows, macOS, Linux, enterprise managers, and hosting providers.
15. Secret scanning and push prevention are separate from core object integrity
Teams can use local hooks, CI scanners, or hosted/server
secret-scanning controls to detect likely credentials. These systems
are pattern/entropy/policy engines around Git content.
git fsck does not become a secret scanner because both
tools inspect repository data.
16. Treat untrusted repositories as code/data that can influence tools
A clone can contain build scripts, submodules, attributes, source generators, and files consumed by IDEs/toolchains. Git deliberately restricts some configuration behavior and ownership cases, but cloning a repository does not certify its code. Review before executing hooks copied by other mechanisms, custom merge/diff drivers, package scripts, build systems, or binaries.
17. Filesystem ownership and server authorization remain separate
On multi-user systems, correct ownership/permissions protect local
repository metadata. On remote servers, authentication and
authorization determine which refs may be read or updated. A
safe.directory exception does not grant push
permissions, and server push permissions do not make a locally
mis-owned repository safe to execute.
18. Decision table — choose the control for the actual risk
| Risk | Primary control | Secondary evidence | Common mistake |
|---|---|---|---|
| Release provenance | Signed tag/commit + trusted key mapping | verify-tag/verify-commit |
Trust any valid key |
| Repository owned by service account | Correct ownership or exact safe.directory | OS ownership + config origin | safe.directory=* |
| Malformed inbound objects | fetch/receive fsck policy | git fsck |
Assume fsck scans secrets |
| Credential accidentally committed | Immediate revoke/rotate | History scan + coordinated rewrite | Rewrite first and leave token valid |
| Credential storage | Approved credential helper/SSO | Config origin | Embed token in URL/script |
19. Knowledge check
Question 1. What does commit.gpgSign change?
Question 2. Why can safe.directory not be trusted from repository-local config?
Question 3. What is the relationship between transfer.fsckObjects and fetch/receive fsckObjects?
Question 4. Are fsck message-specific fetch/receive exceptions inherited from ordinary fsck.<msg-id>?
Question 5. Why inspect credential.helper rather than credential contents?
20. Summary
Signing defaults, trust stores, ownership exceptions, transfer-integrity checks, credential helpers, and scanning policy belong to different layers. Portable production design declares each layer explicitly rather than hoping one security switch covers the others.
Authoritative references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.