feat: Add PathViewer and RiskDriftCard components with templates and styles
- Implemented PathViewerComponent for visualizing reachability call paths. - Added RiskDriftCardComponent to display reachability drift results. - Created corresponding HTML templates and SCSS styles for both components. - Introduced test fixtures for reachability analysis in JSON format. - Enhanced user interaction with collapsible and expandable features in PathViewer. - Included risk trend visualization and summary metrics in RiskDriftCard.
This commit is contained in:
256
docs/contracts/vuln-surface-v1.md
Normal file
256
docs/contracts/vuln-surface-v1.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# 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.<init>()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)
|
||||
Reference in New Issue
Block a user