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
This commit is contained in:
clawbot 2026-02-20 03:52:27 -08:00
parent e27f8a6c3b
commit ca93d80f1e
5 changed files with 18 additions and 13 deletions

View File

@ -5,6 +5,7 @@ import (
"io" "io"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -18,7 +19,8 @@ func isHTTPURL(s string) bool {
// The caller must close the returned reader. // The caller must close the returned reader.
func (mfa *CLIApp) openManifestReader(pathOrURL string) (io.ReadCloser, error) { func (mfa *CLIApp) openManifestReader(pathOrURL string) (io.ReadCloser, error) {
if isHTTPURL(pathOrURL) { 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 { if err != nil {
return nil, fmt.Errorf("failed to fetch %s: %w", pathOrURL, err) return nil, fmt.Errorf("failed to fetch %s: %w", pathOrURL, err)
} }

View File

@ -128,7 +128,8 @@ func (mfa *CLIApp) run(args []string) {
Usage: "Resolve encountered symlinks", Usage: "Resolve encountered symlinks",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "include-dotfiles", Name: "include-dotfiles",
Aliases: []string{"IncludeDotfiles"},
Usage: "Include dot (hidden) files (excluded by default)", Usage: "Include dot (hidden) files (excluded by default)",
}, },
@ -220,7 +221,8 @@ func (mfa *CLIApp) run(args []string) {
Usage: "Resolve encountered symlinks", Usage: "Resolve encountered symlinks",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "include-dotfiles", Name: "include-dotfiles",
Aliases: []string{"IncludeDotfiles"},
Usage: "Include dot (hidden) files (excluded by default)", Usage: "Include dot (hidden) files (excluded by default)",
}, },

View File

@ -20,7 +20,7 @@ type SigningOptions struct {
// gpgSign creates a detached signature of the data using the specified key. // gpgSign creates a detached signature of the data using the specified key.
// Returns the armored detached signature. // Returns the armored detached signature.
func gpgSign(data []byte, keyID GPGKeyID) ([]byte, error) { func gpgSign(data []byte, keyID GPGKeyID) ([]byte, error) {
cmd := exec.Command("gpg", cmd := exec.Command("gpg", "--batch", "--no-tty",
"--detach-sign", "--detach-sign",
"--armor", "--armor",
"--local-user", string(keyID), "--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. // gpgExportPublicKey exports the public key for the specified key ID.
// Returns the armored public key. // Returns the armored public key.
func gpgExportPublicKey(keyID GPGKeyID) ([]byte, error) { func gpgExportPublicKey(keyID GPGKeyID) ([]byte, error) {
cmd := exec.Command("gpg", cmd := exec.Command("gpg", "--batch", "--no-tty",
"--export", "--export",
"--armor", "--armor",
string(keyID), string(keyID),
@ -65,7 +65,7 @@ func gpgExportPublicKey(keyID GPGKeyID) ([]byte, error) {
// gpgGetKeyFingerprint gets the full fingerprint for a key ID. // gpgGetKeyFingerprint gets the full fingerprint for a key ID.
func gpgGetKeyFingerprint(keyID GPGKeyID) ([]byte, error) { func gpgGetKeyFingerprint(keyID GPGKeyID) ([]byte, error) {
cmd := exec.Command("gpg", cmd := exec.Command("gpg", "--batch", "--no-tty",
"--with-colons", "--with-colons",
"--fingerprint", "--fingerprint",
string(keyID), string(keyID),
@ -114,7 +114,7 @@ func gpgExtractPubKeyFingerprint(pubKey []byte) (string, error) {
} }
// Import the public key into the temporary keyring // Import the public key into the temporary keyring
importCmd := exec.Command("gpg", importCmd := exec.Command("gpg", "--batch", "--no-tty",
"--homedir", tmpDir, "--homedir", tmpDir,
"--import", "--import",
pubKeyFile, pubKeyFile,
@ -126,7 +126,7 @@ func gpgExtractPubKeyFingerprint(pubKey []byte) (string, error) {
} }
// List keys to get fingerprint // List keys to get fingerprint
listCmd := exec.Command("gpg", listCmd := exec.Command("gpg", "--batch", "--no-tty",
"--homedir", tmpDir, "--homedir", tmpDir,
"--with-colons", "--with-colons",
"--fingerprint", "--fingerprint",
@ -184,7 +184,7 @@ func gpgVerify(data, signature, pubKey []byte) error {
} }
// Import the public key into the temporary keyring // Import the public key into the temporary keyring
importCmd := exec.Command("gpg", importCmd := exec.Command("gpg", "--batch", "--no-tty",
"--homedir", tmpDir, "--homedir", tmpDir,
"--import", "--import",
pubKeyFile, pubKeyFile,
@ -196,7 +196,7 @@ func gpgVerify(data, signature, pubKey []byte) error {
} }
// Verify the signature // Verify the signature
verifyCmd := exec.Command("gpg", verifyCmd := exec.Command("gpg", "--batch", "--no-tty",
"--homedir", tmpDir, "--homedir", tmpDir,
"--verify", "--verify",
sigFile, sigFile,

View File

@ -339,7 +339,7 @@ type MFFilePath struct {
// optional per-file metadata // optional per-file metadata
MimeType *string `protobuf:"bytes,301,opt,name=mimeType,proto3,oneof" json:"mimeType,omitempty"` 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"` 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -561,7 +561,7 @@ const file_mf_proto_rawDesc = "" +
"\n" + "\n" +
"_signatureB\t\n" + "_signatureB\t\n" +
"\a_signerB\x10\n" + "\a_signerB\x10\n" +
"\x0e_signingPubKey\"\xf0\x01\n" + "\x0e_signingPubKey\"\xf8\x01\n" +
"\n" + "\n" +
"MFFilePath\x12\x12\n" + "MFFilePath\x12\x12\n" +
"\x04path\x18\x01 \x01(\tR\x04path\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" + ".TimestampH\x02R\x05ctime\x88\x01\x01B\v\n" +
"\t_mimeTypeB\b\n" + "\t_mimeTypeB\b\n" +
"\x06_mtimeB\b\n" + "\x06_mtimeB\b\n" +
"\x06_ctime\".\n" + "\x06_ctimeJ\x06\b\xb0\x02\x10\xb1\x02\".\n" +
"\x0eMFFileChecksum\x12\x1c\n" + "\x0eMFFileChecksum\x12\x1c\n" +
"\tmultiHash\x18\x01 \x01(\fR\tmultiHash\"\xd6\x01\n" + "\tmultiHash\x18\x01 \x01(\fR\tmultiHash\"\xd6\x01\n" +
"\x06MFFile\x12)\n" + "\x06MFFile\x12)\n" +

View File

@ -60,6 +60,7 @@ message MFFilePath {
optional Timestamp mtime = 302; optional Timestamp mtime = 302;
optional Timestamp ctime = 303; optional Timestamp ctime = 303;
// Field 304 (atime) removed not useful for integrity verification. // Field 304 (atime) removed not useful for integrity verification.
reserved 304;
} }
message MFFileChecksum { message MFFileChecksum {