Commit Graph
19 Commits
Author SHA1 Message Date
bchanot 6468eda495 fix(push-guard): fail closed on token floods, git failures and quoted cd targets
Hardening after the security gate on a2ac018 (3 MEDIUM, all closed and
re-measured):

- dir tokens are deduplicated and capped: more than 20 distinct cd/-C
  targets in one command denies before any git fork (20000 tokens: 0.15 s
  against the 10 s hook timeout that used to turn a flood into an allow)
- a git or cd failure while reading gitflow.autopush denies instead of
  reading as auto (git absent, usage error, unenterable dir); the key
  being unset is the only "auto" answer; the decision is recorded only
  after one candidate was evaluated cleanly, else the EXIT trap denies
- cd/pushd/-C targets that follow a quote or backtick (bash -c '…') are
  extracted; quote characters are excluded from unquoted tokens

User decision (contract, gated): both fail-closed cases also fire in
auto mode on such pathological commands; silence in auto mode holds for
every ordinary push. Header limits list the residual misses (quotes or
backslashes inside a token, cumulative relative cd, unparseable payload)
backed by the soft_deny rule. Tests: 71 checks (T48–T50b added).
2026-10-07 12:34:56 +02:00
bchanot a2ac0189f7 feat(gitflow): push-guard hook denies Claude's git push in manual-push mode
Run B of manual-push mode (BDR-111). With `gitflow.autopush false`
nothing stops Claude from typing `git push` itself: the `ask` tier is
inert under auto mode. This adds the mechanical block the user chose.

- hooks/push-guard.sh (PreToolUse, matcher Bash|Monitor, timeout 10):
  detects a push in the command text (strict, quote-stripped loose and
  alias patterns; backslash-newline folded in bash, BSD sed/grep only),
  reads gitflow.autopush in the payload cwd and in every literal -C/cd
  dir the command names (global config counts outside a repo), denies
  with the documented JSON form and a reason that tells the user to run
  the command with `!`. Unparseable value = manual (fail closed); once
  a push is detected an EXIT trap emits a static deny on any internal
  error. jq missing = one stderr warning, allow (sibling-hook policy).
- settings.json: hook wiring; 18 deny entries closing the write forms
  of the human-only toggle (any `git … config` spelling, section
  removal/rename, `-c`, config env overrides, direct edits of git config
  files); one soft_deny on pushing in manual mode in any form, with no
  per-turn clearance; the routing-around rule names hook refusals.
- hooks/session-start.sh: `🔒 push : manual (autopush=false) — ! git push`
  banner line when the key reads false (padding in bytes).
- lib/tests/push-guard.test.sh: 61 checks (push forms, over-blocks,
  invalid value, global key, fail-closed trap, no-jq, wiring, banner).

Known limits are listed in the hook header; the soft_deny rule is the
backstop. Run C (skills that push on their own) and run D (fail-closed
readers everywhere) follow. Do not enable manual mode at work before C.
2026-10-07 12:24:35 +02:00
bchanot 16c3dc8fbb chore(memory): BDR-111 + LRN-191..193 — feat manual-push-mode run A 2026-10-06 18:03:36 +02:00
bchanot afd6073371 docs(gitflow): manual-push mode — SETTINGS push discipline, gitflow skill rows, CHANGELOG 2026-10-06 18:03:35 +02:00
bchanot e6cccc1740 chore(memory): journal + contract/plan — feat manual-push-mode run A 2026-10-06 17:50:24 +02:00
bchanot 2fc88304ac feat(gitflow): manual-push mode honoured by the lib, quiet unpushed-guard
`gitflow.autopush false` (human-set git config) now means "nothing is
pushed" end to end, not only in the post-commit/post-merge hooks:

- lib/gitflow.sh: `_gitflow_push_off` is the single reader of
  GITFLOW_NO_PUSH / gitflow.autopush for the lib's push sites; `start`
  and `finish` stop pushing in manual mode. `gitflow_delete` checks out
  the base that contains the branch and drops a lagging upstream before
  `git branch -d` (LRN-161: `-d` judges against the upstream when set).
  Skipped remote deletes say `left in place`; `_gitflow_sync_base`
  replaces the silent `pull --ff-only || true` and warns when a base is
  behind origin and cannot fast-forward.
