Match legacy installed image digests to verified native media policy

This commit is contained in:
archipelago
2026-10-07 01:46:58 -04:00
parent c2c4d9151c
commit fdc596854e
2 changed files with 41 additions and 2 deletions
@@ -27,6 +27,31 @@ describe('node-scoped demo catalog', () => {
expect(await ensureNodeAppAvailable('node-demo-v4v')).toBe(true) expect(await ensureNodeAppAvailable('node-demo-v4v')).toBe(true)
expect(fetcher).toHaveBeenCalledTimes(1) expect(fetcher).toHaveBeenCalledTimes(1)
}) })
it('admits the exact digest-version reported by the installed private demo', async () => {
vi.resetModules()
const digest = '4f28702e4f85368e4ad886f06d58b38f869c560c90ca40487bfaebe69090a870'
const image = `localhost/node-demo:0.6.7-alpha-private@sha256:${digest}`
const payload = { ...demo(), apps: { 'node-demo-v4v': {
version: '0.6.7-alpha-private', digest: `sha256:${digest}`, image,
manifest: { app: { id: 'node-demo-v4v', version: '0.6.7-alpha-private',
container: { image }, metadata: { launch: { media_controls: 'archipelago-v1', requires_host_frame: true } } } },
} } }
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, json: async () => payload })))
const { ensureNodeAppAvailable, appHasMediaBridge, appRequiresHostFrame } = await import('../curatedApps')
expect(await ensureNodeAppAvailable('node-demo-v4v')).toBe(true)
expect(appHasMediaBridge('node-demo-v4v', digest)).toBe(true)
expect(appRequiresHostFrame('node-demo-v4v', digest)).toBe(true)
expect(appHasMediaBridge('node-demo-v4v', 'a'.repeat(64))).toBe(false)
expect(appHasMediaBridge('node-demo-v4v', digest.slice(0, 12))).toBe(false)
payload.apps['node-demo-v4v'].digest = `sha256:${'b'.repeat(64)}`
expect(appHasMediaBridge('node-demo-v4v', digest)).toBe(false)
payload.apps['node-demo-v4v'].digest = `sha256:${digest}`
payload.apps['node-demo-v4v'].manifest.app.container.image = `${image.slice(0, image.indexOf('@'))}@sha256:${'c'.repeat(64)}`
expect(appHasMediaBridge('node-demo-v4v', digest)).toBe(false)
payload.apps['node-demo-v4v'].manifest.app.container.image = image
payload.apps['node-demo-v4v'].image = 'localhost/node-demo:mutable'
expect(appHasMediaBridge('node-demo-v4v', digest)).toBe(false)
})
it('fails closed for unavailable and expired launch policy', async () => { it('fails closed for unavailable and expired launch policy', async () => {
vi.resetModules() vi.resetModules()
const fetcher = vi.fn(async () => ({ ok: true, json: async () => ({ ...demo(), expires_at: '2000-01-01T00:00:00Z' }) })) const fetcher = vi.fn(async () => ({ ok: true, json: async () => ({ ...demo(), expires_at: '2000-01-01T00:00:00Z' }) }))
+16 -2
View File
@@ -59,6 +59,7 @@ export interface SignedAppCatalog {
export interface SignedAppEntry { export interface SignedAppEntry {
version: string version: string
digest?: string
image?: string image?: string
manifest?: { manifest?: {
app?: { app?: {
@@ -117,15 +118,28 @@ function verifiedLaunchEntry(id: string): SignedAppEntry | undefined {
: signedCatalogCache?.apps[id] : signedCatalogCache?.apps[id]
return entry?.manifest?.app?.id === id ? entry : undefined return entry?.manifest?.app?.id === id ? entry : undefined
} }
/** Older package scanners expose the inspected image digest as manifest.version.
* Accept that identity only when it matches the verified immutable image pin;
* a tag, abbreviated digest or conflicting catalog field cannot substitute. */
function installedEntryMatches(entry: SignedAppEntry, installedVersion?: string): boolean {
if (installedVersion === undefined || entry.version === installedVersion) return true
if (!/^[a-f0-9]{64}$/i.test(installedVersion)) return false
const image = entry.image ?? entry.manifest?.app?.container?.image
const pin = image?.match(/@sha256:([a-f0-9]{64})$/i)?.[1]?.toLowerCase()
if (!pin || pin !== installedVersion.toLowerCase()) return false
if (entry.digest !== undefined && entry.digest.toLowerCase() !== `sha256:${pin}`) return false
const manifestImage = entry.manifest?.app?.container?.image
return manifestImage === undefined || manifestImage.match(/@sha256:([a-f0-9]{64})$/i)?.[1]?.toLowerCase() === pin
}
export function appHasMediaBridge(id: string, installedVersion?: string): boolean { export function appHasMediaBridge(id: string, installedVersion?: string): boolean {
const entry = verifiedLaunchEntry(id) const entry = verifiedLaunchEntry(id)
if (!entry || (installedVersion !== undefined && entry.version !== installedVersion)) return false if (!entry || !installedEntryMatches(entry, installedVersion)) return false
const launch = entry.manifest?.app?.metadata?.launch const launch = entry.manifest?.app?.metadata?.launch
return launch?.media_controls === 'archipelago-v1' && launch.open_in_new_tab !== true return launch?.media_controls === 'archipelago-v1' && launch.open_in_new_tab !== true
} }
export function appRequiresHostFrame(id: string, installedVersion?: string): boolean { export function appRequiresHostFrame(id: string, installedVersion?: string): boolean {
const entry = verifiedLaunchEntry(id) const entry = verifiedLaunchEntry(id)
return Boolean(entry && (installedVersion === undefined || entry.version === installedVersion) return Boolean(entry && installedEntryMatches(entry, installedVersion)
&& entry.manifest?.app?.metadata?.launch?.requires_host_frame === true && entry.manifest?.app?.metadata?.launch?.requires_host_frame === true
&& entry.manifest.app.metadata.launch.open_in_new_tab !== true) && entry.manifest.app.metadata.launch.open_in_new_tab !== true)
} }