🛡️ Sentinel: [HIGH] Fix timingSafeEqual DoS Vulnerability (#423)
* Fix timingSafeEqual DoS Vulnerability in Webhook Co-authored-by: ralksta <16941050+ralksta@users.noreply.github.com> * Fix memory exhaustion DoS in token decoding Co-authored-by: ralksta <16941050+ralksta@users.noreply.github.com> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
@@ -104,6 +104,77 @@ describe('deriveGallery accepts valid structures', () => {
|
||||
expect(result.albumHeroImages[A]).toBe(B);
|
||||
});
|
||||
|
||||
it('collects the per-album sort mode', () => {
|
||||
const result = deriveGallery({
|
||||
albums: [{ [A]: { title: 'Series', sort: 'filename' } }, B],
|
||||
} as unknown as GalleryYaml);
|
||||
|
||||
expect(result.albumSortModes[A]).toBe('filename');
|
||||
// Absent means the default; an explicit entry would make `immich` and
|
||||
// "never configured" indistinguishable downstream.
|
||||
expect(result.albumSortModes[B]).toBeUndefined();
|
||||
});
|
||||
|
||||
// A typo would otherwise be invisible: the album would keep rendering in
|
||||
// Immich order and nothing would say why. Throwing is also what gives the
|
||||
// admin PUT its 400, via the deriveGallery dry-run.
|
||||
it('rejects an unknown sort mode, naming the album and the valid values', () => {
|
||||
expect(() =>
|
||||
deriveGallery({ albums: [{ [A]: { sort: 'alphabetical' } }] } as unknown as GalleryYaml),
|
||||
).toThrow(/alphabetical/);
|
||||
|
||||
expect(() =>
|
||||
deriveGallery({ albums: [{ [A]: { sort: 'alphabetical' } }] } as unknown as GalleryYaml),
|
||||
).toThrow(new RegExp(A));
|
||||
|
||||
expect(() =>
|
||||
deriveGallery({ albums: [{ [A]: { sort: 'alphabetical' } }] } as unknown as GalleryYaml),
|
||||
).toThrow(/filename/);
|
||||
});
|
||||
|
||||
it('collects the manual asset order, preserving its order', () => {
|
||||
const result = deriveGallery({
|
||||
albums: [{ [A]: { sort: 'manual', assetOrder: [B, A] } }],
|
||||
} as unknown as GalleryYaml);
|
||||
|
||||
expect(result.albumManualOrders[A]).toEqual([B, A]);
|
||||
});
|
||||
|
||||
// Kept regardless of the mode, so toggling manual → newest → manual in the
|
||||
// admin panel does not silently destroy a hand-curated order.
|
||||
it('keeps the asset order even when the mode is not manual', () => {
|
||||
const result = deriveGallery({
|
||||
albums: [{ [A]: { sort: 'newest', assetOrder: [B] } }],
|
||||
} as unknown as GalleryYaml);
|
||||
|
||||
expect(result.albumSortModes[A]).toBe('newest');
|
||||
expect(result.albumManualOrders[A]).toEqual([B]);
|
||||
});
|
||||
|
||||
it('ignores an empty asset order rather than storing one', () => {
|
||||
const result = deriveGallery({
|
||||
albums: [{ [A]: { sort: 'manual', assetOrder: [] } }],
|
||||
} as unknown as GalleryYaml);
|
||||
|
||||
expect(result.albumManualOrders[A]).toBeUndefined();
|
||||
});
|
||||
|
||||
// processAlbumEntry is reached from four places; only the array-subpage path
|
||||
// was covered before, so a regression in the others would go unnoticed.
|
||||
it('applies the sort mode to section and map-style subpage albums too', () => {
|
||||
const sectioned = deriveGallery({
|
||||
subpages: [
|
||||
{ name: 'Work', sections: [{ title: 'Recent', albums: [{ [A]: { sort: 'oldest' } }] }] },
|
||||
],
|
||||
} as unknown as GalleryYaml);
|
||||
expect(sectioned.albumSortModes[A]).toBe('oldest');
|
||||
|
||||
const mapStyle = deriveGallery({
|
||||
subpages: { Trips: { albums: [{ [B]: { sort: 'filename' } }] } },
|
||||
} as unknown as GalleryYaml);
|
||||
expect(mapStyle.albumSortModes[B]).toBe('filename');
|
||||
});
|
||||
|
||||
it('parses enabled property on subpages correctly', () => {
|
||||
const result = deriveGallery({
|
||||
subpages: [
|
||||
|
||||
Reference in New Issue
Block a user