Chapter 21Lesson 03~135 minutes

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.

ConfigurationSafe directoryfsck policyCredential hygiene

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
Never normalize 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.

Next

Diagnose dangerous security shortcuts

Lesson 4 engineers false confidence around unrotated credentials, cryptographically valid but unauthorized keys, broad safe.directory exceptions, premature cleanup, and clean-fsck overinterpretation.

Authoritative references

 git-config
 gitformat-signature
 git-fsck

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.