6.6 KiB
6.6 KiB
Smart-Diff Technical Reference
Source Advisories:
- 09-Dec-2025 - Smart‑Diff and Provenance‑Rich Binaries
- 12-Dec-2025 - Smart‑Diff Detects Meaningful Risk Shifts
- 13-Dec-2025 - Smart‑Diff - Defining Meaningful Risk Change
- 05-Dec-2025 - Design Notes on Smart‑Diff and Call‑Stack Analysis
Last Updated: 2025-12-14
1. SMART-DIFF PREDICATE SCHEMA
{
"predicateType": "stellaops.dev/predicates/smart-diff@v1",
"predicate": {
"baseImage": {"name":"...", "digest":"sha256:..."},
"targetImage": {"name":"...", "digest":"sha256:..."},
"diff": {
"filesAdded": [...],
"filesRemoved": [...],
"filesChanged": [{"path":"...", "hunks":[...]}],
"packagesChanged": [{"name":"openssl","from":"1.1.1u","to":"3.0.14"}]
},
"context": {
"entrypoint":["/app/start"],
"env":{"FEATURE_X":"true"},
"user":{"uid":1001,"caps":["NET_BIND_SERVICE"]}
},
"reachabilityGate": {"reachable":true,"configActivated":true,"runningUser":false,"class":6},
"scanner": {"name":"StellaOps.Scanner","version":"...","ruleset":"reachability-2025.12"}
}
}
2. REACHABILITY GATE (3-BIT SEVERITY)
Data Model:
public sealed record ReachabilityGate(
bool? Reachable, // true / false / null for unknown
bool? ConfigActivated,
bool? RunningUser,
int Class, // 0..7 derived from the bits when all known
string Rationale // short explanation, human-readable
);
Class Computation: 0-7 based on 3 binary gates (reachable, config-activated, running user)
Unknown Handling:
- Never silently treat
nullasfalseortrue - If any bit is
null, setClass = -1or compute from known bits only
3. DELTA DATA STRUCTURES
// Delta.Packages
{
added[],
removed[],
changed[{name, fromVer, toVer}]
}
// Delta.Layers
{
changed[{path, fromHash, toHash, licenseDelta}]
}
// Delta.Functions
{
added[],
removed[],
changed[{symbol, file, signatureHashFrom, signatureHashTo}]
}
// PatchDelta
{
addedSymbols[],
removedSymbols[],
changedSignatures[]
}
4. SMART-DIFF ALGORITHMS
Core Diff Computation:
prev = load_snapshot(t-1)
curr = load_snapshot(t)
Δ.pkg = diff_packages(prev.lock, curr.lock)
Δ.layers= diff_layers(prev.sbom, curr.sbom)
Δ.funcs = diff_cfg(prev.cfgIndex, curr.cfgIndex)
scope = union(
impact_of(Δ.pkg.changed),
impact_of_files(Δ.layers.changed),
reachability_of(Δ.funcs.changed)
)
for f in scope.functions:
rescore(f)
for v in impacted_vulns(scope):
annotate(v, patch_delta(Δ))
link_evidence(v, dsse_attestation(), proof_links())
for v in previously_flagged where vulnerable_apis_now_absent(v, curr):
emit_vex_candidate(v, status="not_affected", rationale="API not present", evidence=proof_links())
5. MATERIAL RISK CHANGE DETECTION RULES
FindingKey:
FindingKey = (component_purl, component_version, cve_id)
RiskState Fields:
reachable: bool | unknownvex_status: enum(AFFECTED | NOT_AFFECTED | FIXED | UNDER_INVESTIGATION | UNKNOWN)in_affected_range: bool | unknownkev: boolepss_score: float | nullpolicy_flags: set<string>evidence_links: list<EvidenceLink>
Rule R1: Reachability Flip
reachablechanges:false → true(risk ↑) ortrue → false(risk ↓)
Rule R2: VEX Status Flip
- Meaningful changes:
AFFECTED ↔ NOT_AFFECTED,UNDER_INVESTIGATION → NOT_AFFECTED
Rule R3: Affected Range Boundary
in_affected_rangeflips:false → trueortrue → false
Rule R4: Intelligence/Policy Flip
kevchangesfalse → trueepss_scorecrosses configured thresholdpolicy_flagchanges severity (warn → block)
6. SUPPRESSION RULES
Suppression Conditions (ALL must apply):
reachable == falsevex_status == NOT_AFFECTEDkev == false- No policy override
Patch Churn Suppression:
- If version changes AND
in_affected_rangeremains false in both AND no KEV/policy flip → suppress
7. CALL-STACK ANALYSIS
C# Roslyn Skeleton:
public static class SmartDiff
{
public static async Task<HashSet<string>> ReachableSinks(string solutionPath, string[] entrypoints, string[] sinks)
{
var workspace = MSBuild.MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(solutionPath);
var index = new HashSet<string>();
foreach (var proj in solution.Projects)
{
var comp = await proj.GetCompilationAsync();
if (comp is null) continue;
var epSymbols = comp.GlobalNamespace.GetMembers().SelectMany(Descend)
.OfType<IMethodSymbol>().Where(m => entrypoints.Contains(m.ToDisplayString())).ToList();
var sinkSymbols = comp.GlobalNamespace.GetMembers().SelectMany(Descend)
.OfType<IMethodSymbol>().Where(m => sinks.Contains(m.ToDisplayString())).ToList();
foreach (var ep in epSymbols)
foreach (var sink in sinkSymbols)
{
var refs = await SymbolFinder.FindReferencesAsync(sink, solution);
if (refs.SelectMany(r => r.Locations).Any())
index.Add($"{ep.ToDisplayString()} -> {sink.ToDisplayString()}");
}
}
return index;
static IEnumerable<ISymbol> Descend(INamespaceOrTypeSymbol sym)
{
foreach (var m in sym.GetMembers())
{
yield return m;
if (m is INamespaceOrTypeSymbol nt)
foreach (var x in Descend(nt)) yield return x;
}
}
}
}
Go SSA Skeleton:
package main
import (
"fmt"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
)
func main() {
cfg := &packages.Config{Mode: packages.LoadAllSyntax, Tests: false}
pkgs, _ := packages.Load(cfg, "./...")
prog, pkgsSSA := ssa.NewProgram(pkgs[0].Fset, ssa.BuilderMode(0))
for _, p := range pkgsSSA { prog.CreatePackage(p, p.Syntax, p.TypesInfo, true) }
prog.Build()
cg := cha.CallGraph(prog)
fmt.Println("nodes:", len(cg.Nodes))
}
8. SINK TAXONOMY
sinks:
- CMD_EXEC
- UNSAFE_DESER
- SQL_RAW
- SSRF
- FILE_WRITE
- PATH_TRAVERSAL
- TEMPLATE_INJECTION
- CRYPTO_WEAK
- AUTHZ_BYPASS
9. POLICY SCORING FORMULA
Priority Score:
score =
+ 1000 if new.kev
+ 500 if new.reachable
+ 200 if reason includes RANGE_FLIP to affected
+ 150 if VEX_FLIP to AFFECTED
+ 0..100 based on EPSS (epss * 100)
+ policy weight: +300 if decision BLOCK, +100 if WARN
Document Version: 1.0 Target Platform: .NET 10, PostgreSQL ≥16, Angular v17