Skip to content

PointSav Documentation

The engineering library for the PointSav platform — operating systems and services for regulated businesses that own their data, their AI, and their record-keeping outright. Where the monorepo holds the code, this wiki holds the reasoning: architecture, services, security, and the governance commitments that bind future development.

Pre-commit defense in depth

← All revisions

417a31fa · PointSav Digital Systems ·

security/: replace 12 articles with fresh-draft-first pilot rewrites against schema-topic.yaml

View the full record as of this revision →

@@ -1,51 +1,172 @@
---
schema: foundry-doc-v1
title: "Pre-commit defense in depth — secret scan, size guard, helper-only gate"
title: "Pre-commit defense in depth"
slug: pre-commit-defense-in-depth
short_description: "Three-check pre-commit gate — helper-only commits, a 17-pattern secret scan, and a 2 MiB size guard — closing the incident classes behind leaked and mis-attributed commits."
language: en
category: security
type: topic
content_type: topic
quality: complete
status: active
bcsc_class: public-disclosure-safe
last_edited: 2026-07-28
audience: vendor-public
bcsc_class: current-fact
language_protocol: PROSE-TOPIC
last_edited: 2026-08-03
editor: pointsav-engineering
cites: []
short_description: "Three independent git hooks run before a commit is recorded: a helper-only gate, a data-path block, a staged-content secret and size scan, and an author-identity check. Every bypass is logged."
paired_with: pre-commit-defense-in-depth.es.md
---

Three commit-pipeline incidents in 2026 — an SSH key landing in a public monorepo, a `.git/config` author override that mis-attributed hundreds of commits, and chmod-revert races against the identity store — share a structural cause: there was no gate between a workspace participant and `git commit`. Any agent, any operator, could write any content and commit any way.
**Pre-commit defense in depth** is the practice of stacking several independent checks at the
moment a commit is created, each catching a different class of mistake, so that no single failure
lets a secret, an oversized artefact, or a mis-attributed change enter history. It is the cheapest
point in the lifecycle to intervene: a rejected commit costs seconds, whereas a credential that
reaches a published repository must be treated as compromised and rotated regardless of how
quickly the commit is reverted.

## Three checks in one pre-commit hook
The controls described here operate on the *staged* content — what the commit is about to record —
rather than on the working directory. That distinction is what makes them meaningful: a scan of
files on disk can be defeated by staging a different version, whereas reading the staged blob
through Git's own object store sees exactly what is about to be written.

The Phase 1 hardening closes this with a single pre-commit hook symlinked into `.git/hooks/pre-commit` at the workspace root and every archive clone. It runs three checks against every commit.
## The layers

**Helper-only commits.** The hook refuses unless a helper environment variable is set by the authorised commit helper script. Direct `git commit` invocations — by an operator, an agent, or automation — receive a clear error message pointing at the helper. Merge commits and rebases are allowed via `GIT_REFLOG_ACTION` detection; emergencies can bypass with `--no-verify`. The cost of the rule is one redirected commit; the benefit is the end of the entire "wrong author" class.
Four separate checks run at commit time, in a deliberate order: cheapest and broadest first, most
expensive last.

**Secret-pattern scan.** The hook reads a pattern catalogue and scans staged content against 18 regex patterns: SSH/PGP private keys (OPENSSH, RSA, EC, DSA, PGP, encrypted), cloud credentials (AWS, GCP, GitHub PAT/fine-grained/OAuth tokens), API keys (Anthropic, OpenAI, Slack), bearer tokens, hardcoded Foundry identity strings, and generic password assignments. Critical and high-severity matches block the commit; medium and low produce a warning and proceed. A path allowlist exempts the pattern catalogue itself and public-key files.
### Helper-only commit gate

**Correction (2026-07-28):** the pattern count was previously stated as 17; the live
catalogue at `conventions/secret-patterns.yaml` carries 18 entries (verified by direct
count). The category list above is now the accurate breakdown, including two entries
missing from the original count (`encrypted-private-key`, `hardcoded-foundry-identity`).
All other claims in this article — the helper-only gate via `FOUNDRY_COMMIT_HELPER`, the
2 MB size cap, the `GIT_REFLOG_ACTION` merge/rebase/cherry-pick allowance, and the PyYAML
dependency — were checked against `bin/pre-commit-foundry-gate.sh` and confirmed accurate.
The first check refuses a direct `git commit` unless the environment variable
`FOUNDRY_COMMIT_HELPER` is set, which the sanctioned commit helper sets for its own invocation. It
needs no file inspection at all, only an environment check, so it fails fast with an instruction
pointing at the helper rather than a mystery. The effect is that every ordinary commit is routed
through one script, which is where identity alternation, trailers, and signing are applied
consistently — the helper-only gate is what makes the [[five-stage-supply-chain|supply chain]]'s
attribution guarantees trustworthy at their origin.

