feat(git-sign-nostr): implement NIP-GS git object signing with Nostr keys#459
Merged
Conversation
…keys
Adds git-sign-nostr, a standalone binary that implements NIP-GS (Git Object
Signing with Nostr Keys). Git invokes it as gpg.x509.program to produce and
verify BIP-340 Schnorr signatures over commit/tag objects.
Key features:
- Sign mode: reads private key, computes SHA-256 signing hash with domain
separation ("nostr:git:v1:"), produces armored base64 JSON signature
- Verify mode: validates BIP-340 signature, checks NIP-OA delegation if
present, emits GPG-compatible status lines for git's trust framework
- NIP-OA support: optional owner attestation binds agent keys to owner
keys with temporal conditions (created_at<, created_at>)
- Security hardening: O_NOFOLLOW+fstat keyfile handling, Zeroizing<String>
for secrets, bounded reads everywhere, fcntl fd validation, env_remove
on subprocesses, fail-closed on invalid auth tags
Also wires the binary into the agent spawn path and extends the e2e test
suite with signing, auth bypass, HMAC tampering, and hook integrity tests.
Implements: NIP-GS (PR #455)
Related: git-credential-nostr (PR #451)
Tests: 31 unit tests, 16 e2e tests, zero clippy warnings
Reconcile feat/auto-git-auth-for-agents hardening with PR test coverage: Architecture improvements: - KeypairGuard RAII for zeroizing keypair on all exit paths - Error enum + Result-based flow (no process::exit that skips destructors) - git_config_strict() for fail-closed auth tag lookup - validate_conditions() grammar parser for NIP-OA condition strings - parse_decimal_u32() rejects leading zeros in timestamps - write_all to stdout (no panic on broken pipe) - enforce_conditions() for structured temporal condition enforcement - O_NOFOLLOW + fstat keyfile validation - fcntl(F_GETFD) validation on status-fd - Bounded reads everywhere (stdin, sig file, git config) - env_remove for secrets in git subprocess - Comprehensive doc comments and README Ported tests from original PR: - 4 hex validation tests (is_lower_hex) - 7 armor edge-case tests (CRLF, wrong begin/end, trailing whitespace, oversized) - 8 envelope parsing tests (missing fields, wrong types, out-of-range) - 1 canonical JSON roundtrip test - 3 format_date tests (epoch, known date, leap day) - 4 OA tag rejection tests (invalid hex, dangerous conditions, wrong label) - 3 signing hash determinism tests
- Add OaVerifyResult enum replacing bool in verify path - Fix trust logic: direct key match takes priority over OA - Add env var length caps (128B keys, 1KB auth tags) - Reject non-canonical decimals in OA conditions (+/- prefix) - read_keyfile_secure returns Zeroizing<String> directly - Check stdout writes in cmd_sign (fail on broken pipe) - Add status_or_fail! macro for critical verify status output - Stricter arg parsing (reject duplicates, require trailing -) - Merge stdin readers into single Result-returning function Codex CLI: Technical correctness 9/10, Security 9/10, Fitness for purpose 9/10.
…rify - Verify keyfile owner UID matches current user (prevents ACL/privilege bypass) - Make invalid --status-fd fatal in verify mode (git depends on status output) - Sign mode still falls back to stderr on invalid fd (advisory status)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
git-sign-nostr, a standalone Rust binary implementing NIP-GS (Git Object Signing with Nostr Keys). Git invokes it asgpg.x509.programto produce and verify BIP-340 Schnorr signatures over commit and tag objects.Vision
This is the signing half of Sprout's agent git authentication story. Combined with git-credential-nostr (#451), agents get a complete git identity:
One Nostr keypair, two programs, full git identity with owner delegation.
What's Included
crates/git-sign-nostr/— The signing binarynostr:git:v1:), produces armored base64 JSON signaturecreated_at<,created_at>)Security hardening
O_NOFOLLOW+fstatkeyfile handling (no TOCTOU, no symlink following)Zeroizing<String>for all secret material with explicit dropfcntl(F_GETFD)validation before borrowing file descriptorsenv_removeon subprocess spawns (prevents key leakage to git)Type safety & code quality
OaVerifyResultenum replaces stringly-typed OA statusgit_config_strict()distinguishes "not set" from "subprocess failed"read_payload_stdin() -> Result(no duplicated logic)Agent spawn integration
git-sign-nostrinto the managed agent runtimegpg.x509.programandcommit.gpgsignautomaticallyE2E test suite
Review Status
Specs & Related PRs