Skip to content

Commit 53b7a24

Browse files
authored
refactor: resolve codebase audit issues (#872)
* fix: non-unique sheet id, singleton init access, dead observer code * fix: validate raw SQL filter input to prevent injection * refactor: extract shared plugin validation to eliminate duplicate loading code * refactor: remove sessionVersion backward-compatibility shim * refactor: migrate QueryHistoryStorage from DispatchQueue to actor * fix: tighten comment injection pattern, eliminate double bundle creation
1 parent 70d36f9 commit 53b7a24

12 files changed

Lines changed: 270 additions & 434 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Fixed
1515

16+
- Raw SQL filter accepting destructive statements and comment injection
1617
- Export "Don't show again" preference lost when clicking "Open Folder"
1718
- Connection failure error not shown on welcome screen
1819
- Window position not restored between launches

TablePro/Core/Database/DatabaseManager.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ final class DatabaseManager {
3838
/// counter to avoid cross-connection re-renders.
3939
internal(set) var connectionStatusVersions: [UUID: Int] = [:]
4040

41-
/// Backward-compatible alias for views not yet migrated to fine-grained counters.
42-
var sessionVersion: Int { connectionStatusVersion }
43-
4441
/// Currently selected session ID (displayed in UI)
4542
internal var currentSessionId: UUID?
4643

TablePro/Core/Database/FilterSQLGenerator.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,11 @@ struct FilterSQLGenerator {
3838
return conditions.joined(separator: separator)
3939
}
4040

41-
/// Generate a single filter condition
4241
func generateCondition(from filter: TableFilter) -> String? {
4342
guard filter.isValid else { return nil }
4443

45-
// Raw SQL mode - return as-is
4644
if filter.isRawSQL, let rawSQL = filter.rawSQL {
45+
guard isRawSQLSafe(rawSQL) else { return nil }
4746
return "(\(rawSQL))"
4847
}
4948

@@ -273,9 +272,36 @@ struct FilterSQLGenerator {
273272
.replacingOccurrences(of: "_", with: "\\_")
274273
}
275274

275+
// MARK: - Raw SQL Validation
276+
277+
private static let destructiveStatementPattern: NSRegularExpression? = {
278+
let keywords = "DROP|DELETE|INSERT|UPDATE|ALTER|CREATE|TRUNCATE|GRANT|REVOKE|EXEC|EXECUTE"
279+
let pattern = ";\\s*(\(keywords))\\b"
280+
return try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
281+
}()
282+
283+
private static let commentInjectionPattern: NSRegularExpression? = {
284+
try? NSRegularExpression(pattern: "(?:^|\\s)--|\\/\\*", options: [])
285+
}()
286+
287+
private func isRawSQLSafe(_ sql: String) -> Bool {
288+
let range = NSRange(sql.startIndex..., in: sql)
289+
290+
if let pattern = Self.destructiveStatementPattern,
291+
pattern.firstMatch(in: sql, range: range) != nil {
292+
return false
293+
}
294+
295+
if let pattern = Self.commentInjectionPattern,
296+
pattern.firstMatch(in: sql, range: range) != nil {
297+
return false
298+
}
299+
300+
return true
301+
}
302+
276303
// MARK: - List Parsing
277304

278-
/// Parse comma-separated list values
279305
private func parseListValues(_ input: String) -> [String] {
280306
input.split(separator: ",", omittingEmptySubsequences: true)
281307
.compactMap {

0 commit comments

Comments
 (0)