**Size guard.** Blobs over 2 MiB are refused unless the path is on the size allowlist (`data/binary-ledger/`, media-asset repos, fleet-deployment `www/`). This catches the accidental binary commit — large images, copied build artefacts, vendored databases — before history pollution.
The gate makes a deliberate exception for Git's own internal commits: if `GIT_REFLOG_ACTION`
indicates a merge, rebase, or cherry-pick, the commit proceeds without the helper variable. Without
that carve-out, every conflict resolution during a rebase would be blocked. A separate emergency
bypass variable exists for an operator with a legitimate reason; using it is logged rather than
silent.

## Implementation size and commit-time cost
### Data-path block

The Python implementation is approximately 150 lines; PyYAML is the only external dependency. For a typical commit of 5–20 files, the hook adds under a second. For unusually large commits, the size guard catches the bloat before the secret scan has to read it.
A second check inspects staged file *paths* — not their contents — against a catalogue of path
shapes known to carry business records and personal information: specific engine directories, a
named personnel ledger file, transaction-queue prefixes, particular document patterns, and the
input and output working directories. A small allowlist covers legitimate exceptions. This layer
exists because the most costly category of accidental commit is not a credential but a real
business record, and such a file is often indistinguishable from ordinary project content by its
extension alone. It runs before the content scan so that a business-admin file is refused on its
path alone, cheaply, rather than being pumped through pattern matching first.

## Known bypass paths
### Secret-pattern and size scan

What this does not solve: bypass via `--no-verify`, or a commit on a workspace clone without the hook installed. The first is intentional — an operator with a legitimate reason can bypass with a full audit log entry. The second is closed by the archive-provisioning script, which installs the hook symlink whenever an archive is provisioned.
The largest layer reads a pattern catalogue from `conventions/secret-patterns.yaml` and scans the
staged content of each added, copied, modified, or renamed file. Content is retrieved through Git
plumbing rather than from disk. Binary files are skipped by a heuristic examining null bytes and the
printable-character ratio of the first four kilobytes of each file — checked before the expensive
full-content decode, after a real commit of roughly 150 business-admin files (several large PDFs and
a zip) once took over 25 minutes because that ordering was reversed.

Reading the live catalogue directly, it currently carries **23 pattern entries**: private keys in
six forms (OpenSSH, RSA, EC, DSA, PGP, and encrypted PEM), a dedicated bare WireGuard
`PrivateKey =` line pattern — added after a real, live finding of an untracked WireGuard private
key in a business-admin configuration file that the PEM-armored patterns would have missed
entirely — cloud and platform credentials (a major cloud provider's access keys, another's
service-account and API keys, and three shapes of source-forge token), three model-provider API
keys and one chat-platform token, generic password assignments and bearer tokens, a hardcoded
workspace identity path, and four patterns matching personal-information shapes — telephone
numbers, email addresses, national identity numbers, and street addresses. The catalogue has grown
twice since it was last measured at 18 entries: the WireGuard pattern and the four
personal-information patterns were both added afterward, for the reasons above.

Severity determines the outcome. Critical and high matches block the commit outright. Lower
severities print a warning and allow it to proceed. A path allowlist exempts the catalogue file
itself, `identity/*.pub` and `allowed_signers`, and two named test fixtures whose unit tests
necessarily contain secret-shaped strings.

The same layer enforces a size ceiling, set in the catalogue as `size_cap_mb: 2` and applied as a
2 MiB limit against the staged blob size, checked ahead of the secret scan so an oversized blob is
refused on size alone rather than scanned first. A separate path allowlist covers directories where
large artefacts are legitimately expected — the binary ledger, media-asset repositories, and
deployment web roots.

### Author-identity check

A `commit-msg` hook independently verifies the commit author against the two permitted contributor
identities and rejects `Co-Authored-By` or `Signed-off-by` trailers naming anyone else. This closes
a different gap from the others: it does not care what the commit contains, only that attribution
is correct and that no automated tooling has inserted itself into the authorship record. It is what
closed the 2026 mis-attribution incident class, where a stray local configuration override could
silently change the author on a run of commits.

## Why the order and the severity grading matter

False positives are the tax every secret scanner pays, and a gate that cries wolf gets bypassed
culturally long before it gets bypassed technically. Patterns whose matches are almost always real
secrets — a private-key header, a provider access key — block outright. Patterns that legitimately
appear in test fixtures and documentation — generic password assignments, token-like strings — warn
and let the commit proceed, keeping the operator informed without training them to reach for the
bypass. The allowlists follow the same philosophy: the pattern catalogue must be committable even
though it is made of the very strings it hunts, and public keys must be committable even though
they share filesystem real estate with private ones, so both are exempted by path rather than by
weakening the patterns.

## Failure behaviour and bypass

Each layer can be bypassed by its own environment variable, and each bypass is appended to a
`data/bypass-ledger.jsonl` record. The logging is best-effort and never blocks the commit itself.
This is the intended posture: an operator with a legitimate reason can proceed, and the decision
leaves a durable trace rather than being invisible.

