# Vuln Surface Contract v1 **Sprint:** SPRINT_3700_0002_0001 **Task:** SURF-024 **Schema:** `stella.ops/vulnSurface@v1` ## Overview A Vulnerability Surface represents the specific methods that changed between a vulnerable and fixed version of a package. This enables precise reachability analysis by identifying the exact "trigger" methods that are dangerous rather than treating the entire package as vulnerable. ## Use Cases 1. **Noise Reduction** - Only flag findings where code actually calls vulnerable methods 2. **Confidence Tiers** - "Confirmed reachable" (calls trigger) vs "Potentially reachable" (uses package) 3. **Remediation Guidance** - Show developers exactly which API calls to avoid 4. **VEX Precision** - Automatically generate VEX "not_affected" for unreachable triggers ## Data Model ### VulnSurface Root object representing a computed vulnerability surface. | Field | Type | Required | Description | |-------|------|----------|-------------| | `surface_id` | integer | Yes | Database ID | | `cve_id` | string | Yes | CVE identifier (e.g., "CVE-2024-12345") | | `package_id` | string | Yes | Package identifier in PURL format | | `ecosystem` | string | Yes | Package ecosystem: `nuget`, `npm`, `maven`, `pypi` | | `vuln_version` | string | Yes | Vulnerable version analyzed | | `fixed_version` | string | Yes | First fixed version used for diff | | `sinks` | VulnSurfaceSink[] | Yes | Changed methods (vulnerability triggers) | | `trigger_count` | integer | Yes | Number of callers to sink methods | | `status` | VulnSurfaceStatus | Yes | Computation status | | `confidence` | number | Yes | Confidence score (0.0-1.0) | | `computed_at` | string | Yes | ISO 8601 timestamp | ### VulnSurfaceSink A method that changed between vulnerable and fixed versions. | Field | Type | Required | Description | |-------|------|----------|-------------| | `sink_id` | integer | Yes | Database ID | | `method_key` | string | Yes | Fully qualified method signature | | `method_name` | string | Yes | Simple method name | | `declaring_type` | string | Yes | Containing class/module | | `namespace` | string | No | Namespace/package | | `change_type` | MethodChangeType | Yes | How the method changed | | `is_public` | boolean | Yes | Whether method is publicly accessible | | `parameter_count` | integer | No | Number of parameters | | `return_type` | string | No | Return type | | `source_file` | string | No | Source file (from debug symbols) | | `start_line` | integer | No | Starting line number | | `end_line` | integer | No | Ending line number | ### VulnSurfaceTrigger A call site that invokes a vulnerable sink method. | Field | Type | Required | Description | |-------|------|----------|-------------| | `trigger_id` | integer | Yes | Database ID | | `sink_id` | integer | Yes | Reference to sink | | `scan_id` | UUID | Yes | Scan where trigger was found | | `caller_node_id` | string | Yes | Call graph node ID | | `caller_method_key` | string | Yes | FQN of calling method | | `caller_file` | string | No | Source file of caller | | `caller_line` | integer | No | Line number of call | | `reachability_bucket` | string | Yes | Reachability classification | | `path_length` | integer | No | Shortest path from entrypoint | | `confidence` | number | Yes | Confidence score (0.0-1.0) | | `call_type` | string | Yes | Call type: `direct`, `virtual`, `interface`, `reflection` | | `is_conditional` | boolean | Yes | Whether call is behind a condition | ## Enums ### VulnSurfaceStatus | Value | Description | |-------|-------------| | `pending` | Surface computation queued | | `computing` | Currently being computed | | `computed` | Successfully computed | | `failed` | Computation failed | | `stale` | Needs recomputation (new version available) | ### MethodChangeType | Value | Description | |-------|-------------| | `added` | Method added in fix (not in vulnerable version) | | `removed` | Method removed in fix (was in vulnerable version) | | `modified` | Method body changed between versions | | `unknown` | Change type could not be determined | ### Reachability Buckets | Bucket | Description | Risk Level | |--------|-------------|------------| | `entrypoint` | Sink is directly exposed as entrypoint | Critical | | `direct` | Reachable from entrypoint with no authentication gates | High | | `runtime` | Reachable but behind runtime conditions/auth | Medium | | `unknown` | Reachability could not be determined | Medium | | `unreachable` | No path from any entrypoint | Low | ## Fingerprinting Methods ### cecil-il (NuGet/.NET) Uses Mono.Cecil to compute SHA-256 hash of IL instruction sequence: ``` IL_0000: ldarg.0 IL_0001: call System.Object::.ctor() IL_0006: ret ``` Normalized to remove: - NOP instructions - Debug sequence points - Local variable indices (replaced with placeholders) ### babel-ast (npm/Node.js) Uses Babel to parse JavaScript/TypeScript and compute hash of normalized AST: ```javascript function vulnerable(input) { eval(input); // dangerous! } ``` Normalized to remove: - Comments - Whitespace - Variable names (renamed to positional) ### asm-bytecode (Maven/Java) Uses ASM to compute hash of Java bytecode: ``` ALOAD 0 INVOKESPECIAL java/lang/Object.()V RETURN ``` Normalized to remove: - Line number tables - Local variable tables - Stack map frames ### python-ast (PyPI) Uses Python's `ast` module to compute hash of normalized AST: ```python def vulnerable(user_input): exec(user_input) # dangerous! ``` Normalized to remove: - Docstrings - Comments - Variable names ## Database Schema ```sql -- Surfaces table CREATE TABLE scanner.vuln_surfaces ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, cve_id TEXT NOT NULL, package_ecosystem TEXT NOT NULL, package_name TEXT NOT NULL, vuln_version TEXT NOT NULL, fixed_version TEXT, fingerprint_method TEXT NOT NULL, total_methods_vuln INTEGER, total_methods_fixed INTEGER, changed_method_count INTEGER, computed_at TIMESTAMPTZ DEFAULT now(), UNIQUE (tenant_id, cve_id, package_ecosystem, package_name, vuln_version) ); -- Sinks table CREATE TABLE scanner.vuln_surface_sinks ( id UUID PRIMARY KEY, surface_id UUID REFERENCES scanner.vuln_surfaces(id) ON DELETE CASCADE, method_key TEXT NOT NULL, method_name TEXT NOT NULL, declaring_type TEXT NOT NULL, change_type TEXT NOT NULL, UNIQUE (surface_id, method_key) ); -- Triggers table CREATE TABLE scanner.vuln_surface_triggers ( id UUID PRIMARY KEY, sink_id UUID REFERENCES scanner.vuln_surface_sinks(id) ON DELETE CASCADE, scan_id UUID NOT NULL, caller_node_id TEXT NOT NULL, reachability_bucket TEXT NOT NULL, confidence REAL NOT NULL, UNIQUE (sink_id, scan_id, caller_node_id) ); ``` ## API Endpoints ### POST /api/v1/surfaces/compute Request surface computation for a CVE + package. **Request:** ```json { "cveId": "CVE-2024-12345", "ecosystem": "nuget", "packageName": "Newtonsoft.Json", "vulnVersion": "13.0.1", "fixedVersion": "13.0.2" } ``` **Response:** ```json { "surfaceId": "uuid", "status": "pending" } ``` ### GET /api/v1/surfaces/{surfaceId} Get computed surface with sinks. ### GET /api/v1/surfaces/{surfaceId}/triggers?scanId={scanId} Get triggers for a surface in a specific scan. ## Integration Points 1. **Concelier** - Feeds CVE + affected version ranges 2. **Scanner** - Computes surfaces during SBOM analysis 3. **Call Graph** - Provides reachability analysis 4. **VEX Lens** - Uses surfaces for automated VEX decisions 5. **UI** - Displays surface details and trigger paths ## References - [Vuln Surfaces Sprint](../implplan/SPRINT_3700_0002_0001_vuln_surfaces_core.md) - [Reachability Architecture](../reachability/README.md) - [RichGraph Contract](./richgraph-v1.md)