Skip to content

Harden per security re-scan + test-suite adequacy plans - #6

Open
Schroedingberg wants to merge 8 commits into
mainfrom
harden-2026-07
Open

Harden per security re-scan + test-suite adequacy plans#6
Schroedingberg wants to merge 8 commits into
mainfrom
harden-2026-07

Conversation

@Schroedingberg

Copy link
Copy Markdown
Owner

Implements both plan documents (.claude/security-rescan-2026-07-11.md, .claude/test-suite-adequacy-2026-07-12.md): all Medium/Low security findings (M1–M4, L1–L6) and all test-gap items (P1–P7).

App layer (2424a54)

  • /history chunking (M4/P4i): replies split at line boundaries <3500 chars; :reply may now be a string or seq of strings (CONTRACT.md amended minimally).
  • Honest failure replies (L6/P4ii): "⚠ not recorded" only when append! actually failed; a confirmation-send failure after a successful commit logs instead of lying.
  • Contained effects (L4/P5): all run-effects! branches wrapped; failures log to stderr, never escape into the polling loop, and the outermost handler never calls send!.
  • Category sanitization (L1): Expense schema constrains category segments (12,30 x #Evil;Cat now rejected with ⚠).
  • Closed Effect schema (P2): the next :replly-style typo throws instead of silently no-oping; injected-fns map asserted complete.
  • clojure -M:bot check (P6): config + getMe health probe, exit 0/1 — verified against the live Telegram API (401 with a fake token → exit 1).
  • Wire fixture (P7): real sanitized getUpdates JSON (message / edit / photo-caption) driven through handle-update.

CI (6d4c0b7)

  • All actions pinned to commit SHAs (each resolved + cross-verified via the GitHub API, # vX.Y.Z comments kept).
  • deploy.yml secrets moved from job-level to per-step env; PR runs are now fully secret-free (tofu fmt/validate only; plan/apply/destroy are dispatch-only).
  • Image cosign-signed by digest (keyless, GitHub OIDC; cosign-installer v3.10.1 → cosign 2.6.1, deliberately, so the classic signature format stays verifiable server-side).
  • New: clojure -M -e "(require 'ledger.main)" step (a typo'd require no longer sails into a production crash loop) and a native-arch image smoke test before the multi-arch push (gha layer cache makes the push build nearly free).
  • Announce curl gets the token via step env + in-shell expansion.

Infra/deploy (27ae182)

  • Metadata firewall (M1): new bbledger-firewall.service (idempotent iptables -I DOCKER-USER -d 169.254.169.254 -j DROP, reboot-persistent, bot ordered after it).
  • Verified + health-gated autodeploy (M3/P6): pull → digest compare → cosign verify (repo-workflow identity, GitHub OIDC issuer) → one-shot clojure -M:bot check against the real /data:ro → only then restart. Any failure leaves the old bot running; the timer retries. cosign v3.1.1 installed via cloud-init with per-arch sha256 verification (checksums confirmed against real downloads).
  • Non-root hardened container (L3): USER bbledger (fixed uid 1000), --cap-drop=ALL --security-opt=no-new-privileges on every docker-run site; cloud-init chowns /srv/bbledger/data to uid 1000 and the host push unit gets safe.directory. Verified empirically: image built and run hardened as uid 1000 with a host-owned mount — git commit and classpath both work.
  • Pinned GitHub host keys (L2): ssh-keyscan TOFU replaced with the three keys from api.github.com/meta (fetched 2026-07-12).

Verification

  • bb test and clojure -M:test over the combined diff: 60 tests, 204 assertions, 0 failures each (dual-runtime parity holds); lint clean; hledger conformance ran.
  • cloud-init YAML parses raw AND after simulated templatefile()/indent() rendering; all units round-trip byte-identical (verbatim-injection invariant preserved). systemd-analyze verify clean. Autodeploy ExecStart de-escaped and passed sh -n.
  • tofu validate could not run (tofu not installed locally); the main.tf change is one templatefile entry mirroring the existing seven — the deploy.yml PR check will validate it.

Notes for review

  • Includes e1d9efc (the clojure.test/are refactor from test-are) — the new tests build on it.
  • Accepted residuals are documented in CLAUDE.md's rewritten "Security posture" section; notable: the first boot of a fresh VM still pulls :latest unverified (the cosign gate covers autodeploy only), and cosign v2-signs / v3-verifies — believed compatible (classic format), but unprovable until the first real CI run signs an image.
  • CLAUDE.md's Security TODO is replaced by a posture summary; the two plan documents in .claude/ are untracked and left that way.

🤖 Generated with Claude Code

Schroedingberg and others added 8 commits July 12, 2026 12:59
are's failure output substitutes the failing row into the template,
which beats doseq + (is ... (pr-str x)) labels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…closed Effect schema, category sanitization, check cmd

M4/L1/L4/L6 from the 2026-07-11 security re-scan; P2/P4/P5/P6(app)/P7
from the test-suite adequacy analysis.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ger.main + image smoke test

M2/M3(sign)/L5 from the security re-scan; P1/P3 from the test adequacy
analysis. deploy.yml PR runs are now secret-free (fmt+validate only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ontainer, metadata firewall, pinned host keys

M1/M3(verify)/L2/L3 from the security re-scan; P6 from the test
adequacy analysis. New bbledger-firewall.service blocks containers
from 169.254.169.254 and orders the bot after it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…m hledger

The bot only ever needed infer-balances, apply-auto and account-sums;
render/rows/rollups, balance-sheet, income-statement, ledger.cli and the
unused core/balances replicated a tool that is already the conformance
oracle. The ledger file stays hledger-compatible, so `hledger -f ... bal
--auto` replaces `bb ledger`. Also drops the now-meaningless CLI-vs-hledger
benchmark and the babashka/cli dep on both runtimes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ract

289 lines of frozen prose duplicated what the malli schemas (Expense,
Config, Effect) and docstrings already enforce, and could silently drift.
The one non-executable piece worth keeping — the parsed txn/posting/rule
data shapes — moves into the ledger.parse ns docstring, where the shapes
are produced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The STATE_* / S3-backend path was never activated: one disposable VM whose
real state lives in the data repo doesn't need tfstate diffs. Removes the
backend.tf generation, the HAS_STATE conditionals and the AWS_* env from
every step; destroy is fully handled by the wipe step. The STATE_* repo
secrets are dead and can be deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant