Files
git.stella-ops.org/docs/reachability/evidence-schema.md
2026-01-28 02:30:48 +02:00

282 lines
7.3 KiB
Markdown

# Runtime Evidence Schema Reference
## Overview
Runtime evidence is serialized as NDJSON (Newline-Delimited JSON), with one event per line. The schema ensures deterministic output for reproducible evidence chains.
## Schema Location
- JSON Schema: `docs/schemas/runtime-evidence-v1.json`
- C# Models: `src/Signals/__Libraries/StellaOps.Signals.Ebpf/Schema/`
## Common Fields
Every evidence record includes these base fields:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ts_ns` | integer | Yes | Nanoseconds since system boot |
| `src` | string | Yes | Event source identifier |
| `pid` | integer | Yes | Process ID |
| `tid` | integer | No | Thread ID (if available) |
| `cgroup_id` | integer | Yes | Kernel cgroup ID |
| `container_id` | string | No | Container ID (enriched) |
| `image_digest` | string | No | Image digest (enriched) |
| `comm` | string | No | Process command name (max 16 chars) |
| `event` | object | Yes | Type-specific event data |
## Event Types
### File Access (`file_access`)
Captured from `sys_enter_openat` tracepoint.
```json
{
"ts_ns": 1234567890123456789,
"src": "tracepoint:syscalls:sys_enter_openat",
"pid": 1234,
"cgroup_id": 5678,
"container_id": "abc123def456",
"image_digest": "sha256:...",
"comm": "nginx",
"event": {
"type": "file_access",
"path": "/etc/nginx/nginx.conf",
"flags": 0,
"mode": 0,
"access": "read"
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `path` | string | File path (max 256 chars) |
| `flags` | integer | Open flags (`O_RDONLY`, `O_WRONLY`, etc.) |
| `mode` | integer | File mode (for creation) |
| `access` | string | Derived access type: `read`, `write`, `read_write` |
### Process Execution (`process_exec`)
Captured from `sched_process_exec` tracepoint.
```json
{
"ts_ns": 1234567890123456789,
"src": "tracepoint:sched:sched_process_exec",
"pid": 1234,
"cgroup_id": 5678,
"container_id": "abc123def456",
"image_digest": "sha256:...",
"comm": "python3",
"event": {
"type": "process_exec",
"filename": "/usr/bin/python3",
"ppid": 1000,
"argv": ["python3", "script.py", "--config", "/etc/app.conf"]
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `filename` | string | Executed binary path |
| `ppid` | integer | Parent process ID |
| `argv` | string[] | Command arguments (limited to first 4) |
### TCP State Change (`tcp_state`)
Captured from `inet_sock_set_state` tracepoint.
```json
{
"ts_ns": 1234567890123456789,
"src": "tracepoint:sock:inet_sock_set_state",
"pid": 1234,
"cgroup_id": 5678,
"container_id": "abc123def456",
"image_digest": "sha256:...",
"comm": "curl",
"event": {
"type": "tcp_state",
"family": "ipv4",
"old_state": "SYN_SENT",
"new_state": "ESTABLISHED",
"src_addr": "10.0.0.5",
"src_port": 45678,
"dst_addr": "93.184.216.34",
"dst_port": 443
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `family` | string | Address family: `ipv4` or `ipv6` |
| `old_state` | string | Previous TCP state |
| `new_state` | string | New TCP state |
| `src_addr` | string | Source IP address |
| `src_port` | integer | Source port |
| `dst_addr` | string | Destination IP address |
| `dst_port` | integer | Destination port |
TCP States: `CLOSED`, `LISTEN`, `SYN_SENT`, `SYN_RECV`, `ESTABLISHED`, `FIN_WAIT1`, `FIN_WAIT2`, `CLOSE_WAIT`, `CLOSING`, `LAST_ACK`, `TIME_WAIT`
### Network Operation (`network_op`)
Captured from libc `connect`/`accept` uprobes.
```json
{
"ts_ns": 1234567890123456789,
"src": "uprobe:libc.so.6:connect",
"pid": 1234,
"cgroup_id": 5678,
"container_id": "abc123def456",
"image_digest": "sha256:...",
"comm": "app",
"event": {
"type": "network_op",
"operation": "connect",
"family": "ipv4",
"addr": "10.0.1.100",
"port": 5432,
"result": 0
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `operation` | string | `connect` or `accept` |
| `family` | string | Address family |
| `addr` | string | Remote address |
| `port` | integer | Remote port |
| `result` | integer | Return value (0 = success) |
### SSL Operation (`ssl_op`)
Captured from OpenSSL `SSL_read`/`SSL_write` uprobes.
```json
{
"ts_ns": 1234567890123456789,
"src": "uprobe:libssl.so.3:SSL_write",
"pid": 1234,
"cgroup_id": 5678,
"container_id": "abc123def456",
"image_digest": "sha256:...",
"comm": "nginx",
"event": {
"type": "ssl_op",
"operation": "write",
"requested_bytes": 1024,
"actual_bytes": 1024,
"ssl_ptr": 140234567890
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `operation` | string | `read` or `write` |
| `requested_bytes` | integer | Bytes requested |
| `actual_bytes` | integer | Bytes actually transferred |
| `ssl_ptr` | integer | SSL context pointer (for correlation) |
### Symbol Call (`symbol_call`)
Captured from function uprobes.
```json
{
"ts_ns": 1234567890123456789,
"src": "uprobe:app:vulnerable_parse_json",
"pid": 1234,
"cgroup_id": 5678,
"container_id": "abc123def456",
"image_digest": "sha256:...",
"comm": "app",
"event": {
"type": "symbol_call",
"symbol": "vulnerable_parse_json",
"library": "/usr/lib/libapp.so",
"offset": 4096,
"address": 140234571986
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `symbol` | string | Function symbol name |
| `library` | string | Library/binary path |
| `offset` | integer | Offset within library |
| `address` | integer | Runtime address |
## Determinism Requirements
For byte-identical output across runs:
1. **Field Ordering**: All JSON keys sorted alphabetically
2. **Number Format**: Integers as-is, no floating point variance
3. **String Encoding**: UTF-8 with NFC normalization
4. **Null Handling**: Null fields omitted (not `"field": null`)
5. **Whitespace**: No trailing whitespace, single newline per record
## Chunk Metadata
Each evidence chunk includes metadata in its DSSE attestation:
```json
{
"predicateType": "stella.ops/runtime-evidence@v1",
"predicate": {
"chunk_id": "sha256:abc123...",
"chunk_sequence": 42,
"previous_chunk_id": "sha256:def456...",
"event_count": 150000,
"time_range": {
"start": "2026-01-27T10:00:00Z",
"end": "2026-01-27T11:00:00Z"
},
"collector_version": "1.0.0",
"kernel_version": "5.15.0-generic",
"compression": null,
"host_id": "node-01.cluster.local",
"container_ids": ["abc123", "def456"]
}
}
```
## Validation
Evidence can be validated against the JSON Schema:
```bash
# Validate single file
stella evidence validate evidence-chunk-001.ndjson
# Validate and show statistics
stella evidence validate --stats evidence-chunk-001.ndjson
```
## Migration from v0 Schemas
If using earlier per-language schemas, migrate to v1 unified schema:
1. Update field names to snake_case
2. Wrap type-specific fields in `event` object
3. Add `src` field with probe identifier
4. Ensure `ts_ns` uses nanoseconds since boot
Example migration:
```json
// v0 (old)
{"timestamp": 1234567890, "type": "file", "path": "/etc/config"}
// v1 (new)
{"ts_ns": 1234567890000000000, "src": "tracepoint:syscalls:sys_enter_openat", "pid": 1234, "cgroup_id": 5678, "event": {"type": "file_access", "path": "/etc/config", "flags": 0, "mode": 0, "access": "read"}}
```