- hooks/unpushed-guard.sh: manual mode is silent at Stop and gives one
  `ℹ manual push mode:` line at SessionStart counting every local
  branch; an unparseable value is named and treated as auto.
- CLAUDE.global.md: manual-push mode doctrine, "ahead = defect" scoped
  to auto mode.
- Tests: gitflow-test T18m block (T18m0, T18i-T18o, 7 cases),
  unpushed-guard T10-T16.

Follow-ups (TODO.md): run B push-guard hook + settings deny widening +
banner; run C skills that push on their own (/close STEP 5C, …).
Do not enable manual mode on the work machine before B and C land.
2026-10-06 17:49:20 +02:00
bchanot fa67664bac chore(memory): journal — release 2.0.0 cut and tagged 2026-10-06 2026-10-06 16:15:10 +02:00
bchanot 4d81f5a8fc Merge release/2.0.0 into develop 2026-10-06 16:13:13 +02:00
bchanot 9ef66e239b chore(release): 2.0.0 — MIT LICENSE, README License section, Linux note reworded 2026-10-06 16:12:52 +02:00
bchanot b47bba7faf Merge develop into release/2.0.0 2026-10-06 16:07:22 +02:00
bchanot 370f35a5a1 Merge bugfix/macos-portability into develop 2026-10-06 16:06:32 +02:00
bchanot b3f3ae2441 chore(memory): doc-sync deferred items + journal 2026-10-06 2026-10-06 16:00:54 +02:00
bchanot c6fb2e4219 docs: global sync before 2.0.0 — components, slash table, profiles, GSD 3.0.0, migration guide, package-install guard 2026-10-06 16:00:41 +02:00
bchanot a84aaaabbd chore(memory): prune 2026-10-06 — BLK-028 merge, bounded caveman pass on 37 entries 2026-10-06 15:40:26 +02:00
bchanot 6bc7c12bf3 chore(memory): reconcile 2026-10-06 — npm deny items closed, Makefile help re-verified open 2026-10-06 15:24:59 +02:00
bchanot 276fa67c80 chore(memory): BDR-110 + BLK-026/027 + LRN-189/190 + journal — macOS portability bugfix, contract + plan 2026-10-06 15:19:53 +02:00
bchanot e5b6cc5ef4 docs(changelog): macOS portability fix + deferred Linux run under [Unreleased] 2026-10-06 15:19:22 +02:00
bchanot 0efdff0d55 fix(portability): make test green on macOS, GNU-only idioms replaced
The suite and seven libs assumed a GNU userland: `cmd | grep -q` under
pipefail (grep exits at the first match, the producer takes SIGPIPE,
rc 141 → 15 false "merged into" FAILs in gitflow-test), `sed -i` with no
suffix, `wc -l` padding compared as a string, `stat -c`, `touch -d`,
`realpath -m` (gstack-links refusal never fired), bare `timeout` off the
sanitized PATH (design gate READY BUT UNVERIFIED), `grep -oP` (update-all
emptied the plugin list). Thirteen suites were red on this machine.

Portable forms on the native userland of both OS: producer captured out
of the pipeline, `sed -i.bak` in tests and a temp-sibling `sed_profile`
on the user dotfile, `tr -d ' '`, python3 perms, `touch -t`, a
`realpath -m` emulation that refuses `..`, perl `alarm` for the 15 s
bound, `sed -n` token extraction. Regression tests: gstack-links T4b,
doctrine-citers Alphabet/Alpha flip, profile-set-managed T18 (failing CLI
no longer aborts `set`), new portability-census suite over the tracked
shell files. Linux run deferred (see TODO).
2026-10-06 15:10:20 +02:00
bchanot 9d421e4493 chore(release): 2.0.0 — version.txt + CHANGELOG 2026-10-06 12:12:02 +02:00