Skip to content

Git That Shit v0.3.0 — final security hardening - #1

Merged
gitdamnit merged 8 commits into
masterfrom
feat/git-that-shit-v2-hardening
Oct 17, 2026
Merged

gitdamnit merged 8 commits into
masterfrom
feat/git-that-shit-v2-hardening

Conversation

@gitdamnit

Copy link
Copy Markdown
Owner

Git That Shit v0.3.0 — final security hardening

Focused hardening and release-readiness work on top of the existing v0.3.0 development branch. No architectural rewrite;
the shadow-git design, CLI and V2 plugin surface are unchanged.

1. Snapshot failure safety

  • SnapshotResult.verified — a snapshot is only reported as existing when its commit is confirmed to resolve in the
    shadow repository (git rev-parse --verify <hash>^{commit}). Callers key protection off verified, never off
    committed alone.
  • snapshot.failureMode (standard default, strict opt-in), implemented in the new src/engine/safety.ts:
    • standard — the operation continues, a clear credential-free warning is emitted, and no recovery point is
      claimed.
    • strict — Git That Shit denies exclusively classified destructive operations (e.g. git reset --hard,
      git clean -fd, rm -rf) via the OpenCode V2 permission.evaluate gate when their protective snapshot fails.
      Risky package-manager operations, config writes and harmless commands are never blocked.
  • snapshot.gitTimeoutMs bounds shadow-repository git subprocesses; a timeout is a structured failure.
  • no-changes is treated as an existing recovery point (matching restore) rather than a failure.
  • Command previews, manual messages and error strings are text-redacted before they can reach the commit message, the
    manifest, a result object or a log line.

Strict-mode limitation (documented, not hidden). execute.before returns void and cannot veto a tool; only
permission.evaluate can, and the SDK does not specify hook ordering. Strict tool-call blocking is therefore
best-effort and fails open with an explicit warning when no gate is exposed. The restore path's strict check is
guaranteed, because Git That Shit owns it. Runtime confirmation against a live OpenCode host was not possible in the
isolated environment; the gate is verified against the published 2.0.21 types and the V2 harness.

2. Filesystem permissions and secret protection

  • New src/shared/permissions.ts: 0700 directories and 0600 files for Git That Shit's own storage — config,
    manifest, lock, snapshot copies and prune bundles — applied after mkdir so umask cannot widen them. Only
    .git-that-shit/ is touched; user project files and unrelated directories are never chmod'ed.
  • POSIX-only; Windows relies on ACLs; filesystems that reject chmod degrade safely to a no-op.
  • gts doctor reports storage permissions and warns on group/other access.
  • Fail-closed redaction is preserved; no credential reaches history, the manifest, results, logs or bundles.

3. Restore security

  • Refuse a destination that is itself a symlink.
  • Resolve the nearest existing ancestor with realpath and require it to stay within the real project root, so a
    symlinked directory cannot redirect a write outside the project.
  • Write atomically: O_EXCL|O_NOFOLLOW sibling temp file + rename, closing the validate-then-write symlink
    substitution window. Temp files are removed on failure.
  • Redacted content is still never restored (even with force); overwriting a conflict still requires force; dry-run
    remains the default; restores remain under the project lock.
  • Strict mode refuses a restore whose mandatory pre-restore snapshot cannot be created; standard mode continues with a
    warning and claims no recovery point.

4. Plugin independence