Two degradation paths are worth stating plainly. If the pattern catalogue is missing, the secret and
size scan is skipped with a warning rather than failing closed. If PyYAML — the scan's only external
dependency, everything else being standard library — is not installed, the same thing happens. In
both cases the commit proceeds unscanned. Git's own `--no-verify` flag skips every hook entirely,
and a clone without the hooks installed enforces none of these controls; that gap is closed
operationally, by installing the hook automatically at archive-provisioning time, rather than by any
technical impossibility.

## Relationship to the promotion gate

These checks are the first of two lines. The promotion script that moves work onto the canonical
branch re-applies a comparable data-path filter against the tree about to be pushed, blocks mass
deletions and silent reverts above a threshold, enforces a top-level path allowlist for the
canonical repository, and requires explicit confirmation for the repositories that are publicly
visible. A secret that survives commit time still has to pass that second filter before it becomes
externally observable. The layering is deliberate: commit-time checks are fast and local,
promotion-time checks are slower and consider the whole tree.

## What this is not

**This is not a guarantee that no secret can be committed.** The scan is regular-expression matching
against known credential shapes. A credential in an unrecognised format, split across lines, or
encoded passes through. Detection coverage is a catalogue, not a proof — and the pattern count
stated here is a measurement, not a constant; the catalogue has grown twice in this article's
documented history, and any restatement of the number should be re-counted against the live
catalogue rather than quoted from this page.

**This is not a mandatory control.** Three separate escapes exist — per-layer environment
variables, Git's `--no-verify`, and a clone without hooks installed. The first is logged; the other
two are not.

**A missing dependency does not fail the commit.** Absent PyYAML or an absent catalogue file
silently reduces the scan to nothing while allowing the commit. This is a fail-open design, chosen
so a tooling problem does not halt work, and it should be understood as such.

**This does not scan history.** The checks apply to the commit being created. Content already in
history is unaffected, and a secret introduced before these controls existed remains there.

**The personal-information patterns are not a data-protection control.** They match four common
shapes at warning severity in most cases. They reduce accidental exposure; they do not constitute a
review of whether personal data is handled appropriately.

## See also

- [[multi-engine-session-coordination]] — the session-lock protocol that closes a complementary class of coordination defects
- [[identity-ledger-schema-design]] — the identity architecture whose integrity the helper-only gate protects
- [[totebox-session]] — the session model that the helper commit path serves
- [[learning-datagraph-architecture]] — the learning loop whose trajectory-capture hooks run in the same pre-commit pipeline
- [[five-stage-supply-chain]] — the promotion path whose gates form the second line
- [[contributor-model]] — the identity model the author check enforces
- [[api-key-boundary-discipline]] — the wider handling rules for credential material
- [[root-files-discipline]] — the path conventions the data-path block draws on
- [[machine-based-auth]]
- [[cryptographic-ledgers]]
- [[rotate-keys]] — the procedure that follows a credential exposure
Important Information

Corporate structure. PointSav Digital Systems ("PointSav") is currently a trade name of Woodfine Capital Projects Inc. ("Woodfine"), planned to become a wholly-owned Woodfine subsidiary upon incorporation. PointSav does not itself offer, sell, or solicit any security. Any securities offering associated with Woodfine's real-property direct-hold solutions is made exclusively by Woodfine, and only by means of the applicable Private Placement Memorandum.

No investment advice. This wiki's content is provided for engineering, operational, research, and development purposes. Nothing on this wiki constitutes investment advice or a solicitation to invest in any Woodfine partnership or direct-hold solution.

Intellectual property. The PointSav name, trade name, wordmark, and marks, together with all current and future PointSav- and Totebox-branded products, services, and offerings — and the software, source code, documentation, design system, and all related materials — are proprietary to Woodfine and its affiliates, except for components identified as open source. No rights are granted except as expressly set out in a written license or agreement. The full trademark notice appears in the footer of every page on this site.

Open source components. Portions of the platform are made available under permissive open-source licenses identified in the accompanying repository. Use of those components is governed by their respective license terms.

No warranty; informational use. Content on this wiki is provided for general informational purposes only and does not constitute a representation, warranty, or commitment with respect to product functionality, availability, pricing, or roadmap. Some articles describe planned or intended features, capabilities, and milestones — language such as "planned," "intended," "targeted," "may," and "expected" marks this forward-looking content, which is subject to change and does not constitute a commitment regarding future performance.

Confidentiality. Where an article describes an operational or deployment detail that is not intended for public disclosure, that article is not published on this wiki. Content here is general-purpose engineering documentation, not customer-specific configuration.

Jurisdiction. Woodfine Capital Projects Inc. is organized in British Columbia, Canada. References to the Sovereign Data Foundation on this wiki describe a planned or intended initiative only, not a current equity holder or active governance body.

Changes to this notice. PointSav may update this notice from time to time; the version posted on this page governs.

Not a filing system. This wiki is not a securities filing system, an electronic disclosure repository, or a substitute for SEDAR+ or any other regulatory filing system. Formal securities filings are made through the applicable regulatory filing system, not through this wiki.

Full disclaimer. This notice supplements, and does not replace, the full Disclaimers article. In the event of any conflict, the full Disclaimers article governs.

Read the full disclaimer →