Reachability · Runtime + Static Union (v0.1)

Audience: Scanner, Zastava, and Signals engineers wiring the reachability ingest pipeline, plus Policy and UI consumers reading the resulting facts.

What this covers

Pipeline (at a glance)

  1. Scanner emits language-specific callgraphs as richgraph-v1 and packs them into CAS under reachability_graphs/<digest>.tar.zst with manifest meta.json.
  2. Zastava Observer streams NDJSON runtime facts (symbol_id, code_id, hit_count, loader_base, cas_uri) to Signals POST /signals/runtime-facts or /runtime-facts/ndjson.
  3. Union bundles (runtime + static) are uploaded as ZIP to POST /signals/reachability/union with optional X-Analysis-Id; Signals stores under reachability_graphs/{analysisId}/.
  4. Signals scoring consumes union data + runtime facts, computes per-target states (bucket, weight, confidence, score), fact-level score, unknowns pressure, and publishes signals.fact.updated@v1 events.
  5. Replay records provenance: reachability section in replay manifest lists CAS URIs (graphs + runtime traces), namespaces, analyzer/version, callgraphIds, and the shared analysisId.

Storage & CAS namespaces

Signals API quick reference

Scoring and unknowns

Base-image entrypoint and libc inference (S051 decision point)

Application callgraphs often cannot prove reachability for compiled OS packages inside a base image. A thin Alpine image can contain busybox and musl while having no application-layer entrypoint graph that reaches those binaries or libraries. In that state, ReachGraph must emit unknown rather than letting a policy pack imply reachability from package presence alone.

The cheaper S051 path is an explicit inference mode, not a silent default:

  1. Entrypoint/PID 1 package inference. If Scanner resolves the effective container ENTRYPOINT/CMD to an executable path and maps that path to an installed package PURL, ReachGraph may emit a reachable fact for that package with bucket entrypoint. Example: /bin/sh or a busybox applet that is the effective PID 1 command maps to the busybox package.
  2. Runtime loader/libc inference. If Scanner resolves the entrypoint binary’s interpreter/dynamic-loader dependency and maps the loaded libc to an installed package PURL, ReachGraph may emit a reachable fact for that libc package. Example: Alpine musl required by the entrypoint process.

Both inferences must be evidence-backed. The fact must record at least the image digest, package PURL, resolved executable or library path, inference kind (entrypoint-package or process-libc), analyzer name/version, and a stable evidence reference. Until that exists, base-image CVE blocks belong to an honest denylist/backstop (matchClass=denylist), not a reachability verdict (matchClass=reachability).

Replay contract changes (v0.1 add-ons)

Operator checklist


Node Hash Joins and Runtime Evidence Linkage

Sprint: SPRINT_20260112_008_DOCS_path_witness_contracts (PW-DOC-002)

Overview

Node hashes provide a canonical way to join static reachability analysis with runtime observations. Each node in a callgraph can be identified by a stable hash computed from its PURL and symbol information, enabling:

  1. Static-to-runtime correlation: Match runtime stack traces to static callgraph nodes
  2. Cross-scan consistency: Compare reachability across different analysis runs
  3. Evidence linking: Associate attestations with specific code paths

Node Hash Recipe

A node hash is computed as:

nodeHash = SHA256(normalize(purl) + ":" + normalize(symbol))

Where:

Example:

{
  "purl": "pkg:npm/express@4.18.2",
  "symbol": "Router.handle",
  "nodeHash": "sha256:a1b2c3d4..."
}

Path Hash and Top-K Selection

A path hash identifies a specific call path from entrypoint to sink:

pathHash = SHA256(entryNodeHash + ":" + joinedIntermediateHashes + ":" + sinkNodeHash)

For long paths, only the top-K most significant nodes are included (default K=10):

Runtime Evidence Linkage

Runtime observations from Zastava can be linked to static analysis using node hashes:

FieldDescription
observedNodeHashesNode hashes seen at runtime
observedPathHashesPath hashes confirmed by runtime traces
runtimeEvidenceAtTimestamp of runtime observation (RFC3339)
callstackHashHash of the observed call stack

Join Example

To correlate static reachability with runtime evidence:

-- Find statically-reachable vulnerabilities confirmed at runtime
SELECT 
  s.vulnerability_id,
  s.path_hash,
  r.observed_at
FROM static_reachability s
JOIN runtime_observations r 
  ON s.sink_node_hash = ANY(r.observed_node_hashes)
WHERE s.reachable = true
  AND r.observed_at > NOW() - INTERVAL '7 days';

SARIF Integration

Node hashes are exposed in SARIF outputs via stellaops/* property keys:

{
  "results": [{
    "ruleId": "CVE-2024-1234",
    "properties": {
      "stellaops/nodeHash": "sha256:abc123...",
      "stellaops/pathHash": "sha256:def456...",
      "stellaops/topKNodeHashes": ["sha256:...", "sha256:..."],
      "stellaops/evidenceUri": "cas://evidence/...",
      "stellaops/observedAtRuntime": true
    }
  }]
}

Policy Gate Usage

Policy rules can reference node and path hashes for fine-grained control:

rules:
  - name: block-confirmed-critical-path
    match:
      severity: CRITICAL
      reachability:
        pathHash:
          exists: true
        observedAtRuntime: true
    action: block

See path-gates-advanced.yaml for comprehensive examples.


References