Re-verified: no @gitdamnit/* / Stranger Danger / Checkpoint Orchestrator / CheapCode imports in src/; the only
runtime dependency remains @opencode/plugin; secret handling and snapshots are self-contained; hooks do not assume an
execution order; state stays under .git-that-shit/; the project lock serializes concurrent sessions.

Validation

Clean npm ci, typecheck, lint, build, full bun test suite (322 passing / 0 failing across 24 files, 35 new
tests), CLI smoke tests, npm pack validation, and an installed-package import test. All tests are hermetic (isolated
/tmp projects and isolated config dirs); no live provider credentials were used.

Documentation

Updated README.md, docs/ARCHITECTURE.md, docs/SECURITY.md, docs/INTEROP.md, CHANGELOG.md, and added
docs/RELEASE-0.3.0.md with the full audit findings and remaining limitations.

gitdamnit added 8 commits May 13, 2026 16:34
Recover development history by merging origin/master into the recovered
local commit and commit the recovered tests.

Engine fixes:
- restore: execute exactly once, fully inside the lock; report conflicts;
  never silently overwrite user changes; pre-restore snapshot no longer
  blocks on no-changes
- snapshot: protect the entire operation with one exclusive project lock
- lock: atomic exclusive-create acquisition (no read-then-write TOCTOU);
  ownership-checked release and stale-lock removal
- security: redact global OpenCode config; text/PEM/authorization redaction;
  fail closed on unprocessable content; reject path traversal
- paths: canonical layout documented with legacy migration
- version: read packaged version instead of hardcoding
- prune: real, guarded history rewrite with safety bundle; preview default

OpenCode V2 migration:
- @opencode/plugin 2.0.21, Plugin.define({ id, setup })
- all eight gts_* tools via ctx.tool.transform with JSON Schema inputs
- execute.before for pre-op/pre-edit; event stream for session.created,
  session.compaction.ended, filesystem.changed; cleanup on unload

Testing: 274 tests across 19 files (concurrency, restore, prune, security,
CLI, packaging, V2 plugin surface). CI runs typecheck/lint/build/test.

Packaging: ESM, type declarations, dist no longer committed, reduced deps.

Docs: README, CHANGELOG 0.3.0, docs/ARCHITECTURE, docs/SECURITY, docs/INTEROP
…ed files

Runtime validation against an isolated OpenCode 2.0.21 server and a local
OpenAI-compatible mock provider surfaced two real defects:

- filesystem.changed is NOT emitted for agent tool writes, so post-edit
  snapshots were missed. Post-edit is now driven by
  ctx.tool.hook("execute.after") on a completed edit tool; filesystem.changed
  is retained as a debounced fallback for out-of-band changes.
- Restoring a snapshot would have written [REDACTED] placeholders over live
  config files, destroying real secrets. Restore now always skips redacted
  files and reports them.

Adds execute.after and redaction-safe restore tests (278 tests total) and
documents the verified hook mapping.
Audit + hardening so Git That Shit works alone and in any companion
combination with no ordering assumptions and no shared framework.

- Failure isolation: wrap execute.before/execute.after and every subscribed
  event in a safe() guard so a snapshot error, malformed project file, or
  absent companion can never surface as a tool failure or affect other plugins.
- Hooks are read-only with respect to the tool event (never mutate event/input).
- Documented and verified (OpenCode 2.0.21) that async execute.before is awaited
  before the tool runs, so pre-operation snapshots are genuinely blocking.
- Added tests/independence.test.ts (9 tests): no cross-plugin imports, no
  companion installed, read-only hooks, malformed companion data, no writes into
  companion storage, reload/multi-setup isolation, malformed-event stream
  resilience, namespaced config/storage.
- Docs: independence standard, companion matrix, companion-absent behavior,
  optional read-only interop surface, and Docker Plugin Test Lab compatibility.
  Removed order-dependent phrasing.

287 tests / 20 files.
Snapshot failure safety
- Add SnapshotResult.verified: a snapshot is only reported as existing when
  its commit is confirmed to resolve in the shadow repository.
- Add snapshot.failureMode ("standard" default, "strict" opt-in) via a new
  engine/safety module. Standard continues with a clear warning and never
  claims a recovery point; strict denies classified destructive operations
  only, through the OpenCode V2 permission.evaluate gate, and fails open with
  an explicit warning when the SDK exposes no gate.
- Add snapshot.gitTimeoutMs to bound shadow-repository git subprocesses.
- Treat no-changes as an existing recovery point in the plugin (matching
  restore) instead of a failure.
- Redact command previews, manual messages and error strings before they can
  reach the commit message, manifest, result object or logs.

Filesystem permissions and secret protection
- New shared/permissions module: 0700 directories and 0600 files for Git That
  Shit storage (config, manifest, lock, snapshot copies, prune bundles),
  applied after mkdir without recursing into user project directories.
- Doctor reports storage permissions and warns on group/other access.

Restore security
- Refuse symlinked destinations and destinations whose nearest existing
  ancestor resolves outside the project.
- Write atomically via an O_EXCL|O_NOFOLLOW temp file + rename so a symlink
  cannot be substituted between validation and writing; clean up temp files on
  failure.
- Strict mode refuses a restore whose pre-restore snapshot cannot be created;
  standard mode continues with a warning and claims no recovery point.

Tests and docs
- 35 new regression tests (321 -> 322 total) covering failure injection,
  permissions, restore adversarial cases and strict-mode blocking.
- Update README, ARCHITECTURE, SECURITY, INTEROP and add RELEASE-0.3.0 audit.
- Restore GIT_THAT_SHIT_GLOBAL_CONFIG_DIR in afterAll so the new test files
  cannot leak isolation into another file (CI caught this in tests/paths.test.ts).
- Make paths' default-config-path assertion robust to a leaked override.
- Raise the suite-wide Bun test timeout to 30s in bunfig.toml so subprocess-heavy
  tests do not flake on slow CI runners; no assertion is weakened.
… project docs

Distribution and documentation changes only; no source, engine, plugin or test
logic is modified, and the verified commit 6069f91 remains intact.

- Add the unmodified Apache-2.0 LICENSE (package and README already declared
  Apache-2.0 but no licence text was shipped) and a NOTICE attribution file;
  include NOTICE in the published artifact.
- Gate the npm publish workflow to manual workflow_dispatch so publishing a
  GitHub release can never trigger an automatic npm publish.
- Record the non-npm distribution state in the README installation section.
- Add the canonical project documents (PRODUCT_REQUIREMENTS.md,
  DECISION_LOG.md, PROJECT_STATUS.md) and docs/RELEASE-0.3.0-EVIDENCE.md, which
  pins the repository suite and the independent Docker Plugin Test Lab run to
  the verified commit.
@gitdamnit
gitdamnit marked this pull request as ready for review October 17, 2026 00:34
@gitdamnit
gitdamnit merged commit c752e58 into master Oct 17, 2026
2 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T00:35:38.452665Z 237c352 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

gitdamnit added a commit that referenced this pull request Oct 17, 2026
Documentation only. Records the durable release result now that PR #1 has
merged and the GitHub release is published:

- PROJECT_STATUS.md: phase Released; post-release verification entries (merge
  commit, tag, released source archive); npm publication recorded as Blocked.
- docs/RELEASE-0.3.0-EVIDENCE.md: section 5 pins the released commit, tag,
  release URL, archive sha256 and the fresh-build verification.

The release tag v0.3.0 remains at c752e58; this commit does not alter it.
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