Skip to content

Comments

fix: clear stale suspicious flag when VT verdict improves#418

Open
Phineas1500 wants to merge 2 commits intoopenclaw:mainfrom
Phineas1500:fix/clear-stale-suspicious-flag
Open

fix: clear stale suspicious flag when VT verdict improves#418
Phineas1500 wants to merge 2 commits intoopenclaw:mainfrom
Phineas1500:fix/clear-stale-suspicious-flag

Conversation

@Phineas1500
Copy link

@Phineas1500 Phineas1500 commented Feb 18, 2026

Summary

  • The daily VT rescan (rescanActiveSkills) updates vtAnalysis on the version when a verdict changes, but only called escalateByVtInternal for suspicious/malicious verdicts
  • When a verdict improved from suspicious to clean, the version's vtAnalysis.status was updated to "clean" (website shows "Benign") but the skill's moderationFlags kept the stale "flagged.suspicious" entry (CLI warns "suspicious", website shows warning banner)
  • Now the rescan calls approveSkillByHashInternal to clear the flag when a previously-flagged skill's verdict improves to clean

Reported via https://clawhub.ai/pskoett/self-improving-agent — both scanners say Benign but the skill is still flagged.

Test plan

  • Verify rescanActiveSkills clears flagged.suspicious for skills whose VT verdict improved to clean
  • Verify skills that are still suspicious/malicious are unaffected
  • Verify clean skills that were never flagged remain counted as accUnchanged

Greptile Summary

Fixes a bug where the daily VT rescan would update a version's vtAnalysis.status to "clean" when the verdict improved, but left a stale flagged.suspicious entry in the skill's moderationFlags. This caused the CLI to warn "suspicious" and the website to show a warning banner even after VT declared the skill benign.

  • convex/skills.ts: Adds a wasFlagged boolean to the rescan batch query, indicating whether the skill currently has flagged.suspicious in its moderationFlags.
  • convex/vt.ts: Uses wasFlagged in the rescan loop to call approveSkillByHashInternal when a previously-flagged skill's verdict improves, clearing the stale moderation flag.
  • The cross-scanner protection in approveSkillByHashInternal ensures that if another scanner (e.g., LLM) has independently flagged the skill, the flag is preserved.

Confidence Score: 4/5

  • This PR is safe to merge with one minor edge case to consider around unrecognized VT verdicts.
  • The fix is well-scoped and addresses the reported bug correctly. The cross-scanner protection in approveSkillByHashInternal handles multi-scanner scenarios properly. One minor logic concern: the else if (wasFlagged) branch doesn't guard against status === 'pending', which could clear a suspicious flag on an inconclusive verdict. In practice this edge case is unlikely given VT's current verdict categories, but adding && status === 'clean' would be more defensive.
  • convex/vt.ts — the else if (wasFlagged) condition at line 837 could be tightened to only trigger on status === 'clean'.

Last reviewed commit: a17d333

Context used:

  • Context from dashboard - AGENTS.md (source)

The daily VT rescan updated vtAnalysis on the version but only called
escalateByVtInternal for suspicious/malicious verdicts. When a verdict
improved from suspicious to clean, the version's vtAnalysis was updated
(website shows "Benign") but the skill's moderationFlags kept the stale
"flagged.suspicious" entry (CLI warns "suspicious"). Now the rescan
calls approveSkillByHashInternal to clear the flag on de-escalation.
Copilot AI review requested due to automatic review settings February 18, 2026 19:45
@vercel
Copy link
Contributor

vercel bot commented Feb 18, 2026

@Phineas1500 is attempting to deploy a commit to the Amantus Machina Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

convex/vt.ts Outdated
Comment on lines 837 to 845
} else if (wasFlagged) {
// 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,
})
accUpdated++
Copy link

Choose a reason for hiding this comment

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

Pending verdict also clears suspicious flag

The else if (wasFlagged) branch runs for any status that isn't malicious or suspicious, which includes 'pending' (returned by verdictToStatus for unrecognized verdict strings). If VT ever introduces a new verdict category not in BENIGN_VERDICTS/MALICIOUS_VERDICTS/SUSPICIOUS_VERDICTS, a previously-flagged skill would have its suspicious flag cleared even though the verdict is inconclusive.

Consider guarding this branch to only clear on a confirmed clean verdict:

Suggested change
} else if (wasFlagged) {
// 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,
})
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,
})
accUpdated++
Prompt To Fix With AI
This is a comment left during a code review.
Path: convex/vt.ts
Line: 837:845

Comment:
**Pending verdict also clears suspicious flag**

The `else if (wasFlagged)` branch runs for any `status` that isn't `malicious` or `suspicious`, which includes `'pending'` (returned by `verdictToStatus` for unrecognized verdict strings). If VT ever introduces a new verdict category not in `BENIGN_VERDICTS`/`MALICIOUS_VERDICTS`/`SUSPICIOUS_VERDICTS`, a previously-flagged skill would have its suspicious flag cleared even though the verdict is inconclusive.

Consider guarding this branch to only clear on a confirmed clean verdict:

```suggestion
        } 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,
          })
          accUpdated++
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR attempts to fix a bug where skills flagged as suspicious by VirusTotal (VT) retain the flagged.suspicious moderation flag even after VT rescan returns a clean verdict. The rescan function updates the version's vtAnalysis.status but previously did not clear the stale flag from the skill's moderationFlags.

Changes:

  • Added wasFlagged field to track skills with flagged.suspicious flag during rescan batch queries
  • Modified rescan logic to call approveSkillByHashInternal when a previously-flagged skill's VT verdict improves to clean

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
convex/skills.ts Added wasFlagged boolean field to getActiveSkillBatchForRescanInternal to identify skills with existing suspicious flags
convex/vt.ts Added logic to call approveSkillByHashInternal when wasFlagged is true and verdict is no longer suspicious/malicious

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

convex/vt.ts Outdated
Comment on lines 837 to 845
} else if (wasFlagged) {
// 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,
})
accUpdated++
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 condition wasFlagged will execute for any status that is not 'malicious' or 'suspicious', including 'pending'. When verdictToStatus returns 'pending' (for unknown verdicts), calling approveSkillByHashInternal will treat it as clean (since isClean = !isMalicious && !isSuspicious) and clear the suspicious flag even though the verdict is actually pending, not clean. The condition should explicitly check that the status is 'clean' before clearing flags.

Copilot uses AI. Check for mistakes.
Comment on lines +840 to +844
await ctx.runMutation(internal.skills.approveSkillByHashInternal, {
sha256hash,
scanner: 'vt',
status,
})
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.
convex/vt.ts Outdated
Comment on lines 837 to 845
} else if (wasFlagged) {
// 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,
})
accUpdated++
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.

This new functionality lacks test coverage. There should be a test that verifies a previously-flagged skill has its flagged.suspicious flag cleared when VT returns a clean verdict during rescan. This is especially important given the complexity of the multi-scanner flag merging logic in approveSkillByHashInternal.

Copilot uses AI. Check for mistakes.
…ing clean verdicts

Address review feedback:
- Guard rescan de-escalation with `status === 'clean'` so pending/unknown
  verdicts don't accidentally clear the suspicious flag
- Fix approveSkillByHashInternal where `alreadyFlagged` in the condition
  `(isSuspicious || alreadyFlagged) && !bypassSuspicious` prevented clean
  verdicts from reaching the isClean branch that properly checks whether
  a different scanner set the flag
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant