-
Notifications
You must be signed in to change notification settings - Fork 0
fix(security): derive CSP origins on release-backed content #3487
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -130,7 +130,11 @@ export class VeryfrontFSAdapter implements FSAdapter { | |
| /** Resolves when file list initialization is complete (for coordinating reads) */ | ||
| private fileListReadyResolve: (() => void) | null = null; | ||
| /** Single-flight background rewarm when the file list cache disappears */ | ||
| private fileListWarmupPromise: Promise<void> | null = null; | ||
| // Resolves with the files it fetched, so a caller that waited does not have | ||
| // to depend on the cache write having succeeded -- writes are skipped | ||
| // entirely when caching is disabled, and can fail on a backend cache. | ||
| private fileListWarmupPromise: Promise<Array<{ path: string; content?: string }> | null> | null = | ||
| null; | ||
| private fileListWarmupKey: string | null = null; | ||
| /** Single-flight foreground refresh when a branch preview read misses a newly pushed file. */ | ||
| private branchMissRecoveryPromise: Promise<void> | null = null; | ||
|
|
@@ -663,7 +667,7 @@ export class VeryfrontFSAdapter implements FSAdapter { | |
| } | ||
|
|
||
| const warmupContext = this.contentContext; | ||
| let warmupPromise: Promise<void> | null = null; | ||
| let warmupPromise: Promise<Array<{ path: string; content?: string }> | null> | null = null; | ||
| warmupPromise = (async () => { | ||
| try { | ||
| const existing = await this.cache.getAsync<Array<{ path: string; content?: string }>>( | ||
|
|
@@ -676,7 +680,7 @@ export class VeryfrontFSAdapter implements FSAdapter { | |
| cacheKey: effectiveCacheKey, | ||
| fileCount: existing.length, | ||
| }); | ||
| return; | ||
| return existing; | ||
| } | ||
|
|
||
| logger.debug("Starting file list warmup", { | ||
|
|
@@ -704,12 +708,16 @@ export class VeryfrontFSAdapter implements FSAdapter { | |
| totalFiles: files.length, | ||
| filesWithContent: files.filter((file) => file.content).length, | ||
| }); | ||
|
|
||
| return files; | ||
| } catch (error) { | ||
| logger.warn("File list warmup failed", { | ||
| reason, | ||
| cacheKey: effectiveCacheKey, | ||
| error: error instanceof Error ? error.message : String(error), | ||
| }); | ||
|
|
||
| return null; | ||
| } finally { | ||
| if (warmupPromise && this.fileListWarmupPromise === warmupPromise) { | ||
| this.fileListWarmupPromise = null; | ||
|
|
@@ -720,7 +728,8 @@ export class VeryfrontFSAdapter implements FSAdapter { | |
|
|
||
| this.fileListWarmupPromise = warmupPromise; | ||
| this.fileListWarmupKey = effectiveCacheKey; | ||
| this.readOps.setFileListReadyPromise(warmupPromise); | ||
| // That collaborator only needs completion, not the payload. | ||
| this.readOps.setFileListReadyPromise(warmupPromise.then(() => {})); | ||
| } | ||
|
|
||
| private markSourceSnapshotChanged( | ||
|
|
@@ -1040,7 +1049,16 @@ export class VeryfrontFSAdapter implements FSAdapter { | |
| return this.projectData; | ||
| } | ||
|
|
||
| async getAllSourceFiles(): Promise<Array<{ path: string; content?: string }>> { | ||
| /** | ||
| * @param options.waitForWarmup wait for an in-flight file-list fetch instead | ||
| * of answering empty. Off by default: most callers can proceed without the | ||
| * list and must not pay for the fetch, but a caller that has no other way to | ||
| * obtain it -- CSP derivation on a release-backed context, where nothing else | ||
| * populates the cache -- would otherwise read empty on every request forever. | ||
| */ | ||
| async getAllSourceFiles( | ||
| options: { waitForWarmup?: boolean } = {}, | ||
| ): Promise<Array<{ path: string; content?: string }>> { | ||
| if (!this.contentContext) { | ||
| logger.debug("getAllSourceFiles called without contentContext", { | ||
| initialized: this.initialized, | ||
|
|
@@ -1055,7 +1073,22 @@ export class VeryfrontFSAdapter implements FSAdapter { | |
| "getAllSourceFiles miss", | ||
| ); | ||
| const cacheKey = cached?.cacheKey; | ||
| const files = cached?.files; | ||
| let files = cached?.files; | ||
|
|
||
| // A miss schedules a warmup and returns immediately, which is right for | ||
| // callers that can proceed without the list. This one cannot: nothing else | ||
| // populates it for a release-backed context, so returning early meant the | ||
| // list was empty on every request for the life of the process. Wait for the | ||
| // fetch this read just started, then look again. | ||
| if (options.waitForWarmup && cacheKey && !files?.length && this.fileListWarmupPromise) { | ||
| // Take what the fetch returned rather than re-reading the cache: with | ||
| // caching disabled, or a failed backend write, the cache keeps nothing | ||
| // and correctness would depend on a write that never happened. | ||
| const fetched = await this.fileListWarmupPromise; | ||
| files = fetched?.length | ||
| ? fetched | ||
| : await this.cache.getAsync<{ path: string; content?: string }[]>(cacheKey); | ||
| } | ||
|
Comment on lines
+1076
to
+1091
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Bind the waited warmup to
Store warmups by cache key, or return a key-tagged warmup handle from scheduling and consume it only when its key matches 🤖 Prompt for AI Agents |
||
|
|
||
| if (!cacheKey || !files?.length) { | ||
| logger.debug("getAllSourceFiles cache miss or empty", { | ||
|
|
||
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve fetched files when the cache write fails.
If
this.cache.setAsyncrejects on Line 696, this catch returnsnullafter a successful fetch.getAllSourceFilesthen cannot use the fetched files and can only retry the failed cache read. CSP derivation remains empty in the cache-write-failure case that this change must support.Catch and log cache-write failures separately. Continue with
files. Add a focused test with a rejecting cache write.🤖 Prompt for AI Agents