TILLFORGE · PROTECTION

The push boundary, and why it fails closed.

GA

The pre-receive boundary is where a secret is rejected, a policy enforced, and a force-push blocked — before anything lands in history. Owning that boundary is the whole security wedge: the beginner is covered without configuring anything, and no single mistake is unrecoverable.

01The boundary

Every push runs the same policy

Whether a push arrives over HTTPS or SSH, it stops at one boundary that runs before a ref moves. It fails closed — if the policy layer can’t reach a clean verdict, the push is rejected, never waved through — and it never blocks on the control plane: branch policy is cached, so a dashboard blip degrades to the built-in default-branch guard, not to “allow everything.”

CheckDefaultOn violation
Secret scanOn, every repoReject the push, report file:line
Protected branchDefault branch protectedReject force-push + delete
Author identityOnReject if author ≠ authenticated identity
Signed commitsOff (opt-in per branch)Reject unsigned commits on the branch
Linear historyOff (opt-in per branch)Reject non-fast-forward merges
Approvals · checksEnforced at mergeBlock the merge until satisfied
02Secrets

Secret-reject at push, not alert

The boundary scans only the blobs a push introduces (bounded memory — a multi-GB push can’t exhaust it), and a match rejects the whole push with the file and line. The credential never enters history or the reflog, so there is nothing to scrub afterward.

  • Reject, not alert. Alerting fires after the secret is already committed — where it lives forever. Rejecting means it was never written.
  • Fails closed. A scanner error refuses the push rather than passing it through.
  • Audited allowlist for false positives. A genuine non-secret (a test fixture, a sample key) is cleared with a per-repo, reason-stamped allowlist — recorded, not silent. You can also mark a single line inline for the scanner to skip.
bash
# Secrets the boundary rejected, and clearing a false positive.
tilldev forge findings acme-api                       # what was rejected + file:line
tilldev forge allowlist acme-api <finding-id> \
  --reason "test fixture, not a live key"             # audited, reason-stamped
Imported history is scanned too
When you import or mirror an existing repo, its history is scanned as well — so a secret already sitting in the past shows up as a finding you can act on, not a blind spot.
03Rules

Tune branch protection

A branch policy targets a --pattern (a glob) and layers rules on top of the safe defaults. Set required signed commits, linear history, a required approval count, required status checks, and MFA-to-push — or, deliberately, relax force / delete on a non-default pattern.

bash
# Tune a branch rule. Defaults are already safe — this makes them
# stricter (or, deliberately, looser on a pattern that isn't main).
tilldev forge policy ls acme-api
tilldev forge policy set acme-api --pattern "main" \
  --require-signed \        # only signed commits on this branch
  --require-linear \        # no merge commits (rebase / squash only)
  --approvals 2 \           # required reviews before merge
  --checks ci,security \    # required status checks
  --require-mfa              # MFA-to-push on this protected branch
# (force-push + delete of a protected branch are rejected by default;
#  pass --allow-force / --allow-delete only if you truly mean it.)

Pattern matching is total: a catch-all * crosses / so there is no gap a branch can slip through. The required approvals and checks here are the same gate the merge flow enforces.

04Recovery

One-click reflog recovery

“A beginner’s mistake is not fatal” is a hard requirement, not a hope. A server-side reflog — independent of any client reflog — records every ref change with its before and after OID, and garbage collection keeps recently-orphaned commits for a configurable window. So a deleted branch, a force-pushed-over ref, or a dangling commit is a one-click restore to the prior OID.

bash
# A deleted branch or a force-pushed-over ref is recoverable.
tilldev forge reflog acme-api                 # every ref change, before/after OID
tilldev forge recover acme-api <reflog-id>    # one-click restore to the prior OID
Recovery is a safe compare-and-swap
Restoring a ref writes with a compare-and-swap on the value the change left behind — so if someone has pushed again in the meantime, the recovery won’t silently clobber their newer work; it tells you, instead of losing a commit to fix losing a commit.

The full posture behind these controls — sovereignty, isolation, and the CWE map — is in Security. Next: Pull requests for the merge gate, or back to the TillForge overview.