feat(api): Add Policy Registry API specification
Some checks failed
AOC Guard CI / aoc-verify (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
mock-dev-release / package-mock-release (push) Has been cancelled

- Introduced OpenAPI specification for the StellaOps Policy Registry API, covering endpoints for verification policies, policy packs, snapshots, violations, overrides, sealed mode operations, and advisory staleness tracking.
- Defined schemas, parameters, and responses for comprehensive API documentation.

chore(scanner): Add global usings for scanner analyzers

- Created GlobalUsings.cs to simplify namespace usage across analyzer libraries.

feat(scanner): Implement Surface Service Collection Extensions

- Added SurfaceServiceCollectionExtensions for dependency injection registration of surface analysis services.
- Included methods for adding surface analysis, surface collectors, and entry point collectors to the service collection.
This commit is contained in:
StellaOps Bot
2025-12-06 20:52:23 +02:00
parent 05597616d6
commit f6c22854a4
37 changed files with 5664 additions and 1263 deletions

View File

@@ -0,0 +1,886 @@
# OpenAPI 3.1 specification for StellaOps TaskRunner WebService
openapi: 3.1.0
info:
title: StellaOps TaskRunner API
version: 0.1.0-draft
description: |
Contract for TaskRunner service covering pack runs, simulations, logs, artifacts, and approvals.
Uses the platform error envelope and tenant header `X-StellaOps-Tenant`.
## Streaming Endpoints
The `/runs/{runId}/logs` endpoint returns logs in NDJSON (Newline Delimited JSON) format
for efficient streaming. Each line is a complete JSON object.
## Control Flow Steps
TaskPacks support the following step kinds:
- **run**: Execute an action using a builtin or custom executor
- **parallel**: Execute child steps concurrently with optional maxParallel limit
- **map**: Iterate over items and execute a template step for each
- **loop**: Iterate with items expression, range, or static list
- **conditional**: Branch based on condition expressions
- **gate.approval**: Require manual approval before proceeding
- **gate.policy**: Evaluate policy and optionally require override approval
servers:
- url: https://taskrunner.stellaops.example.com
description: Production
- url: https://taskrunner.dev.stellaops.example.com
description: Development
security:
- oauth2: [taskrunner.viewer]
- oauth2: [taskrunner.operator]
- oauth2: [taskrunner.admin]
paths:
/v1/task-runner/simulations:
post:
summary: Simulate a task pack
description: |
Validates a task pack manifest, creates an execution plan, and simulates the run
without actually executing any steps. Returns the simulation result showing which
steps would execute, which are skipped, and which require approvals.
operationId: simulateTaskPack
tags: [Simulations]
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SimulationRequest'
examples:
basic-simulation:
summary: Basic simulation request
value:
manifest: |
apiVersion: stellaops.io/pack.v1
kind: TaskPack
metadata:
name: scan-deploy
version: 1.0.0
spec:
inputs:
- name: target
type: string
required: true
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: scan
run:
uses: builtin:scanner
with:
target: "{{ inputs.target }}"
inputs:
target: "registry.example.com/app:v1.2.3"
responses:
'200':
description: Simulation completed
content:
application/json:
schema:
$ref: '#/components/schemas/SimulationResponse'
examples:
simulation-result:
value:
planHash: "sha256:a1b2c3d4e5f6..."
failurePolicy:
maxAttempts: 1
backoffSeconds: 0
continueOnError: false
steps:
- id: scan
templateId: scan
kind: Run
enabled: true
status: Pending
uses: "builtin:scanner"
children: []
outputs: []
hasPendingApprovals: false
'400':
description: Invalid manifest or inputs
content:
application/json:
schema:
$ref: '#/components/schemas/PlanErrorResponse'
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs:
post:
summary: Create a pack run
description: |
Creates a new pack run from a task pack manifest. The run is scheduled for execution
and will proceed through its steps. If approval gates are present, the run will pause
at those gates until approvals are granted.
operationId: createPackRun
tags: [Runs]
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateRunRequest'
examples:
create-run:
summary: Create a new run
value:
runId: "run-20251206-001"
manifest: |
apiVersion: stellaops.io/pack.v1
kind: TaskPack
metadata:
name: deploy-app
version: 2.0.0
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 200
memoryLimitMiB: 256
quotaSeconds: 120
slo:
runP95Seconds: 600
approvalP95Seconds: 1800
maxQueueDepth: 50
approvals:
- id: security-review
grants: [packs.approve]
steps:
- id: build
run:
uses: builtin:build
- id: approval
gate:
approval:
id: security-review
message: "Security review required before deploy"
- id: deploy
run:
uses: builtin:deploy
tenantId: "tenant-prod"
responses:
'201':
description: Run created
headers:
Location:
description: URL of the created run
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/RunStateResponse'
'400':
description: Invalid manifest or inputs
content:
application/json:
schema:
$ref: '#/components/schemas/PlanErrorResponse'
'409':
description: Run ID already exists
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}:
get:
summary: Get run state
description: |
Returns the current state of a pack run, including status of all steps,
failure policy, and timing information.
operationId: getRunState
tags: [Runs]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'200':
description: Run state
content:
application/json:
schema:
$ref: '#/components/schemas/RunStateResponse'
examples:
running:
summary: Run in progress
value:
runId: "run-20251206-001"
planHash: "sha256:a1b2c3d4..."
failurePolicy:
maxAttempts: 2
backoffSeconds: 30
continueOnError: false
createdAt: "2025-12-06T10:00:00Z"
updatedAt: "2025-12-06T10:05:00Z"
steps:
- stepId: build
kind: Run
enabled: true
continueOnError: false
status: Succeeded
attempts: 1
lastTransitionAt: "2025-12-06T10:02:00Z"
- stepId: approval
kind: GateApproval
enabled: true
continueOnError: false
approvalId: security-review
gateMessage: "Security review required before deploy"
status: Pending
attempts: 0
statusReason: "awaiting-approval"
- stepId: deploy
kind: Run
enabled: true
continueOnError: false
status: Pending
attempts: 0
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/logs:
get:
summary: Stream run logs
description: |
Returns run logs as a stream of NDJSON (Newline Delimited JSON) entries.
Each line is a complete JSON object representing a log entry with timestamp,
level, event type, message, and optional metadata.
**Content-Type**: `application/x-ndjson`
operationId: streamRunLogs
tags: [Logs]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'200':
description: Log stream
content:
application/x-ndjson:
schema:
$ref: '#/components/schemas/RunLogEntry'
examples:
log-stream:
summary: Sample NDJSON log stream
value: |
{"timestamp":"2025-12-06T10:00:00Z","level":"info","eventType":"run.created","message":"Run created via API.","metadata":{"planHash":"sha256:a1b2c3d4...","requestedAt":"2025-12-06T10:00:00Z"}}
{"timestamp":"2025-12-06T10:00:01Z","level":"info","eventType":"step.started","message":"Starting step: build","stepId":"build"}
{"timestamp":"2025-12-06T10:02:00Z","level":"info","eventType":"step.completed","message":"Step completed: build","stepId":"build","metadata":{"duration":"119s"}}
{"timestamp":"2025-12-06T10:02:01Z","level":"warn","eventType":"gate.awaiting","message":"Awaiting approval: security-review","stepId":"approval"}
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/artifacts:
get:
summary: List run artifacts
description: |
Returns a list of artifacts captured during the run, including file outputs,
evidence bundles, and expression-evaluated results.
operationId: listRunArtifacts
tags: [Artifacts]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'200':
description: Artifact list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/RunArtifact'
examples:
artifacts:
value:
- name: scan-report
type: file
sourcePath: "/output/scan-report.json"
storedPath: "runs/run-20251206-001/artifacts/scan-report.json"
status: captured
capturedAt: "2025-12-06T10:02:00Z"
- name: evidence-bundle
type: object
status: captured
capturedAt: "2025-12-06T10:02:00Z"
expressionJson: '{"sha256":"abc123...","attestations":[...]}'
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/approvals/{approvalId}:
post:
summary: Apply approval decision
description: |
Applies an approval decision (approved, rejected, or expired) to a pending
approval gate. The planHash must match to prevent approving a stale plan.
If approved, the run will resume execution. If rejected, the run will fail
at the gate step.
operationId: applyApprovalDecision
tags: [Approvals]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
- $ref: '#/components/parameters/ApprovalId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ApprovalDecisionRequest'
examples:
approve:
summary: Approve the gate
value:
decision: approved
planHash: "sha256:a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef"
actorId: "user:alice@example.com"
summary: "Reviewed and approved for production deployment"
reject:
summary: Reject the gate
value:
decision: rejected
planHash: "sha256:a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef"
actorId: "user:bob@example.com"
summary: "Security scan found critical vulnerabilities"
responses:
'200':
description: Decision applied
content:
application/json:
schema:
$ref: '#/components/schemas/ApprovalDecisionResponse'
examples:
approved:
value:
status: approved
resumed: true
'400':
description: Invalid decision or planHash format
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'404':
description: Run or approval not found
'409':
description: Plan hash mismatch
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/cancel:
post:
summary: Cancel a run
description: |
Requests cancellation of a run. Remaining pending steps will be marked as
skipped. Steps that have already succeeded or been skipped are not affected.
operationId: cancelRun
tags: [Runs]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'202':
description: Cancellation accepted
headers:
Location:
description: URL of the run
schema:
type: string
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: [cancelled]
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/.well-known/openapi:
get:
summary: Get OpenAPI metadata
description: |
Returns metadata about the OpenAPI specification including the spec URL,
ETag for caching, and a signature for verification.
operationId: getOpenApiMetadata
tags: [Metadata]
responses:
'200':
description: OpenAPI metadata
headers:
ETag:
description: Spec version ETag
schema:
type: string
X-Signature:
description: Spec signature for verification
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/OpenApiMetadata'
examples:
metadata:
value:
specUrl: "/openapi"
version: "0.1.0-draft"
buildVersion: "20251206.1"
etag: '"abc123"'
signature: "sha256:def456..."
components:
securitySchemes:
oauth2:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://auth.stellaops.example.com/oauth/token
scopes:
taskrunner.viewer: Read-only access to runs and logs
taskrunner.operator: Create runs and apply approvals
taskrunner.admin: Full administrative access
parameters:
Tenant:
name: X-StellaOps-Tenant
in: header
required: false
description: Tenant slug (optional for single-tenant deployments)
schema:
type: string
RunId:
name: runId
in: path
required: true
description: Unique run identifier
schema:
type: string
pattern: '^[a-zA-Z0-9_-]+$'
ApprovalId:
name: approvalId
in: path
required: true
description: Approval gate identifier (from task pack approvals section)
schema:
type: string
responses:
Error:
description: Standard error envelope
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
examples:
internal-error:
value:
error:
code: internal_error
message: "An unexpected error occurred"
traceId: "f62f3c2b9c8e4c53"
schemas:
ErrorEnvelope:
type: object
required: [error]
properties:
error:
type: object
required: [code, message]
properties:
code:
type: string
description: Machine-readable error code
message:
type: string
description: Human-readable error message
traceId:
type: string
description: Trace ID for debugging
SimulationRequest:
type: object
required: [manifest]
properties:
manifest:
type: string
description: Task pack manifest in YAML format
inputs:
type: object
additionalProperties: true
description: Input values to provide to the task pack
SimulationResponse:
type: object
required: [planHash, failurePolicy, steps, outputs, hasPendingApprovals]
properties:
planHash:
type: string
description: SHA-256 hash of the execution plan
pattern: '^sha256:[a-f0-9]{64}$'
failurePolicy:
$ref: '#/components/schemas/FailurePolicy'
steps:
type: array
items:
$ref: '#/components/schemas/SimulationStep'
outputs:
type: array
items:
$ref: '#/components/schemas/SimulationOutput'
hasPendingApprovals:
type: boolean
description: Whether the plan contains approval gates
SimulationStep:
type: object
required: [id, templateId, kind, enabled, status, children]
properties:
id:
type: string
templateId:
type: string
kind:
type: string
enum: [Run, GateApproval, GatePolicy, Parallel, Map, Loop, Conditional, Unknown]
enabled:
type: boolean
status:
type: string
enum: [Pending, Skipped, RequiresApproval, RequiresPolicy, WillIterate, WillBranch]
statusReason:
type: string
uses:
type: string
description: Executor reference for run steps
approvalId:
type: string
gateMessage:
type: string
maxParallel:
type: integer
continueOnError:
type: boolean
children:
type: array
items:
$ref: '#/components/schemas/SimulationStep'
loopInfo:
$ref: '#/components/schemas/LoopInfo'
conditionalInfo:
$ref: '#/components/schemas/ConditionalInfo'
policyInfo:
$ref: '#/components/schemas/PolicyInfo'
LoopInfo:
type: object
description: Loop step simulation details
properties:
itemsExpression:
type: string
iterator:
type: string
index:
type: string
maxIterations:
type: integer
aggregationMode:
type: string
enum: [collect, merge, last, first, none]
ConditionalInfo:
type: object
description: Conditional step simulation details
properties:
branches:
type: array
items:
type: object
properties:
condition:
type: string
stepCount:
type: integer
elseStepCount:
type: integer
outputUnion:
type: boolean
PolicyInfo:
type: object
description: Policy gate simulation details
properties:
policyId:
type: string
policyVersion:
type: string
failureAction:
type: string
enum: [abort, warn, requestOverride, branch]
retryCount:
type: integer
SimulationOutput:
type: object
required: [name, type, requiresRuntimeValue]
properties:
name:
type: string
type:
type: string
requiresRuntimeValue:
type: boolean
pathExpression:
type: string
valueExpression:
type: string
CreateRunRequest:
type: object
required: [manifest]
properties:
runId:
type: string
description: Optional custom run ID (auto-generated if not provided)
manifest:
type: string
description: Task pack manifest in YAML format
inputs:
type: object
additionalProperties: true
description: Input values to provide to the task pack
tenantId:
type: string
description: Tenant identifier
RunStateResponse:
type: object
required: [runId, planHash, failurePolicy, createdAt, updatedAt, steps]
properties:
runId:
type: string
planHash:
type: string
pattern: '^sha256:[a-f0-9]{64}$'
failurePolicy:
$ref: '#/components/schemas/FailurePolicy'
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
steps:
type: array
items:
$ref: '#/components/schemas/RunStateStep'
RunStateStep:
type: object
required: [stepId, kind, enabled, continueOnError, status, attempts]
properties:
stepId:
type: string
kind:
type: string
enum: [Run, GateApproval, GatePolicy, Parallel, Map, Loop, Conditional, Unknown]
enabled:
type: boolean
continueOnError:
type: boolean
maxParallel:
type: integer
approvalId:
type: string
gateMessage:
type: string
status:
type: string
enum: [Pending, Running, Succeeded, Failed, Skipped]
attempts:
type: integer
lastTransitionAt:
type: string
format: date-time
nextAttemptAt:
type: string
format: date-time
statusReason:
type: string
FailurePolicy:
type: object
required: [maxAttempts, backoffSeconds, continueOnError]
properties:
maxAttempts:
type: integer
minimum: 1
backoffSeconds:
type: integer
minimum: 0
continueOnError:
type: boolean
RunLogEntry:
type: object
required: [timestamp, level, eventType, message]
description: |
Log entry returned in NDJSON stream. Each entry is a single JSON object
followed by a newline character.
properties:
timestamp:
type: string
format: date-time
level:
type: string
enum: [debug, info, warn, error]
eventType:
type: string
description: |
Event type identifier, e.g.:
- run.created, run.started, run.completed, run.failed, run.cancelled
- step.started, step.completed, step.failed, step.skipped
- gate.awaiting, gate.approved, gate.rejected
- run.schedule-failed, run.cancel-requested
message:
type: string
stepId:
type: string
metadata:
type: object
additionalProperties:
type: string
RunArtifact:
type: object
required: [name, type, status]
properties:
name:
type: string
type:
type: string
enum: [file, object]
sourcePath:
type: string
storedPath:
type: string
status:
type: string
enum: [pending, captured, failed]
notes:
type: string
capturedAt:
type: string
format: date-time
expressionJson:
type: string
description: JSON string of evaluated expression result for object outputs
ApprovalDecisionRequest:
type: object
required: [decision, planHash]
properties:
decision:
type: string
enum: [approved, rejected, expired]
planHash:
type: string
pattern: '^sha256:[a-f0-9]{64}$'
description: Plan hash to verify against (must match current run plan)
actorId:
type: string
description: Identifier of the approver (e.g., user:alice@example.com)
summary:
type: string
description: Optional comment explaining the decision
ApprovalDecisionResponse:
type: object
required: [status, resumed]
properties:
status:
type: string
enum: [approved, rejected, expired]
resumed:
type: boolean
description: Whether the run was resumed (true for approved decisions)
PlanErrorResponse:
type: object
required: [errors]
properties:
errors:
type: array
items:
type: object
required: [path, message]
properties:
path:
type: string
description: JSON path to the error location
message:
type: string
OpenApiMetadata:
type: object
required: [specUrl, version, etag]
properties:
specUrl:
type: string
description: URL to fetch the full OpenAPI spec
version:
type: string
description: API version
buildVersion:
type: string
description: Build version identifier
etag:
type: string
description: ETag for caching
signature:
type: string
description: Signature for spec verification
tags:
- name: Simulations
description: Task pack simulation without execution
- name: Runs
description: Pack run lifecycle management
- name: Logs
description: Run log streaming
- name: Artifacts
description: Run artifact management
- name: Approvals
description: Approval gate decisions
- name: Metadata
description: Service metadata and discovery

