Skip to content

Commit 57e27e2

Browse files
Merge pull request #405 from QueryaHub/issue/396-marketplace-sha256
fix(marketplace): require SHA256 checksum before HttpMarketplace install
2 parents 135404d + d245e7c commit 57e27e2

2 files changed

Lines changed: 76 additions & 10 deletions

File tree

‎lib/core/market/http_marketplace_repository.dart‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,26 @@ class HttpMarketplaceRepository implements MarketplaceRepository {
125125

126126
try {
127127
// Step 2: SHA-256 Integrity Verification (Critical Security Check)
128-
if (manifest.sha256Checksum != null && manifest.sha256Checksum!.trim().isNotEmpty) {
129-
final bytes = await archiveFile.readAsBytes();
130-
final actualSha256 = sha256.convert(bytes).toString().toLowerCase();
131-
final expectedSha256 = manifest.sha256Checksum!.trim().toLowerCase();
132-
if (actualSha256 != expectedSha256) {
133-
throw MarketplaceException(
134-
'SHA256 checksum mismatch for "${manifest.id}". Expected: $expectedSha256, Actual: $actualSha256. Installation aborted.',
135-
);
136-
}
128+
final expectedSha256 = manifest.sha256Checksum?.trim().toLowerCase();
129+
if (expectedSha256 == null || expectedSha256.isEmpty) {
130+
throw MarketplaceException(
131+
'Extension manifest is missing SHA256 checksum for "${manifest.id}". '
132+
'Installation aborted.',
133+
);
134+
}
135+
136+
final bytes = await archiveFile.readAsBytes();
137+
final actualSha256 = sha256.convert(bytes).toString().toLowerCase();
138+
if (actualSha256 != expectedSha256) {
139+
throw MarketplaceException(
140+
'SHA256 checksum mismatch for "${manifest.id}". '
141+
'Expected: $expectedSha256, Actual: $actualSha256. Installation aborted.',
142+
);
137143
}
138144

139145
onProgress?.call(0.85);
140146

141147
// Step 3: Safe Archive Extraction (Preventing Path Traversal / Zip Bomb - Issue #242)
142-
final bytes = await archiveFile.readAsBytes();
143148
final archive = ZipDecoder().decodeBytes(bytes);
144149

145150
final dir = await ExtensionPaths.extensionsDirectory();

‎test/core/market/marketplace_repository_test.dart‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,67 @@ void main() {
231231
);
232232
});
233233

234+
test('install aborts when SHA256 checksum is missing', () async {
235+
final archive = Archive();
236+
archive.addFile(ArchiveFile('test.txt', 4, utf8.encode('good')));
237+
final zipBytes = ZipEncoder().encode(archive);
238+
239+
final mockClient = MockClient((request) async {
240+
return http.Response.bytes(zipBytes, 200);
241+
});
242+
243+
final repo = HttpMarketplaceRepository(client: mockClient);
244+
const manifest = ExtensionManifest(
245+
id: 'test.no-sha256',
246+
name: 'No SHA256',
247+
version: '1.0.0',
248+
publisher: 'Test',
249+
type: ExtensionType.theme,
250+
engines: {'querya_desktop': '*'},
251+
downloadUrl: 'http://localhost:8000/test.zip',
252+
);
253+
254+
expect(
255+
() => repo.install(manifest),
256+
throwsA(isA<MarketplaceException>().having(
257+
(e) => e.message,
258+
'message',
259+
contains('missing SHA256 checksum'),
260+
)),
261+
);
262+
});
263+
264+
test('install aborts when SHA256 checksum is empty', () async {
265+
final archive = Archive();
266+
archive.addFile(ArchiveFile('test.txt', 4, utf8.encode('good')));
267+
final zipBytes = ZipEncoder().encode(archive);
268+
269+
final mockClient = MockClient((request) async {
270+
return http.Response.bytes(zipBytes, 200);
271+
});
272+
273+
final repo = HttpMarketplaceRepository(client: mockClient);
274+
const manifest = ExtensionManifest(
275+
id: 'test.empty-sha256',
276+
name: 'Empty SHA256',
277+
version: '1.0.0',
278+
publisher: 'Test',
279+
type: ExtensionType.theme,
280+
engines: {'querya_desktop': '*'},
281+
downloadUrl: 'http://localhost:8000/test.zip',
282+
sha256Checksum: ' ',
283+
);
284+
285+
expect(
286+
() => repo.install(manifest),
287+
throwsA(isA<MarketplaceException>().having(
288+
(e) => e.message,
289+
'message',
290+
contains('missing SHA256 checksum'),
291+
)),
292+
);
293+
});
294+
234295
test('install prevents Path Traversal during archive unpacking (Issue #242)', () async {
235296
final archive = Archive();
236297
archive.addFile(ArchiveFile('../evil.txt', 4, utf8.encode('evil')));

0 commit comments

Comments
 (0)