feat(root): adds better management support for security#452
feat(root): adds better management support for security#452yowainwright merged 5 commits intomainfrom
Conversation
17049ad to
ede9330
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #452 +/- ##
==========================================
+ Coverage 97.04% 97.14% +0.10%
==========================================
Files 61 61
Lines 8312 8618 +306
==========================================
+ Hits 8066 8372 +306
Misses 246 246 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@greptile |
|
| Filename | Overview |
|---|---|
| src/core/security/utils.ts | CVE merge in deduplicateAlerts only fires when the incoming alert wins (higher severity); CVEs from lower-severity duplicate alerts are silently dropped. |
| src/core/appendix/utils.ts | Major improvements: adds isKeptEntry, isKeepExpired (now wired into isUnusedEntry), normalizeLedgerCveField, and multi-detail CVE aggregation. Minor: buildCveDetails does not deduplicate entries when multiple details share the same CVE. |
| src/core/update/index.ts | Adds stepUpdateKeptOverrides (updates potentiallyFixedIn using live alerts) and passes skipRemovalKeys into stepRemoveUnused. Previous issues with KeepConstraint guard and isKeptEntry usage are fully resolved. |
| src/cli/index.ts | Adds checkRemovalSafety helper that runs a synthetic security scan on candidate-unused entries before removal, and wires result into skipRemovalKeys. CVE fields migrated to array form throughout. |
| src/types.ts | Introduces KeepConstraint, CveDetail, and expanded AppendixItem.ledger fields (securityCheckResult, cves, cveDetails, keep, potentiallyFixedIn, resolution fields). Clean, well-structured additions. |
| src/config/constants.ts | Re-exports KeepConstraint from src/types.ts (resolving the previous duplicate definition) and expands AppendixItem.ledger validation with clean isFieldValid helpers covering all new fields. |
| src/core/security/providers/osv.ts | Migrates from extractCVE (returns first alias) to extractCVEs (returns all CVE aliases). Correctly omits the field when the array is empty. |
| src/core/security/providers/github.ts | Wraps the advisory's single cve_id in an array and only adds the field when non-empty. Correct migration to the cves array shape. |
| src/core/security/providers/snyk.ts | Migrates from the first CVE identifier to the full identifiers.CVE array. Correctly omits the field when empty. |
| src/core/security/providers/socket.ts | Wraps the socket issue's single CVE in a one-element array when present, omits it otherwise. Clean migration. |
| src/dx/terminal-graph.ts | Updates display helpers to use cves array, adds formatKeepStatus and formatPotentiallyFixedIn for richer override display, and adjusts icon selection to use info for kept entries. |
| tests/integration/e2e-cli.test.ts | Adds comprehensive e2e tests covering keep round-trips, removeUnused skip behaviour, cveDetails/vulnerableRange population, OSV scan pipeline, and legacy cve → cves normalisation. |
Sequence Diagram
sequenceDiagram
participant CLI as cli/index.ts
participant SC as SecurityChecker
participant CRS as checkRemovalSafety
participant UPD as update pipeline
participant SKO as stepUpdateKeptOverrides
participant SRU as stepRemoveUnused
CLI->>SC: checkSecurity(config)
SC-->>CLI: alerts (cves[], patchedVersion, vulnerableRange)
CLI->>CLI: buildSecurityResult(alerts)
CLI->>CLI: mergedOptions ← securityAlerts
opt removeUnused && config
CLI->>CRS: checkRemovalSafety(config, securityChecker, options)
CRS->>CRS: findUnusedAppendixEntries(appendix)
CRS->>SC: checkSecurity(syntheticConfig)
SC-->>CRS: alerts for candidate packages
CRS-->>CLI: skipKeys (still-vulnerable unused entries)
CLI->>CLI: mergedOptions ← skipRemovalKeys
end
CLI->>UPD: update(mergedOptions)
UPD->>SKO: stepUpdateKeptOverrides(ctx)
SKO->>SKO: match kept entries' cves against live alerts
SKO-->>UPD: updated potentiallyFixedIn in appendix
UPD->>SRU: stepRemoveUnused(ctx)
SRU->>SRU: findUnusedAppendixEntries(appendix, rootDeps)
SRU->>SRU: filter out skipRemovalKeys
SRU->>SRU: warn on CVE-tracked removals
SRU-->>UPD: finalAppendix / finalOverrides
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/core/security/utils.ts
Line: 36-45
Comment:
**CVE merge only fires on the winning branch**
When `shouldReplace` is `false` (the existing alert has equal or higher severity), the incoming alert's CVEs are silently dropped. Two alerts for the same package can legitimately share a first CVE but carry different additional CVEs — for example, OSV can return separate vulnerability records that alias to `["CVE-A", "CVE-B"]` and `["CVE-A", "CVE-C"]` respectively. Processing the higher-severity alert first then encountering the lower-severity one would lose `CVE-C` entirely.
The CVE merge needs to happen unconditionally:
```suggestion
if (shouldReplace) {
const mergedCves = existing
? [...new Set([...(existing.cves || []), ...(alert.cves || [])])]
: alert.cves;
const merged =
mergedCves && mergedCves.length > 0
? { ...alert, cves: mergedCves }
: alert;
map.set(key, merged);
} else if (existing && alert.cves?.length) {
const mergedCves = [...new Set([...(existing.cves || []), ...alert.cves])];
map.set(key, { ...existing, cves: mergedCves });
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/core/appendix/utils.ts
Line: 147-156
Comment:
**`cveDetails` can contain duplicate entries**
`buildCveDetails` flat-maps every CVE across all `SecurityOverrideDetail` objects without deduplication. If two detail objects for the same package both carry the same CVE (e.g. two providers or two vulnerability records sharing an alias), the resulting `cveDetails` array will contain duplicate entries — while the sibling `cves` flat list is correctly deduplicated via `new Set`.
Consider deduplicating by CVE identifier:
```ts
const buildCveDetails = (details: SecurityOverrideDetail[]): CveDetail[] => {
const map = new Map<string, CveDetail>();
for (const d of details) {
for (const cve of d.cves || []) {
if (!map.has(cve)) {
const detail: CveDetail = { cve };
if (d.severity) detail.severity = d.severity;
if (d.patchedVersion) detail.patchedVersion = d.patchedVersion;
map.set(cve, detail);
}
}
}
return Array.from(map.values());
};
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "feat(root): adds more code optimizations" | Re-trigger Greptile
Proposed Changes