fix: block inconclusive skillhub installs

This commit is contained in:
程序员阿江(Relakkes) 2026-07-03 17:16:24 +08:00
parent c744213aae
commit 3b1f190938
2 changed files with 52 additions and 3 deletions

View File

@ -184,9 +184,40 @@ describe('skill market source normalization', () => {
slug: 'skill-vetter',
trustState: 'benign',
trustSummary: '安全,无风险',
installEligibility: { status: 'installable' },
})
})
it('blocks SkillHub details when security reports are missing', () => {
const detail = normalizeSkillHubDetail({
skill: {
slug: 'unreviewed-skill',
displayName: 'Unreviewed Skill',
},
})
expect(detail.trustState).toBe('unknown')
expect(detail.installEligibility.status).toBe('blocked')
expect(detail.installEligibility.reason).toMatch(/security report is missing or inconclusive/i)
})
it('blocks SkillHub details when security reports are mixed or inconclusive', () => {
const detail = normalizeSkillHubDetail({
securityReports: {
community: { status: 'benign', statusText: 'safe' },
staticAnalysis: { status: 'pending-review', statusText: 'Scanner still reviewing.' },
},
skill: {
slug: 'mixed-report-skill',
displayName: 'Mixed Report Skill',
},
})
expect(detail.trustState).toBe('unknown')
expect(detail.installEligibility.status).toBe('blocked')
expect(detail.installEligibility.reason).toMatch(/security report is missing or inconclusive/i)
})
it('uses malicious SkillHub report summaries for blocked details', () => {
const detail = normalizeSkillHubDetail({
securityReports: {

View File

@ -45,6 +45,13 @@ const ALLOWED_EXTERNAL_URL_HOSTS = new Set([
'raw.githubusercontent.com',
])
const SKILLHUB_DETAIL_INSTALLABLE_TRUST_STATES = new Set<SkillMarketTrustState>([
'clean',
'benign',
'signed',
'official',
])
function requiresApiKey(labels?: { requires_api_key?: string }) {
return labels?.requires_api_key === 'true'
}
@ -93,6 +100,19 @@ function trustFromReports(reports?: Record<string, { status?: string; statusText
return { trustState: 'unknown', trustSummary: values.find((report) => report.statusText)?.statusText }
}
function installEligibilityFromTrustState(trustState: SkillMarketTrustState): SkillMarketDetail['installEligibility'] {
if (SKILLHUB_DETAIL_INSTALLABLE_TRUST_STATES.has(trustState)) {
return { status: 'installable' }
}
if (trustState === 'blocked') {
return { status: 'blocked', reason: 'SkillHub security report blocked this skill.' }
}
if (trustState === 'warning') {
return { status: 'blocked', reason: 'SkillHub security report returned warnings.' }
}
return { status: 'blocked', reason: 'SkillHub security report is missing or inconclusive.' }
}
export function normalizeSkillHubList(payload: SkillHubListResponse): SkillMarketListResult {
const items = (payload.data?.skills ?? [])
.filter((item) => item.slug && item.name)
@ -155,8 +175,6 @@ export function normalizeSkillHubDetail(payload: SkillHubDetailResponse): SkillM
installed: false,
files: [],
riskLabels: requiresApiKey(skill.labels) ? ['requires-api-key'] : [],
installEligibility: trust.trustState === 'blocked'
? { status: 'blocked', reason: 'SkillHub security report blocked this skill.' }
: { status: 'installable' },
installEligibility: installEligibilityFromTrustState(trust.trustState),
}
}