@@ -108,6 +108,19 @@ class DriftDraftRepository implements DraftRepository {
108108 Future <DraftRead > load () => _serialized (() => _database.transaction (_read));
109109
110110 Future <DraftRead > _read () async {
111+ // Bound untrusted SQLite values on the database isolate before sending
112+ // strings to the UI isolate or decoding their JSON. This also keeps a
113+ // corrupt-but-readable database from monopolizing startup's timer.
114+ final QueryRow settingsBound = await _database
115+ .customSelect (
116+ 'SELECT COUNT(*) AS row_count, COALESCE(MAX(LENGTH(CAST(value AS BLOB))), 0) AS max_bytes FROM settings WHERE key = ?' ,
117+ variables: < Variable <Object >> [const Variable <String >(_slotKey)],
118+ )
119+ .getSingle ();
120+ if (settingsBound.read <int >('row_count' ) != 1 ||
121+ settingsBound.read <int >('max_bytes' ) > 4096 ) {
122+ throw const DraftStorageException (DraftStorageFailure .corrupt);
123+ }
111124 final List <Setting > settings = await (_database.select (
112125 _database.settings,
113126 )..where ((Settings t) => t.key.equals (_slotKey))).get ();
@@ -136,11 +149,28 @@ class DriftDraftRepository implements DraftRepository {
136149 }
137150 final QueryRow bound = await _database
138151 .customSelect (
139- 'SELECT COUNT(*) AS row_count, COALESCE(MAX(LENGTH(content)), 0) AS max_bytes FROM project_files' ,
152+ 'SELECT COUNT(*) AS row_count, COALESCE(MAX(LENGTH(CAST(content AS BLOB))), 0) AS max_bytes, '
153+ 'COALESCE(MAX(MAX(LENGTH(CAST(id AS BLOB)), LENGTH(CAST(project_id AS BLOB)), '
154+ 'LENGTH(CAST(path AS BLOB)), LENGTH(CAST(content_hash AS BLOB)))), 0) AS metadata_bytes, '
155+ "COALESCE(MIN(TYPEOF(size) = 'integer' AND TYPEOF(updated_at) = 'integer'), 1) AS valid_numbers FROM project_files" ,
140156 )
141157 .getSingle ();
142158 if (bound.read <int >('row_count' ) > 1 ||
143- bound.read <int >('max_bytes' ) > maxDraftBytes) {
159+ bound.read <int >('max_bytes' ) > maxDraftBytes ||
160+ bound.read <int >('metadata_bytes' ) > 128 ||
161+ bound.read <int >('valid_numbers' ) != 1 ) {
162+ throw const DraftStorageException (DraftStorageFailure .corrupt);
163+ }
164+ final QueryRow projectsBound = await _database
165+ .customSelect (
166+ 'SELECT COUNT(*) AS row_count, COALESCE(MAX(MAX(LENGTH(CAST(id AS BLOB)), '
167+ 'LENGTH(CAST(name AS BLOB)))), 0) AS max_bytes, '
168+ "COALESCE(MIN(TYPEOF(created_at) = 'integer' AND TYPEOF(updated_at) = 'integer'), 1) AS valid_numbers FROM projects" ,
169+ )
170+ .getSingle ();
171+ if (projectsBound.read <int >('row_count' ) > 1 ||
172+ projectsBound.read <int >('max_bytes' ) > 128 ||
173+ projectsBound.read <int >('valid_numbers' ) != 1 ) {
144174 throw const DraftStorageException (DraftStorageFailure .corrupt);
145175 }
146176 final List <ProjectFile > files = await _database
0 commit comments