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
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:
886
docs/api/taskrunner-openapi.yaml
Normal file
886
docs/api/taskrunner-openapi.yaml
Normal 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
|
||||
369
docs/contracts/authority-crypto-provider.md
Normal file
369
docs/contracts/authority-crypto-provider.md
Normal 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 |
|
||||
425
docs/contracts/sealed-install-enforcement.md
Normal file
425
docs/contracts/sealed-install-enforcement.md
Normal 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 |
|
||||
@@ -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 A–D): 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 (A–F) covering core analyzer, testing, CLI/WebService/Worker integration, and docs. | Planning |
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
@@ -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 |
|
||||
|
||||
282
docs/modules/scanner/prep/bun-analyzer-design.md
Normal file
282
docs/modules/scanner/prep/bun-analyzer-design.md
Normal 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
|
||||
502
docs/schemas/export-profiles.schema.json
Normal file
502
docs/schemas/export-profiles.schema.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
605
docs/schemas/notify-rules.schema.json
Normal file
605
docs/schemas/notify-rules.schema.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
1510
docs/schemas/policy-registry-api.openapi.yaml
Normal file
1510
docs/schemas/policy-registry-api.openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
564
docs/schemas/reachability-input.schema.json
Normal file
564
docs/schemas/reachability-input.schema.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user