-
Notifications
You must be signed in to change notification settings - Fork 0
fix: implement actual data collection for backup security manager #333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Changes from all commits
d07a470
41e59c7
29312af
42a1b88
cac6640
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -798,7 +798,7 @@ export class BackupSecurityManager { | |||||||||||||||||
| } | ||||||||||||||||||
| } catch (error: unknown) { | ||||||||||||||||||
| logger.error( | ||||||||||||||||||
| `Error searching for backup metadata in ${location}: ${error instanceof Error ? String(error) : String(error)}`, | ||||||||||||||||||
| `Error searching for backup metadata in ${location}: ${error instanceof Error ? error.message : String(error)}`, | ||||||||||||||||||
| ) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -809,16 +809,95 @@ export class BackupSecurityManager { | |||||||||||||||||
| /** | ||||||||||||||||||
| * Get data to backup based on backup type | ||||||||||||||||||
| */ | ||||||||||||||||||
| private async getLastBackupTime(requireFull = false): Promise<Date | null> { | ||||||||||||||||||
| let latestTimestamp = 0; | ||||||||||||||||||
|
|
||||||||||||||||||
| const storageEntries = Array.from(this.storageProviders.entries()); | ||||||||||||||||||
| for (let i = 0; i < storageEntries.length; i++) { | ||||||||||||||||||
| const [, provider] = storageEntries[i]; | ||||||||||||||||||
| if (!provider) continue; | ||||||||||||||||||
|
|
||||||||||||||||||
| try { | ||||||||||||||||||
| const files = await provider.listFiles("backups/"); | ||||||||||||||||||
| const metaFiles = files.filter((f) => f.endsWith(".meta.json")); | ||||||||||||||||||
| for (const metaFile of metaFiles) { | ||||||||||||||||||
| try { | ||||||||||||||||||
| const metadataBuffer = await provider.getFile(metaFile); | ||||||||||||||||||
| const metadata = JSON.parse(new TextDecoder().decode(metadataBuffer)) as BackupMetadata; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (metadata.status === BackupStatus.COMPLETED) { | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Incremental/differential baseline selection only considers COMPLETED backups, but created backups remain PENDING in normal flow, causing repeated fallback windows and potential data omission. Prompt for AI agents |
||||||||||||||||||
| if (requireFull && metadata.type !== BackupType.FULL) continue; | ||||||||||||||||||
| if (!requireFull && metadata.type !== BackupType.FULL && metadata.type !== BackupType.DIFFERENTIAL && metadata.type !== BackupType.INCREMENTAL) continue; | ||||||||||||||||||
|
|
||||||||||||||||||
| const timestamp = new Date(metadata.timestamp).getTime(); | ||||||||||||||||||
| if (timestamp > latestTimestamp) { | ||||||||||||||||||
| latestTimestamp = timestamp; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } catch { | ||||||||||||||||||
| // Ignore individual file parsing errors | ||||||||||||||||||
|
Comment on lines
+837
to
+838
|
||||||||||||||||||
| } catch { | |
| // Ignore individual file parsing errors | |
| } catch (error: unknown) { | |
| logger.warn( | |
| `Failed to parse backup metadata file "${metaFile}": ${ | |
| error instanceof Error ? error.message : String(error) | |
| }`, | |
| ); |
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building a large JSON payload via repeated string += ... inside nested loops can become very costly (frequent reallocations/copies) as the backup grows. Prefer accumulating into an array of string chunks and join('') once, or write into a dedicated buffer/stream abstraction; it keeps the same sequential read pattern without the quadratic-ish string concatenation overhead.
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does two dynamic imports and can yield inconsistent types: if .default is missing, mongoose becomes the module namespace object, so mongoose.modelNames() may not exist. Import once, then resolve default vs namespace from that single result (e.g., store the import result in a variable and use mod.default ?? mod). This also avoids the extra module load.
| const mongoose = (await import(/* @vite-ignore */ mongooseModule)).default || await import(/* @vite-ignore */ mongooseModule); | |
| const mongooseImport = await import(/* @vite-ignore */ mongooseModule); | |
| const mongoose = (mongooseImport as any).default ?? mongooseImport; |
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 24 * 60 * 60 * 1000 fallback is a magic number and the rationale isn’t encoded in the code. Extract this to a named constant (e.g., DEFAULT_BASELINE_LOOKBACK_MS) and add a short comment explaining why 24h is the chosen fallback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Differential/incremental backups fall back to baselineTime = now - 24h when no prior baseline is found. That can produce invalid backup chains (missing older documents), making restores incomplete. For DIFFERENTIAL, if there’s no prior FULL, the correct behavior is usually to fail or force a FULL backup.
Suggestion
Treat “no baseline found” as a hard error (or automatically behave as FULL) to avoid silently producing incomplete backups.
Example:
const lastBackupTime = await this.getLastBackupTime(requireFull)
if (!lastBackupTime) {
if (type === BackupType.DIFFERENTIAL) {
throw new Error('No FULL backup found; cannot create DIFFERENTIAL backup')
}
// INCREMENTAL: either throw or treat as FULL
query = {}
} else {
query = { updatedAt: { $gte: lastBackupTime } }
}Reply with "@CharlieHelps yes please" if you want me to add a commit implementing this baseline behavior.
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building a large JSON payload via repeated string += ... inside nested loops can become very costly (frequent reallocations/copies) as the backup grows. Prefer accumulating into an array of string chunks and join('') once, or write into a dedicated buffer/stream abstraction; it keeps the same sequential read pattern without the quadratic-ish string concatenation overhead.
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building a large JSON payload via repeated string += ... inside nested loops can become very costly (frequent reallocations/copies) as the backup grows. Prefer accumulating into an array of string chunks and join('') once, or write into a dedicated buffer/stream abstraction; it keeps the same sequential read pattern without the quadratic-ish string concatenation overhead.
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building a large JSON payload via repeated string += ... inside nested loops can become very costly (frequent reallocations/copies) as the backup grows. Prefer accumulating into an array of string chunks and join('') once, or write into a dedicated buffer/stream abstraction; it keeps the same sequential read pattern without the quadratic-ish string concatenation overhead.
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building a large JSON payload via repeated string += ... inside nested loops can become very costly (frequent reallocations/copies) as the backup grows. Prefer accumulating into an array of string chunks and join('') once, or write into a dedicated buffer/stream abstraction; it keeps the same sequential read pattern without the quadratic-ish string concatenation overhead.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2:
getLastBackupTimeuseslistFiles("backups/"), but storage providers treat the argument as a full glob pattern (not a recursive prefix). For providers likeLocalFileSystemProvider, the pattern is anchored, sobackups/never matches nested metadata files and the last-backup scan always returns null.Prompt for AI agents