Skip to content

Commit 67ac157

Browse files
committed
fix: keep new skill versions pending until VT verdict
1 parent ef36cfd commit 67ac157

File tree

4 files changed

+21
-54
lines changed

4 files changed

+21
-54
lines changed

convex/llmEval.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -251,23 +251,8 @@ export const evaluateWithLlm = internalAction({
251251
`[llmEval] Evaluated ${skill.slug}@${version.version}: ${result.verdict} (${result.confidence} confidence)`,
252252
)
253253

254-
// 10. Update moderation flags — re-read version to get the sha256hash
255-
// that VT may have stored while we were evaluating (both run concurrently).
256-
const freshVersion = (await ctx.runQuery(internal.skills.getVersionByIdInternal, {
257-
versionId: args.versionId,
258-
})) as Doc<'skillVersions'> | null
259-
260-
const sha256hash = freshVersion?.sha256hash ?? version.sha256hash
261-
if (sha256hash) {
262-
const status = verdictToStatus(result.verdict)
263-
if (status === 'malicious' || status === 'suspicious' || status === 'clean') {
264-
await ctx.runMutation(internal.skills.approveSkillByHashInternal, {
265-
sha256hash,
266-
scanner: 'llm',
267-
status,
268-
})
269-
}
270-
}
254+
// Moderation visibility is finalized by VT results.
255+
// LLM eval only stores analysis payload on the version.
271256
},
272257
})
273258

convex/skills.rateLimit.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('skills anti-spam guards', () => {
9393
).rejects.toThrow(/max 5 new skills per hour/i)
9494
})
9595

96-
it('auto-hides suspicious skills from low-trust publishers', async () => {
96+
it('keeps suspicious skills visible for low-trust publishers', async () => {
9797
const patch = vi.fn(async () => {})
9898
const version = { _id: 'skillVersions:1', skillId: 'skills:1' }
9999
const skill = {
@@ -147,16 +147,17 @@ describe('skills anti-spam guards', () => {
147147
{ db, scheduler: { runAfter: vi.fn() } } as never,
148148
{
149149
sha256hash: 'h'.repeat(64),
150-
scanner: 'llm',
150+
scanner: 'vt',
151151
status: 'suspicious',
152152
} as never,
153153
)
154154

155155
expect(patch).toHaveBeenCalledWith(
156156
'skills:1',
157157
expect.objectContaining({
158-
moderationStatus: 'hidden',
159-
moderationReason: 'scanner.llm.suspicious',
158+
moderationStatus: 'active',
159+
moderationReason: 'scanner.vt.suspicious',
160+
moderationFlags: ['flagged.suspicious'],
160161
}),
161162
)
162163
})

convex/skills.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2332,20 +2332,8 @@ export const approveSkillByHashInternal = internalMutation({
23322332
}
23332333

23342334
const now = Date.now()
2335-
let shouldHideSuspicious = false
2336-
if (isSuspicious && !alreadyBlocked && !bypassSuspicious) {
2337-
if (owner && !owner.deletedAt && !owner.deactivatedAt) {
2338-
const trustSignals = await getOwnerTrustSignals(ctx, owner, now)
2339-
shouldHideSuspicious = trustSignals.isLowTrust
2340-
}
2341-
}
2342-
23432335
const qualityLocked = skill.moderationReason === 'quality.low' && !isMalicious
2344-
const nextModerationStatus = qualityLocked
2345-
? 'hidden'
2346-
: shouldHideSuspicious
2347-
? 'hidden'
2348-
: 'active'
2336+
const nextModerationStatus = qualityLocked ? 'hidden' : 'active'
23492337
const nextModerationReason = qualityLocked
23502338
? 'quality.low'
23512339
: bypassSuspicious
@@ -2354,9 +2342,7 @@ export const approveSkillByHashInternal = internalMutation({
23542342
const nextModerationNotes = qualityLocked
23552343
? (skill.moderationNotes ??
23562344
'Quality gate quarantine is still active. Manual moderation review required.')
2357-
: shouldHideSuspicious
2358-
? 'Auto-hidden: suspicious result from low-trust publisher.'
2359-
: undefined
2345+
: undefined
23602346

23612347
await ctx.db.patch(skill._id, {
23622348
moderationStatus: nextModerationStatus,

convex/vt.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,6 @@ export const scanWithVirusTotal = internalAction({
375375
// File exists and has AI analysis - use the verdict
376376
const verdict = normalizeVerdict(aiResult.verdict)
377377
const status = verdictToStatus(verdict)
378-
const isSafe = status === 'clean'
379-
380378
console.log(
381379
`Version ${args.versionId} found in VT with AI analysis. Hash: ${sha256hash}. Verdict: ${verdict}`,
382380
)
@@ -393,14 +391,12 @@ export const scanWithVirusTotal = internalAction({
393391
},
394392
})
395393

396-
// VT is supplementary — only escalate (never override LLM verdict)
397-
if (!isSafe && (status === 'malicious' || status === 'suspicious')) {
398-
await ctx.runMutation(internal.skills.escalateByVtInternal, {
399-
sha256hash,
400-
status,
401-
})
402-
}
403-
// Clean VT result: vtAnalysis already written above — don't touch moderation
394+
// VT finalizes moderation visibility for newly published versions.
395+
await ctx.runMutation(internal.skills.approveSkillByHashInternal, {
396+
sha256hash,
397+
scanner: 'vt',
398+
status,
399+
})
404400
return
405401
}
406402

@@ -578,13 +574,12 @@ export const pollPendingScans = internalAction({
578574
},
579575
})
580576

581-
// VT is supplementary — only escalate for malicious/suspicious
582-
if (status === 'malicious' || status === 'suspicious') {
583-
await ctx.runMutation(internal.skills.escalateByVtInternal, {
584-
sha256hash,
585-
status,
586-
})
587-
}
577+
// VT finalizes moderation visibility for newly published versions.
578+
await ctx.runMutation(internal.skills.approveSkillByHashInternal, {
579+
sha256hash,
580+
scanner: 'vt',
581+
status,
582+
})
588583
updated++
589584
} catch (error) {
590585
console.error(`[vt:pollPendingScans] Error checking hash ${sha256hash}:`, error)

0 commit comments

Comments
 (0)