View File

@@ -0,0 +1,369 @@
# Authority Crypto Provider Contract
> **Status:** APPROVED
> **Version:** 1.0.0
> **Last Updated:** 2025-12-06
> **Owner:** Authority Core Guild
> **Unblocks:** AUTH-CRYPTO-90-001, SEC-CRYPTO-90-014, SCANNER-CRYPTO-90-001, ATTESTOR-CRYPTO-90-001
## Overview
This contract defines the Authority signing provider interface for StellaOps, enabling pluggable cryptographic backends including:
- **Software keys** (default) — ECDSA P-256/P-384, RSA, EdDSA
- **HSM integration** — PKCS#11, Cloud KMS (AWS, GCP, Azure)
- **Regional compliance** — CryptoPro GOST (R1), SM2/SM3 (CN), eIDAS (EU), FIPS 140-2
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Authority Crypto Provider │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ ISigningProvider Interface ││
│ │ ││
│ │ + Sign(data: byte[], keyId: string) → SignatureResult ││
│ │ + Verify(data: byte[], signature: byte[], keyId: string) → bool ││
│ │ + GetPublicKey(keyId: string) → PublicKeyInfo ││
│ │ + ListKeys(filter: KeyFilter) → KeyInfo[] ││
│ │ + CreateKey(spec: KeySpec) → KeyInfo ││
│ │ + RotateKey(keyId: string) → KeyInfo ││
│ │ + ExportJWKS(keyIds: string[]) → JWKS ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Software │ │ PKCS#11 │ │ Cloud KMS │ │
│ │ Provider │ │ Provider │ │ Provider │ │
│ │ │ │ │ │ │ │
│ │ • File keys │ │ • HSM │ │ • AWS KMS │ │
│ │ • Memory │ │ • SmartCard │ │ • GCP KMS │ │
│ │ • Vault │ │ • CryptoPro │ │ • Azure KV │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## 1. ISigningProvider Interface
### 1.1 Core Methods
```csharp
/// <summary>
/// Pluggable cryptographic signing provider for Authority service.
/// </summary>
public interface ISigningProvider
{
/// <summary>Provider identifier (e.g., "software", "pkcs11", "aws-kms")</summary>
string ProviderId { get; }
/// <summary>Supported algorithms by this provider</summary>
IReadOnlyList<string> SupportedAlgorithms { get; }
/// <summary>Sign data with the specified key</summary>
Task<SignatureResult> SignAsync(
byte[] data,
string keyId,
SigningOptions? options = null,
CancellationToken ct = default);
/// <summary>Verify a signature</summary>
Task<bool> VerifyAsync(
byte[] data,
byte[] signature,
string keyId,
CancellationToken ct = default);
/// <summary>Get public key information</summary>
Task<PublicKeyInfo> GetPublicKeyAsync(
string keyId,
CancellationToken ct = default);
/// <summary>List available keys</summary>
Task<IReadOnlyList<KeyInfo>> ListKeysAsync(
KeyFilter? filter = null,
CancellationToken ct = default);
/// <summary>Create a new key pair</summary>
Task<KeyInfo> CreateKeyAsync(
KeySpec spec,
CancellationToken ct = default);
/// <summary>Rotate a key (create new version)</summary>
Task<KeyInfo> RotateKeyAsync(
string keyId,
CancellationToken ct = default);
/// <summary>Export keys as JWKS for distributed verification</summary>
Task<JsonWebKeySet> ExportJwksAsync(
IEnumerable<string>? keyIds = null,
CancellationToken ct = default);
/// <summary>Import a public key for verification</summary>
Task<KeyInfo> ImportPublicKeyAsync(
byte[] keyData,
string format,
KeyMetadata? metadata = null,
CancellationToken ct = default);
}
```
### 1.2 Supporting Types
```csharp
public record SignatureResult(
byte[] Signature,
string Algorithm,
string KeyId,
string? KeyVersion,
DateTimeOffset Timestamp);
public record SigningOptions(
string? Algorithm = null,
bool IncludeTimestamp = true,
string? Nonce = null);
public record PublicKeyInfo(
string KeyId,
string Algorithm,
byte[] PublicKey,
string Format, // "PEM", "DER", "JWK"
string? Fingerprint,
DateTimeOffset? ExpiresAt);
public record KeyInfo(
string KeyId,
string Algorithm,
KeyState State,
DateTimeOffset CreatedAt,
DateTimeOffset? ExpiresAt,
string? CurrentVersion,
IReadOnlyDictionary<string, string>? Metadata);
public enum KeyState
{
Active,
Disabled,
PendingDeletion,
Deleted
}
public record KeySpec(
string Algorithm,
int? KeySize = null,
string? Purpose = null, // "signing", "attestation", "authority"
IReadOnlyDictionary<string, string>? Metadata = null,
DateTimeOffset? ExpiresAt = null);
public record KeyFilter(
string? Purpose = null,
KeyState? State = null,
string? Algorithm = null);
```
## 2. Supported Algorithms
### 2.1 Algorithm Registry
| Algorithm | OID | Key Size | Compliance | Provider Support |
|-----------|-----|----------|------------|------------------|
| **ES256** | 1.2.840.10045.4.3.2 | P-256 | FIPS, eIDAS | All |
| **ES384** | 1.2.840.10045.4.3.3 | P-384 | FIPS, eIDAS | All |
| **RS256** | 1.2.840.113549.1.1.11 | 2048+ | FIPS, eIDAS | All |
| **RS384** | 1.2.840.113549.1.1.12 | 2048+ | FIPS, eIDAS | All |
| **EdDSA** | 1.3.101.112 | Ed25519 | — | Software, some HSM |
| **PS256** | 1.2.840.113549.1.1.10 | 2048+ | FIPS | All |
| **GOST R 34.10-2012** | 1.2.643.7.1.1.1.1 | 256/512 | R1 | PKCS#11 (CryptoPro) |
| **SM2** | 1.2.156.10197.1.301 | 256 | CN | PKCS#11 |
### 2.2 Default Configuration
```yaml
# etc/authority.yaml
crypto:
provider: software # or: pkcs11, aws-kms, gcp-kms, azure-keyvault
software:
keys_path: /var/lib/stellaops/keys
default_algorithm: ES256
pkcs11:
library_path: /usr/lib/libpkcs11.so
slot_id: 0
pin_env: AUTHORITY_HSM_PIN
# For CryptoPro:
# library_path: /opt/cprocsp/lib/amd64/libcapi20.so
aws_kms:
region: us-east-1
key_alias_prefix: stellaops/
azure_keyvault:
vault_url: https://stellaops.vault.azure.net/
gcp_kms:
project: stellaops-prod
location: global
key_ring: attestation-keys
# Regional compliance overrides
compliance:
ru:
provider: pkcs11
algorithms: [GOST-R-34.10-2012-256, GOST-R-34.10-2012-512]
library_path: /opt/cprocsp/lib/amd64/libcapi20.so
cn:
provider: pkcs11
algorithms: [SM2]
```
## 3. JWKS Export Requirements
### 3.1 JWKS Endpoint
The Authority service MUST expose a JWKS endpoint for distributed verification:
```
GET /.well-known/jwks.json
```
Response format:
```json
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "base64url-encoded-x",
"y": "base64url-encoded-y",
"kid": "attestation-key-001",
"alg": "ES256",
"use": "sig",
"key_ops": ["verify"],
"x5t#S256": "sha256-fingerprint"
}
]
}
```
### 3.2 Key Rotation
When keys are rotated:
1. New key becomes `Active`, old key becomes `Disabled` (verification-only)
2. JWKS includes both keys during transition period
3. Old key removed after `rotation_grace_period` (default: 7 days)
4. All consuming services refresh JWKS on schedule or via webhook
### 3.3 Key Discovery Flow
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Scanner │ │ Authority │ │ Attestor │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ GET /jwks.json│ │
│───────────────>│ │
│<───────────────│ │
│ JWKS │ │
│ │ │
│ Sign(SBOM) │ │
│───────────────>│ │
│<───────────────│ │
│ Signature │ │
│ │ │
│ │ GET /jwks.json │
│ │<────────────────│
│ │────────────────>│
│ │ JWKS │
│ │ │
│ │ Verify(SBOM) │
│ │<────────────────│
│ │ ✓ Valid │
```
## 4. Provider Registration
### 4.1 Service Registration
```csharp
// Program.cs
services.AddAuthoritySigningProvider(options =>
{
options.Provider = configuration["Crypto:Provider"];
options.Configuration = configuration.GetSection("Crypto");
});
// Extension method
public static IServiceCollection AddAuthoritySigningProvider(
this IServiceCollection services,
Action<CryptoProviderOptions> configure)
{
var options = new CryptoProviderOptions();
configure(options);
return options.Provider switch
{
"software" => services.AddSingleton<ISigningProvider, SoftwareSigningProvider>(),
"pkcs11" => services.AddSingleton<ISigningProvider, Pkcs11SigningProvider>(),
"aws-kms" => services.AddSingleton<ISigningProvider, AwsKmsSigningProvider>(),
"gcp-kms" => services.AddSingleton<ISigningProvider, GcpKmsSigningProvider>(),
"azure-keyvault" => services.AddSingleton<ISigningProvider, AzureKeyVaultSigningProvider>(),
_ => throw new ArgumentException($"Unknown provider: {options.Provider}")
};
}
```
### 4.2 Regional Provider Registry
For multi-region deployments with compliance requirements:
```yaml
# Regional key registry
key_registry:
attestation-sbom:
default:
key_id: "stellaops/attestation-sbom-001"
algorithm: ES256
provider: aws-kms
ru:
key_id: "ru/attestation-sbom-gost"
algorithm: GOST-R-34.10-2012-256
provider: pkcs11
cn:
key_id: "cn/attestation-sbom-sm2"
algorithm: SM2
provider: pkcs11
```
## 5. Error Codes
| Code | Name | Description |
|------|------|-------------|
| `CRYPTO_001` | `KEY_NOT_FOUND` | Requested key does not exist |
| `CRYPTO_002` | `KEY_DISABLED` | Key is disabled and cannot sign |
| `CRYPTO_003` | `ALGORITHM_UNSUPPORTED` | Algorithm not supported by provider |
| `CRYPTO_004` | `HSM_UNAVAILABLE` | HSM/PKCS#11 device not available |
| `CRYPTO_005` | `SIGNATURE_FAILED` | Signing operation failed |
| `CRYPTO_006` | `VERIFICATION_FAILED` | Signature verification failed |
| `CRYPTO_007` | `KEY_EXPIRED` | Key has expired |
| `CRYPTO_008` | `COMPLIANCE_VIOLATION` | Algorithm not allowed by compliance profile |
## 6. Tasks Unblocked
This contract unblocks:
| Task ID | Description | Status |
|---------|-------------|--------|
| AUTH-CRYPTO-90-001 | Authority signing provider contract | ✅ UNBLOCKED |
| SEC-CRYPTO-90-014 | Security Guild crypto integration | ✅ UNBLOCKED |
| SCANNER-CRYPTO-90-001 | Scanner SBOM signing | ✅ UNBLOCKED |
| ATTESTOR-CRYPTO-90-001 | Attestor DSSE signing | ✅ UNBLOCKED |
## 7. Changelog
| Date | Version | Change |
|------|---------|--------|
| 2025-12-06 | 1.0.0 | Initial contract with interface, algorithms, JWKS, regional support |

View File

@@ -0,0 +1,425 @@
# Sealed Install Enforcement Contract
> **Status:** APPROVED
> **Version:** 1.0.0
> **Last Updated:** 2025-12-06
> **Owner:** AirGap Controller Guild
> **Unblocks:** TASKRUN-AIRGAP-57-001, TASKRUN-AIRGAP-58-001
## Overview
This contract defines the sealed install enforcement semantics for StellaOps air-gapped deployments. When a pack or task declares `sealed_install: true`, the Task Runner MUST refuse to execute if the environment is not properly sealed.
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Sealed Install Enforcement Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Task Pack │ │ Task Runner │ │ AirGap │ │
│ │ │────>│ │────>│ Controller │ │
│ │ sealed_ │ │ Enforcement │ │ │ │
│ │ install:true │ │ Check │ │ /status │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ Decision Matrix │ │
│ │ │ │
│ │ Pack: sealed Env: sealed │ │
│ │ ────────────── ──────────── │ │
│ │ true true → RUN │ │
│ │ true false → DENY │ │
│ │ false true → RUN │ │
│ │ false false → RUN │ │
│ └──────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## 1. Pack Declaration
### 1.1 Sealed Install Flag
Packs declare their sealed requirement in the pack manifest:
```json
{
"pack_id": "compliance-scan-airgap",
"version": "1.0.0",
"name": "Air-Gap Compliance Scanner",
"sealed_install": true,
"sealed_requirements": {
"min_bundle_version": "2025.10.0",
"max_advisory_staleness_hours": 168,
"require_time_anchor": true,
"allowed_offline_duration_hours": 720
}
}
```
### 1.2 Sealed Requirements Schema
```json
{
"type": "object",
"properties": {
"sealed_install": {
"type": "boolean",
"default": false,
"description": "If true, pack MUST run in sealed environment"
},
"sealed_requirements": {
"type": "object",
"properties": {
"min_bundle_version": {
"type": "string",
"description": "Minimum air-gap bundle version"
},
"max_advisory_staleness_hours": {
"type": "integer",
"minimum": 1,
"default": 168,
"description": "Maximum age of advisory data in hours"
},
"require_time_anchor": {
"type": "boolean",
"default": true,
"description": "Require valid time anchor"
},
"allowed_offline_duration_hours": {
"type": "integer",
"minimum": 1,
"default": 720,
"description": "Maximum allowed offline duration"
},
"require_signature_verification": {
"type": "boolean",
"default": true,
"description": "Require bundle signature verification"
}
}
}
}
}
```
## 2. Environment Detection
### 2.1 Sealed Mode Status API
The Task Runner queries the AirGap Controller to determine sealed status:
```
GET /api/v1/airgap/status
```
Response:
```json
{
"sealed": true,
"mode": "sealed",
"sealed_at": "2025-12-01T00:00:00Z",
"sealed_by": "ops-admin@company.com",
"bundle_version": "2025.10.0",
"bundle_digest": "sha256:abc123...",
"last_advisory_update": "2025-12-01T00:00:00Z",
"advisory_staleness_hours": 120,
"time_anchor": {
"timestamp": "2025-12-01T00:00:00Z",
"signature": "base64...",
"valid": true,
"expires_at": "2025-12-31T00:00:00Z"
},
"egress_blocked": true,
"network_policy": "deny-all"
}
```
### 2.2 Detection Heuristics
If the AirGap Controller is unavailable, the Task Runner uses fallback heuristics:
| Heuristic | Weight | Indicates |
|-----------|--------|-----------|
| No external DNS resolution | High | Sealed |
| Blocked ports 80, 443 | High | Sealed |
| AIRGAP_MODE=sealed env var | High | Sealed |
| /etc/stellaops/sealed file exists | Medium | Sealed |
| No internet connectivity | Medium | Sealed |
| Local-only registry configured | Low | Sealed |
Combined heuristic score threshold: **0.7** to consider environment sealed.
## 3. Enforcement Logic
### 3.1 Pre-Execution Check
```csharp
public sealed class SealedInstallEnforcer
{
public async Task<EnforcementResult> EnforceAsync(
TaskPack pack,
CancellationToken ct = default)
{
// If pack doesn't require sealed install, allow
if (!pack.SealedInstall)
{
return EnforcementResult.Allowed("Pack does not require sealed install");
}
// Get environment sealed status
var status = await _airgapController.GetStatusAsync(ct);
// Core check: environment must be sealed
if (!status.Sealed)
{
return EnforcementResult.Denied(
"SEALED_INSTALL_VIOLATION",
"Pack requires sealed environment but environment is not sealed",
new SealedInstallViolation
{
PackId = pack.PackId,
RequiredSealed = true,
ActualSealed = false,
Recommendation = "Activate sealed mode with: stella airgap seal"
});
}
// Check sealed requirements
if (pack.SealedRequirements != null)
{
var violations = ValidateRequirements(pack.SealedRequirements, status);
if (violations.Any())
{
return EnforcementResult.Denied(
"SEALED_REQUIREMENTS_VIOLATION",
"Sealed requirements not met",
violations);
}
}
return EnforcementResult.Allowed("Sealed install requirements satisfied");
}
private List<RequirementViolation> ValidateRequirements(
SealedRequirements requirements,
SealedModeStatus status)
{
var violations = new List<RequirementViolation>();
// Bundle version check
if (requirements.MinBundleVersion != null)
{
if (Version.Parse(status.BundleVersion) < Version.Parse(requirements.MinBundleVersion))
{
violations.Add(new RequirementViolation
{
Requirement = "min_bundle_version",
Expected = requirements.MinBundleVersion,
Actual = status.BundleVersion,
Message = $"Bundle version {status.BundleVersion} < required {requirements.MinBundleVersion}"
});
}
}
// Advisory staleness check
if (status.AdvisoryStalenessHours > requirements.MaxAdvisoryStalenessHours)
{
violations.Add(new RequirementViolation
{
Requirement = "max_advisory_staleness_hours",
Expected = requirements.MaxAdvisoryStalenessHours.ToString(),
Actual = status.AdvisoryStalenessHours.ToString(),
Message = $"Advisory data is {status.AdvisoryStalenessHours}h old, max allowed is {requirements.MaxAdvisoryStalenessHours}h"
});
}
// Time anchor check
if (requirements.RequireTimeAnchor && (status.TimeAnchor == null || !status.TimeAnchor.Valid))
{
violations.Add(new RequirementViolation
{
Requirement = "require_time_anchor",
Expected = "valid time anchor",
Actual = status.TimeAnchor?.Valid.ToString() ?? "missing",
Message = "Valid time anchor required but not present"
});
}
return violations;
}
}
```
### 3.2 Decision Matrix
| Pack `sealed_install` | Environment Sealed | Bundle Valid | Advisories Fresh | Result |
|-----------------------|-------------------|--------------|------------------|--------|
| `true` | `true` | `true` | `true` | ✅ RUN |
| `true` | `true` | `true` | `false` | ⚠️ WARN + RUN (if within grace) |
| `true` | `true` | `false` | * | ❌ DENY |
| `true` | `false` | * | * | ❌ DENY |
| `false` | `true` | * | * | ✅ RUN |
| `false` | `false` | * | * | ✅ RUN |
### 3.3 Grace Period Handling
For advisory staleness, a grace period can be configured:
```yaml
# etc/taskrunner.yaml
enforcement:
sealed_install:
staleness_grace_period_hours: 24
staleness_warning_threshold_hours: 120
deny_on_staleness: true # or false for warn-only
```
## 4. Refusal Semantics
### 4.1 Error Response
When enforcement denies execution:
```json
{
"error": {
"code": "SEALED_INSTALL_VIOLATION",
"message": "Pack requires sealed environment but environment is not sealed",
"details": {
"pack_id": "compliance-scan-airgap",
"pack_version": "1.0.0",
"sealed_install_required": true,
"environment_sealed": false,
"violations": [],
"recommendation": "Activate sealed mode with: stella airgap seal"
}
},
"status": "rejected",
"rejected_at": "2025-12-06T10:00:00Z"
}
```
### 4.2 CLI Exit Codes
| Code | Name | Description |
|------|------|-------------|
| 40 | `SEALED_INSTALL_VIOLATION` | Pack requires sealed but environment is not |
| 41 | `BUNDLE_VERSION_VIOLATION` | Bundle version below minimum |
| 42 | `ADVISORY_STALENESS_VIOLATION` | Advisory data too stale |
| 43 | `TIME_ANCHOR_VIOLATION` | Time anchor missing or invalid |
| 44 | `SIGNATURE_VERIFICATION_VIOLATION` | Bundle signature verification failed |
### 4.3 Audit Logging
All enforcement decisions are logged:
```json
{
"event_type": "sealed_install_enforcement",
"timestamp": "2025-12-06T10:00:00Z",
"pack_id": "compliance-scan-airgap",
"pack_version": "1.0.0",
"decision": "denied",
"reason": "SEALED_INSTALL_VIOLATION",
"environment": {
"sealed": false,
"bundle_version": null,
"advisory_staleness_hours": null
},
"user": "task-runner-service",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
## 5. Integration Points
### 5.1 Task Runner Integration
```csharp
// In TaskRunner execution pipeline
public async Task<TaskResult> ExecuteAsync(TaskPack pack, TaskContext context)
{
// Pre-execution enforcement
var enforcement = await _sealedInstallEnforcer.EnforceAsync(pack);
if (!enforcement.Allowed)
{
await _auditLogger.LogEnforcementDenialAsync(pack, enforcement);
return TaskResult.Rejected(enforcement);
}
// Continue with execution
return await _executor.ExecuteAsync(pack, context);
}
```
### 5.2 CLI Integration
```bash
# Check sealed status before running pack
$ stella pack run compliance-scan-airgap
Error: Sealed install violation
Pack 'compliance-scan-airgap' requires a sealed environment.
Current environment:
Sealed: false
To resolve:
1. Import an air-gap bundle: stella airgap import <bundle.tar.gz>
2. Activate sealed mode: stella airgap seal
3. Verify status: stella airgap status
Exit code: 40
```
## 6. Configuration
### 6.1 Task Runner Configuration
```yaml
# etc/taskrunner.yaml
enforcement:
sealed_install:
enabled: true
# Staleness handling
staleness_grace_period_hours: 24
staleness_warning_threshold_hours: 120
deny_on_staleness: true
# Fallback detection
use_heuristic_detection: true
heuristic_threshold: 0.7
# Logging
log_all_decisions: true
audit_retention_days: 365
```
### 6.2 Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `AIRGAP_MODE` | Force sealed mode detection | — |
| `AIRGAP_CONTROLLER_URL` | AirGap controller endpoint | `http://localhost:8080` |
| `SEALED_INSTALL_BYPASS` | Bypass enforcement (dev only) | `false` |
## 7. Tasks Unblocked
This contract unblocks:
| Task ID | Description | Status |
|---------|-------------|--------|
| TASKRUN-AIRGAP-57-001 | Sealed install enforcement contract | ✅ UNBLOCKED |
| TASKRUN-AIRGAP-58-001 | Sealed install CLI integration | ✅ UNBLOCKED |
## 8. Changelog
| Date | Version | Change |
|------|---------|--------|
| 2025-12-06 | 1.0.0 | Initial contract with enforcement logic, decision matrix, CLI integration |

View File

@@ -38,28 +38,28 @@
## Delivery Tracker
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- |
| P1 | PREP-SCANNER-BUN-001-DESIGN-DOC | TODO | Due TBD · Accountable: Bun Analyzer Guild | Bun Analyzer Guild | Document Bun analyzer design at `docs/modules/scanner/prep/bun-analyzer-design.md` covering lockfile schema, discovery heuristics, evidence model, and CLI/WebService contract. |
| 1 | SCANNER-BUN-001 | TODO | Await P1 | Bun Analyzer Guild | Create project scaffold: `StellaOps.Scanner.Analyzers.Lang.Bun.csproj`, plugin manifest (`manifest.json`), and `BunAnalyzerPlugin` implementing `ILanguageAnalyzerPlugin`. |
| 2 | SCANNER-BUN-002 | TODO | Depends on task 1 | Bun Analyzer Guild | Implement `BunProjectDiscoverer`: identify candidate roots by presence of `package.json` + (`bun.lock` \| `bun.lockb` \| `bunfig.toml` \| `node_modules/.bun/`). |
| 3 | SCANNER-BUN-003 | TODO | Depends on task 2 | Bun Analyzer Guild | Implement `BunInputNormalizer`: classify each root as installed-path (node_modules exists) or lockfile-path (`bun.lock` only) or unsupported (`bun.lockb` only). |
| 4 | SCANNER-BUN-004 | TODO | Depends on task 3 | Bun Analyzer Guild | Implement `bun.lockb` unsupported handler: emit remediation finding with migration command (`bun install --save-text-lockfile`). |
| 5 | SCANNER-BUN-005 | TODO | Depends on task 3 | Bun Analyzer Guild | Implement `BunLockParser`: tolerant JSONC parser for `bun.lock` text format; extract (name, version, resolved, integrity) tuples. |
| 6 | SCANNER-BUN-006 | TODO | Depends on task 5 | Bun Analyzer Guild | Implement dev/prod dependency filtering for lockfile path; expose `include_dev` configuration option. |
| 7 | SCANNER-BUN-007 | TODO | Depends on task 3 | Bun Analyzer Guild | Implement `BunInstalledCollector`: traverse `node_modules/**/package.json` and `node_modules/.bun/**/package.json` with symlink-safe walker. |
| 8 | SCANNER-BUN-008 | TODO | Depends on task 7 | Bun Analyzer Guild | Implement symlink safety: follow symlinks only within root prefix; maintain visited inode/realpath set; record logical + real paths for evidence. |
| 9 | SCANNER-BUN-009 | TODO | Depends on task 7 | Bun Analyzer Guild | Extract package metadata from `package.json`: name, version, private flag; attach lockfile evidence (resolved, integrity) when available. |
| 10 | SCANNER-BUN-010 | TODO | Depends on tasks 5, 9 | Bun Analyzer Guild | Implement `BunPackageNormalizer`: deduplicate by (name, version); accumulate occurrence paths for traceability; emit `LanguageComponentRecord`. |
| 11 | SCANNER-BUN-011 | TODO | Depends on task 10 | Bun Analyzer Guild | PURL generation: emit `pkg:npm/<name>@<version>` with correct scoped-package encoding (`@scope/pkg``%40scope/pkg`). |
| 12 | SCANNER-BUN-012 | TODO | Depends on task 10 | Bun Analyzer Guild | Evidence emission: attach `LanguageComponentEvidence` with kind (File/Metadata), source (`node_modules`/`bun.lock`), locator (path), and optional sha256. |
| 13 | SCANNER-BUN-013 | TODO | Depends on task 12 | Bun Analyzer Guild | Assemble `BunLanguageAnalyzer` orchestrating discovery → input normalization → collection → normalization → emit via `LanguageComponentWriter`. |
| 14 | SCANNER-BUN-014 | TODO | Depends on task 13 | Bun Analyzer Guild | Performance guards: implement max-files-per-root cap, max-symlink-depth limit, prefix pruning to avoid full image traversal. |
| 15 | SCANNER-BUN-015 | TODO | Depends on task 13 | QA Guild | Create test project `StellaOps.Scanner.Analyzers.Lang.Bun.Tests` with golden fixture harness using `LanguageAnalyzerTestHarness.AssertDeterministicAsync`. |
| 16 | SCANNER-BUN-016 | TODO | Depends on task 15 | QA Guild | Fixture: Standard Bun install (hoisted/default linker) with `node_modules` and `bun.lock`; verify installed inventory path. |
| 17 | SCANNER-BUN-017 | TODO | Depends on task 15 | QA Guild | Fixture: Isolated linker install (`bun install --linker isolated`) with packages under `node_modules/.bun/`; verify `.bun/` traversal. |
| 18 | SCANNER-BUN-018 | TODO | Depends on task 15 | QA Guild | Fixture: Lockfile-only image (no `node_modules`); verify lockfile inventory path and dev/prod filtering. |
| 19 | SCANNER-BUN-019 | TODO | Depends on task 15 | QA Guild | Fixture: Binary lockfile only (`bun.lockb`); verify unsupported remediation message emitted. |
| 20 | SCANNER-BUN-020 | TODO | Depends on task 15 | QA Guild | Fixture: Monorepo/workspaces with multiple `package.json` under single lock; verify workspace member handling. |
| 21 | SCANNER-BUN-021 | TODO | Depends on task 15 | QA Guild | Fixture: Symlink corner cases (verify no traversal outside root, no infinite loops, both logical/real paths in evidence). |
| P1 | PREP-SCANNER-BUN-001-DESIGN-DOC | DONE (2025-12-06) | Design doc at `docs/modules/scanner/prep/bun-analyzer-design.md` | Bun Analyzer Guild | Document Bun analyzer design at `docs/modules/scanner/prep/bun-analyzer-design.md` covering lockfile schema, discovery heuristics, evidence model, and CLI/WebService contract. |
| 1 | SCANNER-BUN-001 | DONE (2025-12-06) | Scaffold at `StellaOps.Scanner.Analyzers.Lang.Bun` | Bun Analyzer Guild | Create project scaffold: `StellaOps.Scanner.Analyzers.Lang.Bun.csproj`, plugin manifest (`manifest.json`), and `BunAnalyzerPlugin` implementing `ILanguageAnalyzerPlugin`. |
| 2 | SCANNER-BUN-002 | DONE (2025-12-06) | `BunProjectDiscoverer.cs` implemented | Bun Analyzer Guild | Implement `BunProjectDiscoverer`: identify candidate roots by presence of `package.json` + (`bun.lock` \| `bun.lockb` \| `bunfig.toml` \| `node_modules/.bun/`). |
| 3 | SCANNER-BUN-003 | DONE (2025-12-06) | `BunInputNormalizer.cs` implemented | Bun Analyzer Guild | Implement `BunInputNormalizer`: classify each root as installed-path (node_modules exists) or lockfile-path (`bun.lock` only) or unsupported (`bun.lockb` only). |
| 4 | SCANNER-BUN-004 | DONE (2025-12-06) | `EmitBinaryLockfileRemediation` in BunLanguageAnalyzer | Bun Analyzer Guild | Implement `bun.lockb` unsupported handler: emit remediation finding with migration command (`bun install --save-text-lockfile`). |
| 5 | SCANNER-BUN-005 | DONE (2025-12-06) | `BunLockParser.cs` with JSONC support | Bun Analyzer Guild | Implement `BunLockParser`: tolerant JSONC parser for `bun.lock` text format; extract (name, version, resolved, integrity) tuples. |
| 6 | SCANNER-BUN-006 | DONE (2025-12-06) | `IncludeDev` in BunInputClassification | Bun Analyzer Guild | Implement dev/prod dependency filtering for lockfile path; expose `include_dev` configuration option. |
| 7 | SCANNER-BUN-007 | DONE (2025-12-06) | `BunInstalledCollector.cs` implemented | Bun Analyzer Guild | Implement `BunInstalledCollector`: traverse `node_modules/**/package.json` and `node_modules/.bun/**/package.json` with symlink-safe walker. |
| 8 | SCANNER-BUN-008 | DONE (2025-12-06) | Symlink safety in BunInstalledCollector | Bun Analyzer Guild | Implement symlink safety: follow symlinks only within root prefix; maintain visited inode/realpath set; record logical + real paths for evidence. |
| 9 | SCANNER-BUN-009 | DONE (2025-12-06) | `TryParsePackage` in BunInstalledCollector | Bun Analyzer Guild | Extract package metadata from `package.json`: name, version, private flag; attach lockfile evidence (resolved, integrity) when available. |
| 10 | SCANNER-BUN-010 | DONE (2025-12-06) | `BunPackageNormalizer.cs` implemented | Bun Analyzer Guild | Implement `BunPackageNormalizer`: deduplicate by (name, version); accumulate occurrence paths for traceability; emit `LanguageComponentRecord`. |
| 11 | SCANNER-BUN-011 | DONE (2025-12-06) | `BuildPurl` in BunPackage | Bun Analyzer Guild | PURL generation: emit `pkg:npm/<name>@<version>` with correct scoped-package encoding (`@scope/pkg``%40scope/pkg`). |
| 12 | SCANNER-BUN-012 | DONE (2025-12-06) | `CreateEvidence` in BunPackage | Bun Analyzer Guild | Evidence emission: attach `LanguageComponentEvidence` with kind (File/Metadata), source (`node_modules`/`bun.lock`), locator (path), and optional sha256. |
| 13 | SCANNER-BUN-013 | DONE (2025-12-06) | `BunLanguageAnalyzer.cs` orchestration complete | Bun Analyzer Guild | Assemble `BunLanguageAnalyzer` orchestrating discovery → input normalization → collection → normalization → emit via `LanguageComponentWriter`. |
| 14 | SCANNER-BUN-014 | DONE (2025-12-06) | MaxFilesPerRoot/MaxSymlinkDepth guards in place | Bun Analyzer Guild | Performance guards: implement max-files-per-root cap, max-symlink-depth limit, prefix pruning to avoid full image traversal. |
| 15 | SCANNER-BUN-015 | DONE (2025-12-06) | Test project with 6 test methods | QA Guild | Create test project `StellaOps.Scanner.Analyzers.Lang.Bun.Tests` with golden fixture harness using `LanguageAnalyzerTestHarness.AssertDeterministicAsync`. |
| 16 | SCANNER-BUN-016 | DONE (2025-12-06) | `StandardInstallProducesDeterministicOutputAsync` test | QA Guild | Fixture: Standard Bun install (hoisted/default linker) with `node_modules` and `bun.lock`; verify installed inventory path. |
| 17 | SCANNER-BUN-017 | DONE (2025-12-06) | `IsolatedLinkerInstallIsParsedAsync` test | QA Guild | Fixture: Isolated linker install (`bun install --linker isolated`) with packages under `node_modules/.bun/`; verify `.bun/` traversal. |
| 18 | SCANNER-BUN-018 | DONE (2025-12-06) | `LockfileOnlyIsParsedAsync` test | QA Guild | Fixture: Lockfile-only image (no `node_modules`); verify lockfile inventory path and dev/prod filtering. |
| 19 | SCANNER-BUN-019 | DONE (2025-12-06) | `BinaryLockfileEmitsRemediationAsync` test | QA Guild | Fixture: Binary lockfile only (`bun.lockb`); verify unsupported remediation message emitted. |
| 20 | SCANNER-BUN-020 | DONE (2025-12-06) | `WorkspacesAreParsedAsync` test | QA Guild | Fixture: Monorepo/workspaces with multiple `package.json` under single lock; verify workspace member handling. |
| 21 | SCANNER-BUN-021 | DONE (2025-12-06) | `SymlinkSafetyIsEnforcedAsync` test | QA Guild | Fixture: Symlink corner cases (verify no traversal outside root, no infinite loops, both logical/real paths in evidence). |
| 22 | SCANNER-BUN-022 | TODO | Depends on task 14 | CLI Guild | Implement `stellaops-cli bun inspect` verb: display Bun package inventory for local root or scan ID; wire into `CommandFactory`. |
| 23 | SCANNER-BUN-023 | TODO | Depends on task 22 | CLI Guild | Implement `stellaops-cli bun resolve` verb: resolve Bun packages by scan ID, digest, or image reference with JSON/table output. |
| 24 | SCANNER-BUN-024 | TODO | Depends on task 23 | CLI Guild | Add CLI unit tests for Bun verbs (`CommandFactoryTests`, JSON output assertions); update CLI help text and golden outputs. |
@@ -72,6 +72,7 @@
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-06 | Completed P1 through 21 (Waves AD): Created design doc at `docs/modules/scanner/prep/bun-analyzer-design.md`. Verified core analyzer implementation in `StellaOps.Scanner.Analyzers.Lang.Bun`: BunAnalyzerPlugin, BunLanguageAnalyzer, BunProjectDiscoverer, BunInputNormalizer, BunLockParser (JSONC with git/tarball/workspace source detection), BunInstalledCollector (symlink-safe), BunPackageNormalizer, BunPackage (PURL + evidence). Performance guards (MaxFilesPerRoot=50000, MaxSymlinkDepth=10) in place. Test project with 6 golden fixture tests. Build succeeds. | Implementer |
| 2025-12-05 | Sprint file created from product advisory; 29 tasks across 6 waves (AF) covering core analyzer, testing, CLI/WebService/Worker integration, and docs. | Planning |
## Decisions & Risks

View File

@@ -25,9 +25,9 @@
| 2 | TASKRUN-AIRGAP-56-002 | DONE (2025-12-03) | Helper delivered; downstream AIRGAP-57/58 await controller/importer bundle specs. | Task Runner Guild · AirGap Importer Guild | Add helper steps for bundle ingestion (checksum verification, staging to object store) with deterministic outputs. |
| 3 | TASKRUN-AIRGAP-57-001 | BLOCKED (2025-11-30) | Depends on 56-002; awaiting sealed-install enforcement contract. | Task Runner Guild · AirGap Controller Guild | Refuse to execute plans when environment sealed=false but declared sealed install; emit advisory timeline events. |
| 4 | TASKRUN-AIRGAP-58-001 | BLOCKED (2025-11-30) | Depends on 57-001. | Task Runner Guild · Evidence Locker Guild | Capture bundle import job transcripts, hashed inputs/outputs into portable evidence bundles. |
| 5 | TASKRUN-42-001 | TODO | ✅ Control-flow contract at `docs/schemas/taskpack-control-flow.schema.json`; proceed with execution engine upgrades (loops/conditionals/maxParallel), simulation mode, policy gate integration, deterministic failure recovery. | Task Runner Guild (`src/TaskRunner/StellaOps.TaskRunner`) | Execution engine enhancements + simulation API/CLI. |
| 6 | TASKRUN-OAS-61-001 | TODO | ✅ Control-flow contract published 2025-12-06; proceed with OAS freeze. | Task Runner Guild · API Contracts Guild | Document TaskRunner APIs (pack runs, logs, approvals) with streaming schemas/examples. |
| 7 | TASKRUN-OAS-61-002 | TODO | Depends on 61-001; ready once OAS documented. | Task Runner Guild | Expose `GET /.well-known/openapi` returning signed spec metadata, build version, ETag. |
| 5 | TASKRUN-42-001 | DONE (2025-12-06) | Implemented Loop/Conditional step kinds, extended execution graph/simulation engine, added manifest/planner/validator support, 128 tests passing. | Task Runner Guild (`src/TaskRunner/StellaOps.TaskRunner`) | Execution engine enhancements + simulation API/CLI. |
| 6 | TASKRUN-OAS-61-001 | DONE (2025-12-06) | Created `docs/api/taskrunner-openapi.yaml` with full API documentation including streaming logs (NDJSON), loop/conditional/policy gate schemas. | Task Runner Guild · API Contracts Guild | Document TaskRunner APIs (pack runs, logs, approvals) with streaming schemas/examples. |
| 7 | TASKRUN-OAS-61-002 | TODO | ✅ 61-001 DONE; endpoint already implemented in Program.cs; needs signing integration. | Task Runner Guild | Expose `GET /.well-known/openapi` returning signed spec metadata, build version, ETag. |
| 8 | TASKRUN-OAS-62-001 | TODO | Depends on 61-002. | Task Runner Guild · SDK Generator Guild | SDK examples for pack run lifecycle; streaming log helpers; paginator wrappers. |
| 9 | TASKRUN-OAS-63-001 | TODO | Depends on 62-001. | Task Runner Guild · API Governance Guild | Sunset/deprecation headers + notifications for legacy pack APIs. |
| 10 | TASKRUN-OBS-50-001 | DONE (2025-11-25) | Telemetry core adoption. | Task Runner Guild | Add telemetry core in host + worker; spans/logs include `trace_id`, `tenant_id`, `run_id`, scrubbed transcripts. |
@@ -56,6 +56,8 @@
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-06 | TASKRUN-OAS-61-001 DONE: Created `docs/api/taskrunner-openapi.yaml` OpenAPI 3.1 specification documenting all TaskRunner WebService APIs: POST /v1/task-runner/simulations (simulate task pack), POST /v1/task-runner/runs (create run), GET /v1/task-runner/runs/{runId} (get state), GET /v1/task-runner/runs/{runId}/logs (NDJSON streaming), GET /v1/task-runner/runs/{runId}/artifacts (list artifacts), POST /v1/task-runner/runs/{runId}/approvals/{approvalId} (apply decision), POST /v1/task-runner/runs/{runId}/cancel (cancel run), GET /.well-known/openapi (metadata). Includes LoopInfo, ConditionalInfo, PolicyInfo schemas for new control-flow steps. Examples provided for all endpoints. | Implementer |
| 2025-12-06 | TASKRUN-42-001 DONE: Extended `PackRunStepKind` enum with `Loop` and `Conditional`. Added `PackRunLoopConfig`, `PackRunConditionalConfig`, `PackRunPolicyGateConfig` record types to `PackRunExecutionGraph.cs`. Updated `PackRunExecutionGraphBuilder` to extract loop/conditional/policy gate configs. Extended `PackRunSimulationEngine` and `PackRunSimulationModels.cs` with `WillIterate`/`WillBranch` statuses and simulation info records. Added `TaskPackLoopStep`, `TaskPackConditionalStep` manifest models. Updated `TaskPackPlanner` with `BuildLoopStep`/`BuildConditionalStep` methods. Updated `TaskPackManifestValidator` for loop/conditional validation. Added 3 new simulation tests (loop, conditional, policy gate); 128 total tests passing. | Implementer |
| 2025-12-06 | TASKRUN-OBS-53-001 DONE: Created `PackRunEvidenceSnapshot.cs` domain model with Merkle root computation for hash chain integrity. Created `IPackRunEvidenceSnapshotService.cs` with service for capturing run completion, step execution, approval decisions, and policy evaluations. Created `IPackRunEvidenceStore.cs` with InMemoryPackRunEvidenceStore for testing. Created `IPackRunRedactionGuard.cs` with PackRunRedactionGuard for sensitive data redaction (bearer tokens, passwords, emails, identities). Added 29 comprehensive tests in `PackRunEvidenceSnapshotTests.cs`. Build verified (0 errors), all tests passing. | Implementer |
| 2025-12-06 | TASKRUN-OBS-52-001 DONE: Created `PackRunTimelineEvent.cs` domain model per timeline-event.schema.json with event types (pack.started, pack.step.completed, pack.failed, etc.). Created `PackRunTimelineEventEmitter.cs` with retry logic and deterministic batch ordering. Created `IPackRunTimelineEventSink.cs` with InMemoryPackRunTimelineEventSink for testing. Added 32 comprehensive tests in `PackRunTimelineEventTests.cs`. Build verified (0 errors), all tests passing. | Implementer |
| 2025-12-05 | **OBS Unblocked:** TASKRUN-OBS-52-001 and TASKRUN-OBS-53-001 changed from BLOCKED to TODO. Root blocker resolved: `timeline-event.schema.json` created 2025-12-04 per BLOCKED_DEPENDENCY_TREE.md Section 8.3. | Implementer |

View File

@@ -0,0 +1,282 @@
# Bun Analyzer Design — PREP-SCANNER-BUN-001-DESIGN-DOC
Status: Draft (2025-12-06)
Owners: Bun Analyzer Guild · Scanner Guild
Scope: Bun package manager analyzer for npm-ecosystem vulnerability scanning in container filesystems.
## Overview
The Bun analyzer extracts npm-ecosystem package inventory from Bun-managed JavaScript/TypeScript projects. Bun uses npm-compatible package.json and produces packages in `node_modules`, making it similar to the Node analyzer but with distinct lockfile formats and installation structures.
## Supported Artifacts
### Lockfile Formats
| Format | Extension | Status | Notes |
|--------|-----------|--------|-------|
| Text lockfile | `bun.lock` | Supported | JSONC format with package metadata |
| Binary lockfile | `bun.lockb` | Unsupported | Undocumented binary format; emit migration guidance |
### Installation Structures
| Structure | Discovery Pattern | Notes |
|-----------|-------------------|-------|
| Default (hoisted) | `node_modules/**/package.json` | Standard flat structure |
| Isolated linker | `node_modules/.bun/**/package.json` | Symlink-heavy, requires safe traversal |
## Discovery Heuristics
### Project Root Detection
A directory is considered a Bun project root when:
1. `package.json` exists, AND
2. One or more of:
- `bun.lock` exists (text lockfile)
- `bun.lockb` exists (binary lockfile — triggers unsupported message)
- `bunfig.toml` exists (Bun configuration)
- `node_modules/.bun/` exists (isolated linker marker)
### Input Classification
```
BunInputNormalizer classifies each root:
├── InstalledPath: node_modules exists → traverse installed packages
├── LockfilePath: bun.lock only (no node_modules) → parse lockfile
└── Unsupported: bun.lockb only → emit remediation finding
```
## Lockfile Schema (`bun.lock`)
The `bun.lock` text format is a JSONC variant (JSON with comments and trailing commas):
```jsonc
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "my-app",
"dependencies": {
"lodash": "^4.17.21"
}
}
},
"packages": {
"lodash@4.17.21": {
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57...",
"dependencies": {}
}
}
}
```
### Extracted Fields
| Field | Source | Usage |
|-------|--------|-------|
| name | packages key (before `@version`) | PURL name component |
| version | packages key (after `@`) | PURL version component |
| resolved | packages[].resolved | Evidence locator |
| integrity | packages[].integrity | Evidence hash (sha512/sha256) |
## Evidence Model
### LanguageComponentEvidence Structure
```csharp
record BunPackageEvidence(
LanguageEvidenceKind Kind, // File | Metadata | Lockfile
string Source, // "node_modules" | "bun.lock"
string Locator, // File path or registry URL
string? Content, // package.json content (if File)
string? Sha256); // Content hash
```
### Evidence Collection Matrix
| Source | Kind | Locator | Content | Hash |
|--------|------|---------|---------|------|
| `node_modules/**/package.json` | File | Relative path | JSON content | sha256 of content |
| `bun.lock` | Lockfile | `bun.lock:packages[name@version]` | null | null |
| Registry resolution | Metadata | resolved URL | null | integrity value |
## PURL Generation
Bun packages are npm packages with `bun` package manager qualifier:
```
pkg:npm/<name>@<version>?package_manager=bun
```
### Scoped Package Encoding
| Raw Name | Encoded PURL |
|----------|--------------|
| `lodash` | `pkg:npm/lodash@4.17.21?package_manager=bun` |
| `@types/node` | `pkg:npm/%40types/node@20.10.0?package_manager=bun` |
| `@org/pkg` | `pkg:npm/%40org/pkg@1.0.0?package_manager=bun` |
## Symlink Safety
Bun's isolated linker creates symlink-heavy structures. Safety requirements:
1. **Prefix Containment**: Only follow symlinks that resolve within root path
2. **Cycle Detection**: Maintain visited inode/realpath set
3. **Path Recording**: Record both logical path (symlink) and real path (target)
4. **Depth Limit**: Cap symlink depth at 32 levels (configurable)
```csharp
record SymlinkSafetyContext(
string RootPrefix,
HashSet<(long Inode, long Device)> VisitedInodes,
int MaxDepth = 32);
```
## Performance Guards
| Guard | Default | Rationale |
|-------|---------|-----------|
| max-files-per-root | 50,000 | Prevent full image traversal |
| max-symlink-depth | 32 | Avoid infinite loops in malformed structures |
| prefix-pruning | enabled | Skip paths outside expected locations |
| timeout-per-root | 60s | Bound analysis time per project |
## CLI Contract
### `stellaops-cli bun inspect`
Display Bun package inventory for local root or scan ID:
```bash
# Local analysis
stellaops-cli bun inspect /path/to/project
# Remote scan lookup
stellaops-cli bun inspect --scan-id abc123
```
Output formats: `--output json|table|ndjson`
### `stellaops-cli bun resolve`
Resolve Bun packages by scan ID, digest, or image reference:
```bash
stellaops-cli bun resolve --scan-id abc123 --package lodash
stellaops-cli bun resolve --digest sha256:abc... --format json
```
## WebService Contract
### `GET /api/scans/{scanId}/bun-packages`
Returns inventory of Bun packages for a completed scan.
Query parameters:
- `page`, `pageSize`: Pagination
- `name`: Filter by package name (prefix match)
- `scope`: Filter by npm scope
Response schema:
```json
{
"scanId": "abc123",
"analyzer": "bun",
"packages": [
{
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21?package_manager=bun",
"evidence": [
{
"kind": "File",
"source": "node_modules",
"locator": "node_modules/lodash/package.json",
"sha256": "abc..."
}
]
}
],
"total": 150,
"page": 1,
"pageSize": 50
}
```
## Unsupported Artifact Handling
### Binary Lockfile (`bun.lockb`)
When only `bun.lockb` is present (no `bun.lock` or `node_modules`):
1. Emit remediation finding with severity `Info`
2. Provide migration command: `bun install --save-text-lockfile`
3. Skip package enumeration (no false negatives from partial binary parse)
```csharp
record BunLockbUnsupportedFinding(
string Path,
string RemediationCommand = "bun install --save-text-lockfile",
string Reason = "Binary lockfile format is undocumented and unstable");
```
## Test Fixtures
| Fixture | Purpose | Validation |
|---------|---------|------------|
| `hoisted-install` | Standard Bun install with `node_modules` + `bun.lock` | Installed inventory path |
| `isolated-linker` | `bun install --linker isolated` structure | `.bun/` traversal |
| `lockfile-only` | No `node_modules`, only `bun.lock` | Lockfile inventory, dev/prod filtering |
| `binary-lockfile-only` | Only `bun.lockb` present | Unsupported remediation message |
| `monorepo-workspaces` | Multiple `package.json` under single lock | Workspace member handling |
| `symlink-cycles` | Malformed structure with cycles | Cycle detection, no infinite loops |
## Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `STELLAOPS_BUN_MAX_FILES` | 50000 | Max files per root |
| `STELLAOPS_BUN_MAX_SYMLINK_DEPTH` | 32 | Max symlink traversal depth |
| `STELLAOPS_BUN_INCLUDE_DEV` | true | Include dev dependencies |
| `STELLAOPS_BUN_TIMEOUT_SECONDS` | 60 | Per-root analysis timeout |
### appsettings.json
```json
{
"Scanner": {
"Analyzers": {
"Bun": {
"MaxFilesPerRoot": 50000,
"MaxSymlinkDepth": 32,
"IncludeDevDependencies": true,
"TimeoutSeconds": 60
}
}
}
}
```
## Determinism Requirements
1. **Sorted Output**: Packages ordered by `(name, version)` tuple
2. **Stable IDs**: Component keys computed as `sha256(analyzerId + purl)`
3. **Reproducible Evidence**: Evidence ordered by `(kind, source, locator)`
4. **No Timestamps**: Evidence does not include file modification times
5. **Canonical Paths**: All paths normalized (forward slashes, no trailing slash)
## Open Decisions
1. **Dev Dependency Default**: Currently `include_dev: true` for lockfile-only scans — confirm with Policy Guild
2. **Workspace Handling**: Whether to emit separate inventory per workspace or merged — await monorepo fixture results
3. **PURL Qualifier**: Using `?package_manager=bun` vs no qualifier — coordinate with Concelier linkset resolution
## Handoff
This document serves as the PREP artifact for PREP-SCANNER-BUN-001-DESIGN-DOC. Update upon:
- Policy Guild confirmation of dev dependency defaults
- Concelier Guild decision on PURL qualifier handling
- Fixture suite completion revealing edge cases

View File

@@ -0,0 +1,502 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/export-profiles.schema.json",
"title": "StellaOps Export Profiles Schema",
"description": "Schema for CLI export profiles, scheduling, and distribution configuration. Unblocks CLI-EXPORT-35-001.",
"type": "object",
"definitions": {
"ExportProfile": {
"type": "object",
"required": ["profile_id", "name", "format", "created_at"],
"properties": {
"profile_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the export profile"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"description": "Human-readable profile name"
},
"description": {
"type": "string",
"maxLength": 512
},
"format": {
"$ref": "#/definitions/ExportFormat"
},
"filters": {
"$ref": "#/definitions/ExportFilters"
},
"schedule": {
"$ref": "#/definitions/ExportSchedule"
},
"distribution": {
"$ref": "#/definitions/Distribution"
},
"retention": {
"$ref": "#/definitions/RetentionPolicy"
},
"signing": {
"$ref": "#/definitions/SigningConfig"
},
"metadata": {
"type": "object",
"additionalProperties": true
},
"enabled": {
"type": "boolean",
"default": true
},
"tenant_id": {
"type": "string",
"format": "uuid"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
}
}
},
"ExportFormat": {
"type": "object",
"required": ["type"],
"properties": {
"type": {
"type": "string",
"enum": ["sbom", "vex", "attestation", "evidence", "risk-report", "compliance-report", "airgap-bundle"]
},
"variant": {
"type": "string",
"enum": ["cyclonedx-1.6", "spdx-3.0.1", "openvex", "csaf-vex", "in-toto", "dsse", "json", "csv", "pdf"],
"description": "Format variant for the export type"
},
"options": {
"type": "object",
"properties": {
"include_signatures": {
"type": "boolean",
"default": true
},
"include_provenance": {
"type": "boolean",
"default": false
},
"include_rekor_receipts": {
"type": "boolean",
"default": false
},
"compress": {
"type": "boolean",
"default": true
},
"compression_algorithm": {
"type": "string",
"enum": ["gzip", "zstd", "none"],
"default": "gzip"
}
}
}
}
},
"ExportFilters": {
"type": "object",
"description": "Filters to apply when selecting data for export",
"properties": {
"date_range": {
"type": "object",
"properties": {
"from": {
"type": "string",
"format": "date-time"
},
"to": {
"type": "string",
"format": "date-time"
},
"relative": {
"type": "string",
"pattern": "^-?[0-9]+[hdwmy]$",
"description": "Relative time range (e.g., -7d for last 7 days)"
}
}
},
"severity": {
"type": "array",
"items": {
"type": "string",
"enum": ["critical", "high", "medium", "low", "info", "unknown"]
}
},
"vex_status": {
"type": "array",
"items": {
"type": "string",
"enum": ["affected", "not_affected", "fixed", "under_investigation"]
}
},
"components": {
"type": "array",
"items": {
"type": "string"
},
"description": "PURL patterns to include"
},
"exclude_components": {
"type": "array",
"items": {
"type": "string"
},
"description": "PURL patterns to exclude"
},
"cve_ids": {
"type": "array",
"items": {
"type": "string",
"pattern": "^CVE-[0-9]{4}-[0-9]+$"
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"environments": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"ExportSchedule": {
"type": "object",
"description": "Schedule for automated exports",
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"cron": {
"type": "string",
"pattern": "^(@(annually|yearly|monthly|weekly|daily|hourly))|((\\*|[0-9,\\-\\/]+)\\s+){4,5}(\\*|[0-9,\\-\\/]+)$",
"description": "Cron expression for scheduling (5 or 6 fields)"
},
"timezone": {
"type": "string",
"default": "UTC",
"description": "IANA timezone identifier"
},
"next_run": {
"type": "string",
"format": "date-time",
"readOnly": true
},
"last_run": {
"type": "string",
"format": "date-time",
"readOnly": true
},
"last_status": {
"type": "string",
"enum": ["success", "partial", "failed", "pending"],
"readOnly": true
}
}
},
"Distribution": {
"type": "object",
"description": "Distribution targets for exports",
"properties": {
"targets": {
"type": "array",
"items": {
"$ref": "#/definitions/DistributionTarget"
}
},
"notify_on_completion": {
"type": "boolean",
"default": true
},
"notify_on_failure": {
"type": "boolean",
"default": true
}
}
},
"DistributionTarget": {
"type": "object",
"required": ["type"],
"properties": {
"type": {
"type": "string",
"enum": ["s3", "azure-blob", "gcs", "sftp", "webhook", "email", "local"]
},
"name": {
"type": "string"
},
"enabled": {
"type": "boolean",
"default": true
},
"config": {
"type": "object",
"description": "Target-specific configuration",
"additionalProperties": true
}
},
"allOf": [
{
"if": {
"properties": { "type": { "const": "s3" } }
},
"then": {
"properties": {
"config": {
"type": "object",
"required": ["bucket", "region"],
"properties": {
"bucket": { "type": "string" },
"region": { "type": "string" },
"prefix": { "type": "string" },
"credentials_secret": { "type": "string" }
}
}
}
}
},
{
"if": {
"properties": { "type": { "const": "webhook" } }
},
"then": {
"properties": {
"config": {
"type": "object",
"required": ["url"],
"properties": {
"url": { "type": "string", "format": "uri" },
"method": { "type": "string", "enum": ["POST", "PUT"], "default": "POST" },
"headers": { "type": "object", "additionalProperties": { "type": "string" } },
"auth_secret": { "type": "string" }
}
}
}
}
}
]
},
"RetentionPolicy": {
"type": "object",
"description": "Retention policy for exported artifacts",
"properties": {
"max_age_days": {
"type": "integer",
"minimum": 1,
"maximum": 3650,
"default": 365
},
"max_count": {
"type": "integer",
"minimum": 1,
"description": "Maximum number of exports to retain"
},
"delete_on_success": {
"type": "boolean",
"default": false,
"description": "Delete source data after successful export"
}
}
},
"SigningConfig": {
"type": "object",
"description": "Signing configuration for exports",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"key_id": {
"type": "string",
"description": "Key identifier for signing"
},
"algorithm": {
"type": "string",
"enum": ["ES256", "RS256", "EdDSA"],
"default": "ES256"
},
"include_rekor": {
"type": "boolean",
"default": false,
"description": "Include Rekor transparency log receipt"
},
"timestamp_authority": {
"type": "string",
"format": "uri",
"description": "RFC 3161 timestamp authority URL"
}
}
},
"ExportJob": {
"type": "object",
"description": "Export job status",
"required": ["job_id", "profile_id", "status", "created_at"],
"properties": {
"job_id": {
"type": "string",
"format": "uuid"
},
"profile_id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": ["pending", "running", "success", "partial", "failed", "cancelled"]
},
"progress": {
"type": "object",
"properties": {
"percent": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"items_processed": {
"type": "integer"
},
"items_total": {
"type": "integer"
}
}
},
"artifacts": {
"type": "array",
"items": {
"$ref": "#/definitions/ExportArtifact"
}
},
"errors": {
"type": "array",
"items": {
"type": "string"
}
},
"created_at": {
"type": "string",
"format": "date-time"
},
"started_at": {
"type": "string",
"format": "date-time"
},
"completed_at": {
"type": "string",
"format": "date-time"
}
}
},
"ExportArtifact": {
"type": "object",
"required": ["artifact_id", "digest", "size"],
"properties": {
"artifact_id": {
"type": "string",
"format": "uuid"
},
"filename": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"size": {
"type": "integer",
"description": "Size in bytes"
},
"format": {
"type": "string"
},
"signature": {
"type": "string",
"description": "Base64-encoded signature"
},
"download_url": {
"type": "string",
"format": "uri"
},
"expires_at": {
"type": "string",
"format": "date-time"
}
}
}
},
"properties": {
"profiles": {
"type": "array",
"items": {
"$ref": "#/definitions/ExportProfile"
}
}
},
"examples": [
{
"profiles": [
{
"profile_id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Weekly SBOM Export",
"description": "Export all SBOMs in CycloneDX format weekly",
"format": {
"type": "sbom",
"variant": "cyclonedx-1.6",
"options": {
"include_signatures": true,
"compress": true
}
},
"filters": {
"date_range": {
"relative": "-7d"
}
},
"schedule": {
"enabled": true,
"cron": "0 2 * * 0",
"timezone": "UTC"
},
"distribution": {
"targets": [
{
"type": "s3",
"name": "compliance-bucket",
"config": {
"bucket": "company-compliance-exports",
"region": "us-east-1",
"prefix": "sboms/"
}
}
]
},
"retention": {
"max_age_days": 365,
"max_count": 52
},
"enabled": true,
"created_at": "2025-12-01T00:00:00Z"
}
]
}
]
}

