From ca93d80f1ea0cfd12e64125933e7ce36f806e099 Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 20 Feb 2026 03:52:27 -0800 Subject: [PATCH] fix: address PR #32 review findings - Add --batch --no-tty to all GPG invocations (fixes TestManifestTamperedSignatureFails hang) - Add 'reserved 304' to mf.proto for removed atime field - Restore IncludeDotfiles alias on include-dotfiles flag - Replace http.Get with http.Client{Timeout: 30s} in manifest_loader.go --- internal/cli/manifest_loader.go | 4 +++- internal/cli/mfer.go | 6 ++++-- mfer/gpg.go | 14 +++++++------- mfer/mf.pb.go | 6 +++--- mfer/mf.proto | 1 + 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/internal/cli/manifest_loader.go b/internal/cli/manifest_loader.go index 5d575d8..333ac38 100644 --- a/internal/cli/manifest_loader.go +++ b/internal/cli/manifest_loader.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "strings" + "time" "github.com/urfave/cli/v2" ) @@ -18,7 +19,8 @@ func isHTTPURL(s string) bool { // The caller must close the returned reader. func (mfa *CLIApp) openManifestReader(pathOrURL string) (io.ReadCloser, error) { if isHTTPURL(pathOrURL) { - resp, err := http.Get(pathOrURL) //nolint:gosec // user-provided URL is intentional + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Get(pathOrURL) //nolint:gosec // user-provided URL is intentional if err != nil { return nil, fmt.Errorf("failed to fetch %s: %w", pathOrURL, err) } diff --git a/internal/cli/mfer.go b/internal/cli/mfer.go index ed399f2..0ef7dbf 100644 --- a/internal/cli/mfer.go +++ b/internal/cli/mfer.go @@ -128,7 +128,8 @@ func (mfa *CLIApp) run(args []string) { Usage: "Resolve encountered symlinks", }, &cli.BoolFlag{ - Name: "include-dotfiles", + Name: "include-dotfiles", + Aliases: []string{"IncludeDotfiles"}, Usage: "Include dot (hidden) files (excluded by default)", }, @@ -220,7 +221,8 @@ func (mfa *CLIApp) run(args []string) { Usage: "Resolve encountered symlinks", }, &cli.BoolFlag{ - Name: "include-dotfiles", + Name: "include-dotfiles", + Aliases: []string{"IncludeDotfiles"}, Usage: "Include dot (hidden) files (excluded by default)", }, diff --git a/mfer/gpg.go b/mfer/gpg.go index 943f102..2ae607b 100644 --- a/mfer/gpg.go +++ b/mfer/gpg.go @@ -20,7 +20,7 @@ type SigningOptions struct { // gpgSign creates a detached signature of the data using the specified key. // Returns the armored detached signature. func gpgSign(data []byte, keyID GPGKeyID) ([]byte, error) { - cmd := exec.Command("gpg", + cmd := exec.Command("gpg", "--batch", "--no-tty", "--detach-sign", "--armor", "--local-user", string(keyID), @@ -42,7 +42,7 @@ func gpgSign(data []byte, keyID GPGKeyID) ([]byte, error) { // gpgExportPublicKey exports the public key for the specified key ID. // Returns the armored public key. func gpgExportPublicKey(keyID GPGKeyID) ([]byte, error) { - cmd := exec.Command("gpg", + cmd := exec.Command("gpg", "--batch", "--no-tty", "--export", "--armor", string(keyID), @@ -65,7 +65,7 @@ func gpgExportPublicKey(keyID GPGKeyID) ([]byte, error) { // gpgGetKeyFingerprint gets the full fingerprint for a key ID. func gpgGetKeyFingerprint(keyID GPGKeyID) ([]byte, error) { - cmd := exec.Command("gpg", + cmd := exec.Command("gpg", "--batch", "--no-tty", "--with-colons", "--fingerprint", string(keyID), @@ -114,7 +114,7 @@ func gpgExtractPubKeyFingerprint(pubKey []byte) (string, error) { } // Import the public key into the temporary keyring - importCmd := exec.Command("gpg", + importCmd := exec.Command("gpg", "--batch", "--no-tty", "--homedir", tmpDir, "--import", pubKeyFile, @@ -126,7 +126,7 @@ func gpgExtractPubKeyFingerprint(pubKey []byte) (string, error) { } // List keys to get fingerprint - listCmd := exec.Command("gpg", + listCmd := exec.Command("gpg", "--batch", "--no-tty", "--homedir", tmpDir, "--with-colons", "--fingerprint", @@ -184,7 +184,7 @@ func gpgVerify(data, signature, pubKey []byte) error { } // Import the public key into the temporary keyring - importCmd := exec.Command("gpg", + importCmd := exec.Command("gpg", "--batch", "--no-tty", "--homedir", tmpDir, "--import", pubKeyFile, @@ -196,7 +196,7 @@ func gpgVerify(data, signature, pubKey []byte) error { } // Verify the signature - verifyCmd := exec.Command("gpg", + verifyCmd := exec.Command("gpg", "--batch", "--no-tty", "--homedir", tmpDir, "--verify", sigFile, diff --git a/mfer/mf.pb.go b/mfer/mf.pb.go index 6312c21..d3ef0de 100644 --- a/mfer/mf.pb.go +++ b/mfer/mf.pb.go @@ -339,7 +339,7 @@ type MFFilePath struct { // optional per-file metadata MimeType *string `protobuf:"bytes,301,opt,name=mimeType,proto3,oneof" json:"mimeType,omitempty"` Mtime *Timestamp `protobuf:"bytes,302,opt,name=mtime,proto3,oneof" json:"mtime,omitempty"` - Ctime *Timestamp `protobuf:"bytes,303,opt,name=ctime,proto3,oneof" json:"ctime,omitempty"` // Field 304 (atime) removed — not useful for integrity verification. + Ctime *Timestamp `protobuf:"bytes,303,opt,name=ctime,proto3,oneof" json:"ctime,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -561,7 +561,7 @@ const file_mf_proto_rawDesc = "" + "\n" + "_signatureB\t\n" + "\a_signerB\x10\n" + - "\x0e_signingPubKey\"\xf0\x01\n" + + "\x0e_signingPubKey\"\xf8\x01\n" + "\n" + "MFFilePath\x12\x12\n" + "\x04path\x18\x01 \x01(\tR\x04path\x12\x12\n" + @@ -574,7 +574,7 @@ const file_mf_proto_rawDesc = "" + ".TimestampH\x02R\x05ctime\x88\x01\x01B\v\n" + "\t_mimeTypeB\b\n" + "\x06_mtimeB\b\n" + - "\x06_ctime\".\n" + + "\x06_ctimeJ\x06\b\xb0\x02\x10\xb1\x02\".\n" + "\x0eMFFileChecksum\x12\x1c\n" + "\tmultiHash\x18\x01 \x01(\fR\tmultiHash\"\xd6\x01\n" + "\x06MFFile\x12)\n" + diff --git a/mfer/mf.proto b/mfer/mf.proto index d77b8e4..91b013a 100644 --- a/mfer/mf.proto +++ b/mfer/mf.proto @@ -60,6 +60,7 @@ message MFFilePath { optional Timestamp mtime = 302; optional Timestamp ctime = 303; // Field 304 (atime) removed — not useful for integrity verification. + reserved 304; } message MFFileChecksum {