Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/rules/alt-text-quality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {createJudge} from '../judges/index.js'
import type {JudgeAltText, JudgeVerdict} from '../judges/index.js'
import type {Rule, RuleContext, RuleResult, ImageRecord} from '../types.js'
import {loadImageAsDataUrl} from '../utils/load-image-data-url.js'
import {redactUrl} from '../utils/redact-url.js'

// Lazily build the judge so missing tokens surface only when the rule actually
// runs
Expand Down Expand Up @@ -47,7 +48,7 @@ function buildContextString(image: ImageRecord, pageUrl: string): string {
if (image.pageTitle) parts.push(`Page title: ${JSON.stringify(image.pageTitle)}`)
if (image.sectionHeading) parts.push(`Nearest heading above the image: ${JSON.stringify(image.sectionHeading)}`)
parts.push(`Image HTML: ${sanitizeImageHtml(image.outerHTML)}`)
if (image.inLink) parts.push(`The image is inside a link with href="${image.inLink.href}".`)
if (image.inLink) parts.push(`The image is inside a link with href="${redactUrl(image.inLink.href)}".`)
if (image.inButton) parts.push('The image is inside a button (or role="button" element).')
Comment on lines 49 to 52
if (image.figcaption) parts.push(`Adjacent figcaption: ${JSON.stringify(image.figcaption)}`)
if (image.nearbyText) parts.push(`Surrounding body text: ${JSON.stringify(image.nearbyText)}`)
Expand Down Expand Up @@ -102,7 +103,8 @@ export const altTextQuality: Rule = {
try {
dataUrl = await loadImageAsDataUrl(resolved)
} catch (err) {
console.error(`[alt-text-quality] failed to load ${resolved}:`, err)
const msg = (err instanceof Error ? err.message : String(err)).replace(/([?#])[^\s]*/g, '$1…')
console.error(`[alt-text-quality] failed to load ${redactUrl(resolved)}: ${redactUrl(msg)}`)
continue
}
Comment on lines 105 to 109

Expand All @@ -116,7 +118,8 @@ export const altTextQuality: Rule = {
naturalHeight: image.naturalHeight,
})
} catch (err) {
console.error(`[alt-text-quality] judge failed for ${resolved}:`, err)
const msg = (err instanceof Error ? err.message : String(err)).replace(/([?#])[^\s]*/g, '$1…')
console.error(`[alt-text-quality] judge failed for ${redactUrl(resolved)}: ${redactUrl(msg)}`)
continue
}
Comment on lines 120 to 124

Expand Down
15 changes: 15 additions & 0 deletions src/utils/redact-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Strips the query string and fragment from a URL so potentially sensitive
// values (signed-CDN tokens, session ids, user identifiers) are not forwarded
// to the model context or written to CI logs. Origin + path are preserved for
// debuggability. Inputs that don't parse as URLs are returned with anything
// after '?' or '#' dropped as a fallback.
export function redactUrl(url: string): string {
try {
const u = new URL(url)
u.search = ''
u.hash = ''
return u.toString()
} catch {
return url.split(/[?#]/)[0] ?? url
}
}
16 changes: 16 additions & 0 deletions tests/unit/alt-text-quality.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,20 @@ describe('alt-text-quality', () => {
expect(context).toContain('src="(omitted)"')
expect(context).toContain('srcset="(omitted)"')
})

it('redacts query/fragment from the link href in the judge context', async () => {
const fake = new FakeJudge(() => verdict())
__setJudge(fake)
await run([
makeImage({
src: DATA_URL,
alt: 'a dog',
inLink: {href: 'https://example.com/page?sig=secret123#frag'},
}),
])
const context = fake.calls[0]!.context
expect(context).not.toContain('sig=secret123')
expect(context).not.toContain('#frag')
expect(context).toContain('https://example.com/page')
})
})
17 changes: 17 additions & 0 deletions tests/unit/redact-url.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {describe, it, expect} from 'vitest'
import {redactUrl} from '../../src/utils/redact-url.js'

describe('redactUrl', () => {
it('strips the query string', () => {
expect(redactUrl('https://cdn.example.com/img.png?sig=secret123')).toBe('https://cdn.example.com/img.png')
})
it('strips the fragment', () => {
expect(redactUrl('https://example.com/page#section')).toBe('https://example.com/page')
})
it('leaves a clean URL unchanged', () => {
expect(redactUrl('https://example.com/img.png')).toBe('https://example.com/img.png')
})
it('falls back gracefully on non-URL input', () => {
expect(redactUrl('not a url?x=1')).toBe('not a url')
})
})