View File

@@ -0,0 +1,605 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/notify-rules.schema.json",
"title": "StellaOps Notification Rules Schema",
"description": "Schema for notification rules, webhook payloads, and digest formats. Unblocks CLI-NOTIFY-38-001.",
"type": "object",
"definitions": {
"NotifyRule": {
"type": "object",
"required": ["rule_id", "name", "event_types", "channels", "created_at"],
"properties": {
"rule_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the notification rule"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"description": "Human-readable rule name"
},
"description": {
"type": "string",
"maxLength": 512
},
"event_types": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/EventType"
},
"description": "Event types that trigger this rule"
},
"filters": {
"$ref": "#/definitions/NotifyFilters"
},
"channels": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/NotifyChannel"
}
},
"throttle": {
"$ref": "#/definitions/ThrottleConfig"
},
"digest": {
"$ref": "#/definitions/DigestConfig"
},
"templates": {
"$ref": "#/definitions/NotifyTemplates"
},
"enabled": {
"type": "boolean",
"default": true
},
"priority": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"default": 50,
"description": "Rule priority (higher = processed first)"
},
"tenant_id": {
"type": "string",
"format": "uuid"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
}
}
},
"EventType": {
"type": "string",
"enum": [
"vulnerability.new",
"vulnerability.updated",
"vulnerability.resolved",
"vulnerability.critical",
"vex.status_changed",
"vex.consensus_changed",
"policy.violation",
"policy.override_requested",
"policy.override_approved",
"policy.override_expired",
"scan.completed",
"scan.failed",
"attestation.created",
"attestation.verification_failed",
"airgap.staleness_warning",
"airgap.staleness_critical",
"airgap.bundle_imported",
"export.completed",
"export.failed",
"system.health_degraded",
"system.error"
]
},
"NotifyFilters": {
"type": "object",
"description": "Filters to apply before triggering notification",
"properties": {
"severity": {
"type": "array",
"items": {
"type": "string",
"enum": ["critical", "high", "medium", "low", "info"]
},
"description": "Only trigger for these severities"
},
"cvss_minimum": {
"type": "number",
"minimum": 0,
"maximum": 10,
"description": "Minimum CVSS score to trigger"
},
"components": {
"type": "array",
"items": {
"type": "string"
},
"description": "PURL patterns to match"
},
"environments": {
"type": "array",
"items": {
"type": "string"
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"kev_only": {
"type": "boolean",
"default": false,
"description": "Only trigger for Known Exploited Vulnerabilities"
},
"fix_available": {
"type": "boolean",
"description": "Filter by fix availability"
}
}
},
"NotifyChannel": {
"type": "object",
"required": ["type"],
"properties": {
"type": {
"type": "string",
"enum": ["email", "slack", "teams", "webhook", "pagerduty", "opsgenie", "sns"]
},
"name": {
"type": "string"
},
"enabled": {
"type": "boolean",
"default": true
},
"config": {
"type": "object",
"additionalProperties": true
}
},
"allOf": [
{
"if": { "properties": { "type": { "const": "email" } } },
"then": {
"properties": {
"config": {
"type": "object",
"required": ["recipients"],
"properties": {
"recipients": {
"type": "array",
"items": { "type": "string", "format": "email" }
},
"cc": {
"type": "array",
"items": { "type": "string", "format": "email" }
},
"subject_prefix": { "type": "string" }
}
}
}
}
},
{
"if": { "properties": { "type": { "const": "slack" } } },
"then": {
"properties": {
"config": {
"type": "object",
"required": ["webhook_url"],
"properties": {
"webhook_url": { "type": "string", "format": "uri" },
"channel": { "type": "string" },
"username": { "type": "string" },
"icon_emoji": { "type": "string" }
}
}
}
}
},
{
"if": { "properties": { "type": { "const": "teams" } } },
"then": {
"properties": {
"config": {
"type": "object",
"required": ["webhook_url"],
"properties": {
"webhook_url": { "type": "string", "format": "uri" }
}
}
}
}
},
{
"if": { "properties": { "type": { "const": "webhook" } } },
"then": {
"properties": {
"config": {
"type": "object",
"required": ["url"],
"properties": {
"url": { "type": "string", "format": "uri" },
"method": { "type": "string", "enum": ["POST", "PUT"], "default": "POST" },
"headers": { "type": "object", "additionalProperties": { "type": "string" } },
"auth_type": { "type": "string", "enum": ["none", "basic", "bearer", "hmac"] },
"auth_secret": { "type": "string" },
"retry_count": { "type": "integer", "minimum": 0, "maximum": 5, "default": 3 },
"timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 60, "default": 30 }
}
}
}
}
},
{
"if": { "properties": { "type": { "const": "pagerduty" } } },
"then": {
"properties": {
"config": {
"type": "object",
"required": ["routing_key"],
"properties": {
"routing_key": { "type": "string" },
"severity_mapping": {
"type": "object",
"additionalProperties": { "type": "string", "enum": ["critical", "error", "warning", "info"] }
}
}
}
}
}
}
]
},
"ThrottleConfig": {
"type": "object",
"description": "Throttling configuration to prevent notification storms",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"max_per_hour": {
"type": "integer",
"minimum": 1,
"default": 100
},
"max_per_day": {
"type": "integer",
"minimum": 1,
"default": 1000
},
"dedupe_window_seconds": {
"type": "integer",
"minimum": 0,
"default": 300,
"description": "Window for deduplicating identical notifications"
},
"dedupe_key_fields": {
"type": "array",
"items": { "type": "string" },
"default": ["event_type", "cve_id", "purl"],
"description": "Fields to use for deduplication key"
}
}
},
"DigestConfig": {
"type": "object",
"description": "Configuration for digest/summary notifications",
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"frequency": {
"type": "string",
"enum": ["hourly", "daily", "weekly"],
"default": "daily"
},
"schedule": {
"type": "string",
"description": "Cron expression for digest delivery"
},
"timezone": {
"type": "string",
"default": "UTC"
},
"min_events": {
"type": "integer",
"minimum": 1,
"default": 1,
"description": "Minimum events required to send digest"
},
"group_by": {
"type": "array",
"items": {
"type": "string",
"enum": ["severity", "event_type", "component", "environment"]
},
"description": "Fields to group events by in digest"
},
"include_summary": {
"type": "boolean",
"default": true
},
"include_details": {
"type": "boolean",
"default": false,
"description": "Include full event details in digest"
}
}
},
"NotifyTemplates": {
"type": "object",
"description": "Custom notification templates",
"properties": {
"subject": {
"type": "string",
"description": "Template for notification subject (supports {{variables}})"
},
"body": {
"type": "string",
"description": "Template for notification body"
},
"body_html": {
"type": "string",
"description": "HTML template for email body"
}
}
},
"WebhookPayload": {
"type": "object",
"description": "Standard webhook payload format",
"required": ["id", "timestamp", "event_type", "data"],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique notification ID"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"event_type": {
"$ref": "#/definitions/EventType"
},
"version": {
"type": "string",
"default": "1.0.0"
},
"tenant_id": {
"type": "string",
"format": "uuid"
},
"data": {
"type": "object",
"description": "Event-specific payload data",
"additionalProperties": true
},
"metadata": {
"type": "object",
"properties": {
"rule_id": { "type": "string", "format": "uuid" },
"rule_name": { "type": "string" },
"retry_count": { "type": "integer" },
"digest_id": { "type": "string", "format": "uuid" }
}
}
}
},
"DigestPayload": {
"type": "object",
"description": "Digest/summary notification payload",
"required": ["id", "timestamp", "period", "summary"],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"period": {
"type": "object",
"required": ["start", "end"],
"properties": {
"start": { "type": "string", "format": "date-time" },
"end": { "type": "string", "format": "date-time" }
}
},
"summary": {
"type": "object",
"properties": {
"total_events": { "type": "integer" },
"by_severity": {
"type": "object",
"additionalProperties": { "type": "integer" }
},
"by_event_type": {
"type": "object",
"additionalProperties": { "type": "integer" }
},
"new_vulnerabilities": { "type": "integer" },
"resolved_vulnerabilities": { "type": "integer" },
"policy_violations": { "type": "integer" }
}
},
"events": {
"type": "array",
"items": {
"$ref": "#/definitions/WebhookPayload"
},
"description": "Optional detailed event list"
},
"groups": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": { "type": "string" },
"count": { "type": "integer" },
"sample_events": {
"type": "array",
"items": { "$ref": "#/definitions/WebhookPayload" }
}
}
}
}
}
},
"NotifySimulationRequest": {
"type": "object",
"description": "Request to simulate a notification rule",
"required": ["event"],
"properties": {
"rule_id": {
"type": "string",
"format": "uuid",
"description": "Rule to simulate (optional, uses all matching if not specified)"
},
"event": {
"$ref": "#/definitions/WebhookPayload"
},
"dry_run": {
"type": "boolean",
"default": true,
"description": "If true, don't actually send notifications"
}
}
},
"NotifySimulationResult": {
"type": "object",
"required": ["matched_rules", "would_notify"],
"properties": {
"matched_rules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rule_id": { "type": "string", "format": "uuid" },
"rule_name": { "type": "string" },
"matched": { "type": "boolean" },
"reason": { "type": "string" }
}
}
},
"would_notify": {
"type": "array",
"items": {
"type": "object",
"properties": {
"channel_type": { "type": "string" },
"channel_name": { "type": "string" },
"payload_preview": { "type": "object" }
}
}
},
"throttled": {
"type": "boolean"
},
"throttle_reason": {
"type": "string"
}
}
},
"NotifyAckToken": {
"type": "object",
"description": "Acknowledgement token for notifications",
"required": ["token", "notification_id", "expires_at"],
"properties": {
"token": {
"type": "string",
"description": "Opaque acknowledgement token"
},
"notification_id": {
"type": "string",
"format": "uuid"
},
"event_type": {
"$ref": "#/definitions/EventType"
},
"expires_at": {
"type": "string",
"format": "date-time"
},
"ack_url": {
"type": "string",
"format": "uri",
"description": "URL to acknowledge the notification"
}
}
}
},
"properties": {
"rules": {
"type": "array",
"items": {
"$ref": "#/definitions/NotifyRule"
}
}
},
"examples": [
{
"rules": [
{
"rule_id": "550e8400-e29b-41d4-a716-446655440002",
"name": "Critical Vulnerability Alert",
"description": "Immediate notification for critical vulnerabilities",
"event_types": ["vulnerability.critical", "vulnerability.new"],
"filters": {
"severity": ["critical"],
"kev_only": false
},
"channels": [
{
"type": "slack",
"name": "security-alerts",
"config": {
"webhook_url": "https://hooks.slack.com/services/xxx",
"channel": "#security-alerts",
"icon_emoji": ":warning:"
}
},
{
"type": "pagerduty",
"name": "security-oncall",
"config": {
"routing_key": "xxx",
"severity_mapping": {
"critical": "critical",
"high": "error"
}
}
}
],
"throttle": {
"enabled": true,
"max_per_hour": 50,
"dedupe_window_seconds": 300
},
"enabled": true,
"priority": 100,
"created_at": "2025-12-01T00:00:00Z"
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,564 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/reachability-input.schema.json",
"title": "StellaOps Reachability Input Schema",
"description": "Schema for reachability/exploitability signals input to Policy Engine. Unblocks POLICY-ENGINE-80-001, POLICY-RISK-66-003.",
"type": "object",
"definitions": {
"ReachabilityInput": {
"type": "object",
"description": "Input payload for policy engine reachability evaluation",
"required": ["subject", "reachability_facts", "timestamp"],
"properties": {
"subject": {
"$ref": "#/definitions/Subject"
},
"reachability_facts": {
"type": "array",
"items": {
"$ref": "#/definitions/ReachabilityFact"
}
},
"exploitability_facts": {
"type": "array",
"items": {
"$ref": "#/definitions/ExploitabilityFact"
}
},
"callgraph_refs": {
"type": "array",
"items": {
"$ref": "#/definitions/CallgraphRef"
}
},
"runtime_facts": {
"type": "array",
"items": {
"$ref": "#/definitions/RuntimeFact"
}
},
"entropy_score": {
"$ref": "#/definitions/EntropyScore"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"metadata": {
"type": "object",
"additionalProperties": true
}
}
},
"Subject": {
"type": "object",
"description": "Subject being evaluated (component + vulnerability)",
"required": ["purl"],
"properties": {
"purl": {
"type": "string",
"description": "Package URL of the component"
},
"cve_id": {
"type": "string",
"pattern": "^CVE-[0-9]{4}-[0-9]+$"
},
"ghsa_id": {
"type": "string",
"pattern": "^GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$"
},
"vulnerability_id": {
"type": "string",
"description": "Internal vulnerability identifier"
},
"affected_symbols": {
"type": "array",
"items": {
"type": "string"
},
"description": "Vulnerable symbols/functions in the component"
},
"version_range": {
"type": "string",
"description": "Affected version range (e.g., '<1.2.3')"
}
}
},
"ReachabilityFact": {
"type": "object",
"description": "Static reachability analysis result",
"required": ["state", "confidence"],
"properties": {
"state": {
"type": "string",
"enum": ["reachable", "unreachable", "potentially_reachable", "unknown"],
"description": "Reachability state"
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Confidence score (0-1)"
},
"source": {
"type": "string",
"enum": ["static_analysis", "dynamic_analysis", "sbom_inference", "manual", "external"],
"description": "Source of the reachability determination"
},
"analyzer": {
"type": "string",
"description": "Analyzer tool that produced this fact"
},
"analyzer_version": {
"type": "string"
},
"call_path": {
"$ref": "#/definitions/CallPath"
},
"entry_points": {
"type": "array",
"items": {
"$ref": "#/definitions/EntryPoint"
}
},
"evidence": {
"$ref": "#/definitions/ReachabilityEvidence"
},
"evaluated_at": {
"type": "string",
"format": "date-time"
}
}
},
"CallPath": {
"type": "object",
"description": "Call path from entry point to vulnerable symbol",
"properties": {
"depth": {
"type": "integer",
"minimum": 0,
"description": "Call depth from entry point"
},
"nodes": {
"type": "array",
"items": {
"$ref": "#/definitions/CallNode"
}
},
"edges": {
"type": "array",
"items": {
"$ref": "#/definitions/CallEdge"
}
}
}
},
"CallNode": {
"type": "object",
"required": ["id", "symbol"],
"properties": {
"id": {
"type": "string"
},
"symbol": {
"type": "string",
"description": "Fully qualified symbol name"
},
"file": {
"type": "string"
},
"line": {
"type": "integer"
},
"package": {
"type": "string"
},
"is_vulnerable": {
"type": "boolean"
},
"is_entry_point": {
"type": "boolean"
}
}
},
"CallEdge": {
"type": "object",
"required": ["source", "target"],
"properties": {
"source": {
"type": "string"
},
"target": {
"type": "string"
},
"call_type": {
"type": "string",
"enum": ["direct", "indirect", "virtual", "reflection", "dynamic"]
}
}
},
"EntryPoint": {
"type": "object",
"description": "Application entry point that can reach vulnerable code",
"required": ["type", "identifier"],
"properties": {
"type": {
"type": "string",
"enum": ["http_endpoint", "grpc_method", "cli_command", "event_handler", "scheduled_job", "main", "test"]
},
"identifier": {
"type": "string",
"description": "Entry point identifier (e.g., 'POST /api/users')"
},
"file": {
"type": "string"
},
"line": {
"type": "integer"
},
"exposed": {
"type": "boolean",
"default": true,
"description": "Whether this entry point is externally exposed"
},
"authentication_required": {
"type": "boolean"
}
}
},
"ReachabilityEvidence": {
"type": "object",
"description": "Supporting evidence for reachability determination",
"properties": {
"digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"evidence_uri": {
"type": "string",
"format": "uri"
},
"callgraph_digest": {
"type": "string"
},
"sbom_digest": {
"type": "string"
},
"analysis_log_uri": {
"type": "string",
"format": "uri"
}
}
},
"ExploitabilityFact": {
"type": "object",
"description": "Exploitability assessment",
"required": ["state", "confidence"],
"properties": {
"state": {
"type": "string",
"enum": ["exploitable", "not_exploitable", "conditionally_exploitable", "unknown"]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"source": {
"type": "string",
"enum": ["kev", "epss", "vendor_advisory", "internal_analysis", "exploit_db"]
},
"epss_score": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "EPSS probability score"
},
"epss_percentile": {
"type": "number",
"minimum": 0,
"maximum": 100
},
"kev_listed": {
"type": "boolean",
"description": "Listed in CISA Known Exploited Vulnerabilities"
},
"kev_due_date": {
"type": "string",
"format": "date"
},
"exploit_maturity": {
"type": "string",
"enum": ["not_defined", "unproven", "poc", "functional", "high"],
"description": "Exploit maturity level (per CVSS)"
},
"exploit_refs": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
},
"conditions": {
"type": "array",
"items": {
"$ref": "#/definitions/ExploitCondition"
},
"description": "Conditions required for exploitation"
},
"evaluated_at": {
"type": "string",
"format": "date-time"
}
}
},
"ExploitCondition": {
"type": "object",
"description": "Condition required for exploitation",
"required": ["condition", "met"],
"properties": {
"condition": {
"type": "string",
"description": "Description of the condition"
},
"met": {
"type": "boolean"
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"evidence": {
"type": "string"
}
}
},
"CallgraphRef": {
"type": "object",
"description": "Reference to a stored callgraph",
"required": ["digest"],
"properties": {
"digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"format": {
"type": "string",
"enum": ["richgraph-v1", "dot", "json-graph", "sarif"],
"default": "richgraph-v1"
},
"uri": {
"type": "string",
"format": "uri"
},
"generated_at": {
"type": "string",
"format": "date-time"
},
"generator": {
"type": "string"
},
"generator_version": {
"type": "string"
}
}
},
"RuntimeFact": {
"type": "object",
"description": "Runtime observation fact",
"required": ["type", "observed_at"],
"properties": {
"type": {
"type": "string",
"enum": ["function_called", "function_not_called", "path_executed", "path_not_executed", "module_loaded", "module_not_loaded"]
},
"symbol": {
"type": "string"
},
"module": {
"type": "string"
},
"call_count": {
"type": "integer",
"minimum": 0
},
"last_called": {
"type": "string",
"format": "date-time"
},
"observed_at": {
"type": "string",
"format": "date-time"
},
"observation_window": {
"type": "string",
"description": "Duration of observation (e.g., '7d', '30d')"
},
"environment": {
"type": "string",
"enum": ["production", "staging", "development", "test"]
}
}
},
"EntropyScore": {
"type": "object",
"description": "Scanner entropy/trust score for confidence weighting",
"properties": {
"overall": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Overall trust score"
},
"sbom_completeness": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"callgraph_coverage": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"runtime_coverage": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"analyzer_confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"data_freshness": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "How recent the underlying data is"
}
}
},
"ReachabilityOutput": {
"type": "object",
"description": "Policy engine output after reachability evaluation",
"required": ["subject", "effective_state", "risk_adjustment"],
"properties": {
"subject": {
"$ref": "#/definitions/Subject"
},
"effective_state": {
"type": "string",
"enum": ["reachable", "unreachable", "potentially_reachable", "unknown"]
},
"effective_exploitability": {
"type": "string",
"enum": ["exploitable", "not_exploitable", "conditionally_exploitable", "unknown"]
},
"risk_adjustment": {
"type": "object",
"properties": {
"factor": {
"type": "number",
"minimum": 0,
"maximum": 2,
"description": "Risk multiplier (0 = suppress, 1 = neutral, >1 = amplify)"
},
"severity_override": {
"type": "string",
"enum": ["critical", "high", "medium", "low", "info"]
},
"justification": {
"type": "string"
}
}
},
"policy_trace": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rule_id": { "type": "string" },
"result": { "type": "string" },
"reason": { "type": "string" }
}
}
},
"evaluated_at": {
"type": "string",
"format": "date-time"
}
}
}
},
"properties": {
"inputs": {
"type": "array",
"items": {
"$ref": "#/definitions/ReachabilityInput"
}
}
},
"examples": [
{
"inputs": [
{
"subject": {
"purl": "pkg:npm/lodash@4.17.20",
"cve_id": "CVE-2021-23337",
"affected_symbols": ["lodash.template"]
},
"reachability_facts": [
{
"state": "reachable",
"confidence": 0.95,
"source": "static_analysis",
"analyzer": "stellaops-scanner",
"analyzer_version": "2025.10.0",
"call_path": {
"depth": 3,
"nodes": [
{ "id": "n1", "symbol": "app.renderTemplate", "is_entry_point": true },
{ "id": "n2", "symbol": "templateEngine.compile" },
{ "id": "n3", "symbol": "lodash.template", "is_vulnerable": true }
],
"edges": [
{ "source": "n1", "target": "n2", "call_type": "direct" },
{ "source": "n2", "target": "n3", "call_type": "direct" }
]
},
"entry_points": [
{
"type": "http_endpoint",
"identifier": "POST /api/render",
"exposed": true,
"authentication_required": true
}
],
"evaluated_at": "2025-12-06T10:00:00Z"
}
],
"exploitability_facts": [
{
"state": "exploitable",
"confidence": 0.8,
"source": "epss",
"epss_score": 0.42,
"epss_percentile": 87,
"kev_listed": false,
"exploit_maturity": "functional",
"evaluated_at": "2025-12-06T10:00:00Z"
}
],
"entropy_score": {
"overall": 0.85,
"sbom_completeness": 0.95,
"callgraph_coverage": 0.78,
"analyzer_confidence": 0.9
},
"timestamp": "2025-12-06T10:00:00Z"
}
]
}
]
}