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
7 changes: 5 additions & 2 deletions convex/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,7 @@ export const getActiveSkillBatchForRescanInternal = internalQuery({
versionId: Id<'skillVersions'>
sha256hash: string
slug: string
wasFlagged: boolean
}> = []
let nextCursor = cursor

Expand All @@ -2007,6 +2008,8 @@ export const getActiveSkillBatchForRescanInternal = internalQuery({
versionId: version._id,
sha256hash: version.sha256hash,
slug: skill.slug,
wasFlagged:
(skill.moderationFlags as string[] | undefined)?.includes('flagged.suspicious') ?? false,
})
}

Expand Down Expand Up @@ -2608,8 +2611,8 @@ export const approveSkillByHashInternal = internalMutation({
if (isMalicious || alreadyBlocked) {
// Malicious from ANY scanner → blocked.malware (upgrade from suspicious)
newFlags = ['blocked.malware']
} else if ((isSuspicious || alreadyFlagged) && !bypassSuspicious) {
// Suspicious from ANY scanner → flagged.suspicious
} else if (isSuspicious && !bypassSuspicious) {
// Suspicious from this scanner → flagged.suspicious
newFlags = ['flagged.suspicious']
} else if (isClean) {
// Clean from this scanner — only clear if no other scanner has flagged
Expand Down
11 changes: 10 additions & 1 deletion convex/vt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ export const rescanActiveSkills = internalAction({
`[vt:rescan] Processing batch of ${batch.skills.length} skills (cursor=${cursor}, accumulated=${accTotal})`,
)

for (const { versionId, sha256hash, slug } of batch.skills) {
for (const { versionId, sha256hash, slug, wasFlagged } of batch.skills) {
try {
const vtResult = await checkExistingFile(apiKey, sha256hash)

Expand Down Expand Up @@ -834,6 +834,15 @@ export const rescanActiveSkills = internalAction({
status,
})
accUpdated++
} else if (wasFlagged && status === 'clean') {
// Verdict improved from suspicious → clean: clear the stale moderation flag
console.log(`[vt:rescan] ${slug}: verdict improved to clean, clearing suspicious flag`)
await ctx.runMutation(internal.skills.approveSkillByHashInternal, {
sha256hash,
scanner: 'vt',
status,
})
Comment on lines +840 to +844
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to approveSkillByHashInternal will not clear the suspicious flag for non-privileged users due to a bug in that function's logic. In approveSkillByHashInternal at line 2614, the condition (isSuspicious || alreadyFlagged) && !bypassSuspicious will match when a previously-flagged skill gets a clean verdict, causing it to keep newFlags = ['flagged.suspicious'] instead of clearing the flag. The isClean branch that checks for other scanners (line 2617-2624) never executes because the previous condition matches first. This means the PR's intended fix will not work as described - skills will remain flagged even when VT returns a clean verdict.

Copilot uses AI. Check for mistakes.
accUpdated++
} else {
accUnchanged++
}
Expand Down