report an oversized download manifest instead of verifying the signature alone
What changed, and why it matters
This commit changes how Sparrow Wallet's download verifier handles large manifest files. Previously, if a downloaded manifest exceeded 100 KB, the software would reject it outright and stop verification. Now, if the oversized file does not look like a manifest (no PGP signed-message header and no typical hash lines), it is treated as a release file that the signature signs directly. The change also adds a more descriptive error message. The commit title frames this as reporting an oversized manifest rather than only verifying the signature, suggesting a fix for a verification-logic edge case.
Review the drongo submodule diff at 0d8aaac106b52e71510b5e0bfa97e7103c8aefbc to understand what underlying verification behavior changed. Verify that treating any oversized non-manifest-looking file as a signed release does not allow an attacker to substitute a large malicious file and have it accepted as a direct-signed release. Consider adding explicit user-facing warnings when an oversized file is treated as a release rather than a manifest.
Security signals we found
Change in signature/verification logic
Oversized manifest handling altered
Potential bypass of manifest-based hash verification for large files
No explicit CVE or security advisory referenced in commit
Evidence from the diff
In DownloadVerifierDialog.java, getManifest() now throws an InvalidManifestException with a message instead of a bare exception. The catch block for InvalidManifestException now calls isManifestContent() on the oversized file. If the file does not appear to be a manifest, it is assumed to be a release file and stored in release.set(manifestFile), with verify=false so signature verification proceeds against that file. A new isManifestContent() helper reads up to 1 KB and checks for a PGP clearsigned header or a sha256sum-style hash line. The drongo submodule was also bumped to 0d8aaac106b52e71510b5e0bfa97e7103c8aefbc.
Changed components
src/main/java/com/sparrowwallet/sparrow/control/DownloadVerifierDialog.javadrongo submoduleInspect captured patch +25 / −5
### drongo
@@ -1 +1 @@
-Subproject commit 96c91f506ef96114cdd7414c5fb8cdd33224fb3d
+Subproject commit 0d8aaac106b52e71510b5e0bfa97e7103c8aefbc
### src/main/java/com/sparrowwallet/sparrow/control/DownloadVerifierDialog.java
@@ -50,6 +50,9 @@ public class DownloadVerifierDialog extends Dialog<ButtonBar.ButtonData> {
private static final DateFormat signatureDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy z");
private static final long MAX_VALID_MANIFEST_SIZE = 100 * 1024;
+ private static final int MANIFEST_HEADER_LENGTH = 1024;
+ private static final Pattern MANIFEST_HASH_LINE = Pattern.compile("^[0-9a-fA-F]{64}\\s+\\S+.*");
+ private static final String CLEARSIGNED_HEADER = "-----BEGIN PGP SIGNED MESSAGE-----";
private static final String SHA256SUMS_MANIFEST_PREFIX = "sha256sums";
private static final List<String> SIGNATURE_EXTENSIONS = List.of("asc", "sig", "gpg");
@@ -216,8 +219,11 @@ public DownloadVerifierDialog(File initialFile) {
log.debug("Error reading manifest file", e);
verify = false;
} catch(InvalidManifestException e) {
- release.set(manifestFile);
- verify = false;
+ //A file too large to be a manifest is assumed to be a release file the signature signs directly, unless it still looks like a manifest
+ if(!isManifestContent(manifestFile)) {
+ release.set(manifestFile);
+ verify = false;
+ }
}
if(verify) {
@@ -466,7 +472,7 @@ private Field setupResultField(Label label, String title) {
public static Map<File, String> getManifest(File manifest) throws IOException, InvalidManifestException {
if(manifest.length() > MAX_VALID_MANIFEST_SIZE) {
- throw new InvalidManifestException();
+ throw new InvalidManifestException("Manifest file is larger than " + (MAX_VALID_MANIFEST_SIZE / 1024) + "KB");
}
try(InputStream manifestStream = new FileInputStream(manifest)) {
@@ -494,6 +500,16 @@ public static Map<File, String> getManifest(InputStream manifestStream) throws I
return manifest;
}
+ private static boolean isManifestContent(File file) {
+ try(InputStream inputStream = new FileInputStream(file)) {
+ String header = new String(inputStream.readNBytes(MANIFEST_HEADER_LENGTH), StandardCharsets.UTF_8);
+ return header.lines().anyMatch(line -> line.startsWith(CLEARSIGNED_HEADER) || MANIFEST_HASH_LINE.matcher(line).matches());
+ } catch(IOException e) {
+ log.debug("Error reading manifest file", e);
+ return false;
+ }
+ }
+
private String getManifestHash(String contentFileName, Map<File, String> manifest) {
for(Map.Entry<File, String> entry : manifest.entrySet()) {
if(contentFileName.equalsIgnoreCase(entry.getKey().getName())) {
@@ -822,5 +838,9 @@ private String sha256(InputStream stream) throws IOException {
}
}
- private static class InvalidManifestException extends Exception { }
+ private static class InvalidManifestException extends Exception {
+ public InvalidManifestException(String message) {
+ super(message);
+ }
+ }
}Why this scored 46/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.