34 KiB
Charybdis - Architecture
Version: 2.0 Last Updated: 2026-05-06 Status: Living Document
For the product vision and roadmap, see VISION.md.
Executive Summary
Charybdis is a security-native platform engineering tool that unifies software catalog, vulnerability management, and compliance posture in a single event-driven platform. Built in Rust, deployed as a single binary.
Core Value Proposition
Problem:
- Platform engineers run 5+ disconnected tools (Backstage, DefectDojo, Dependency-Track, license scanners, compliance spreadsheets)
- Software catalogs rely on static YAML files that drift from reality
- Security posture is invisible at the catalog level — vulns live in separate tools with no link to services
- Compliance evidence is assembled manually from fragmented data sources
Solution:
- Dynamic software catalog — event-driven, gRPC-native, no static YAML
- Native vulnerability management — ingest scan results (SARIF, CycloneDX, SPDX), triage, assess, track
- Security gates & compliance — severity thresholds, license policies, compliance framework mappings
- Plugin system for integrations — Slack, Jira, GitHub, custom tools react to events
- Single binary deployment — Rust + PostgreSQL, ~50MB RAM, sub-millisecond latency
Use Case Examples
Service registration with auto-provisioning:
CI/CD Pipeline (Python) → gRPC CreateEntity(kind=Component)
↓
Charybdis persists entity
↓
Event: EntityCreated published
↓
┌────────────────────┴────────────────────┐
↓ ↓
Jira Plugin Slack Plugin
- Creates onboarding epic - Notifies #platform channel
↓ ↓
└────────────────────┬────────────────────┘
↓
Entity visible via gRPC API / Backstage YAML adapter
Vulnerability ingestion with security gates:
Scanner (Trivy) → CI/CD → gRPC IngestScan(sarif_report)
↓
Charybdis parses SARIF
Creates Vulnerability entities linked to Component
↓
Rules engine evaluates auto-assessment
Security gate checks thresholds
↓
┌────────────────────┴────────────────────┐
↓ ↓
Gate PASSED Gate FAILED
- Vulns tracked - Slack alert to #security
- Dashboard updated - Jira ticket created
- CI/CD pipeline blocked
Architectural Principles
1. No Database Migrations Ever
The database schema is created once on first startup and never changes.
How it works:
- Entities stored as protobuf bytes in
entity_data BYTEAcolumn - Plugins add new entity types via protobuf
oneofvariants - Build system regenerates code, database schema remains static
Benefits:
- Deploy new plugins without downtime
- No migration scripts to manage
- Forward/backward compatibility built-in
- Easy rollback (protobuf versioning)
2. gRPC First, Everything Else is Adapter
Charybdis provides only gRPC APIs. All other interfaces (REST, YAML, GraphQL) are adapters on top.
Rationale:
- Strong typing: Protobuf ensures type safety across all languages
- Performance: Binary protocol, efficient serialization
- Multi-language: Official gRPC clients for 10+ languages
- Streaming: Built-in support for real-time updates (future)
- Code generation: Automatic client/server code generation
What Charybdis provides:
- ✅ gRPC API with protobuf definitions
- ✅ YAML adapter endpoints (for Backstage compatibility/migration)
- ❌ REST API (teams can add if needed via gateway)
- ❌ GraphQL (teams can add if needed)
3. Plugin-Based Extensibility
Plugins are compile-time integrated Rust crates that:
- Define entity types via protobuf schemas
- React to events via EventHandler trait
- Call external APIs to synchronize state
- Store references in entity annotations
- (Future) Extend gRPC API with custom endpoints
Plugin characteristics:
- Not hot-swappable (require rebuild)
- Type-safe at compile time
- No runtime dependency resolution
- Configurable via YAML config file
- Can be developed by community
4. Event-Driven Orchestration
Events are published after entities are persisted:
CRUD Operation → Persist to DB → Publish Event → Plugins React
Event flow:
- Entity created/updated/deleted in database
- Event published to event bus with full entity data
- All subscribed plugin handlers receive event
- Plugins filter events they care about
- Plugins perform asynchronous actions
- Plugins update entity annotations (via repository)
Error handling:
- Plugin errors do not fail the CRUD operation
- Entity is already persisted before event publishing
- Plugins log errors for observability
- Retries handled by event bus backend
5. Backstage Compatibility by Design
Charybdis entity model is 100% compatible with Backstage's descriptor format.
How it works:
- Core protobuf types mirror Backstage YAML structure
- YAML adapter endpoints serve entities in Backstage format
- Backstage reads Charybdis as a dynamic location provider
- No changes needed to Backstage frontend
Key compatibility points:
- Entity
kind,apiVersion,metadata,specstructure - Annotation format (reverse-DNS style)
- Relationship types (
dependsOn,partOf, etc.) - All standard Backstage entity kinds supported
System Architecture
Component Overview
┌─────────────────────────────────────────────────────────────────┐
│ Client Layer │
│ CI/CD Pipelines (Python, Go, Node.js, Bash, etc.) │
│ Security Scanners (SARIF, CycloneDX, SPDX output) │
│ IaC Tools (Terraform, Pulumi, Crossplane) │
│ Backstage (via YAML adapter, optional) │
└────────────────┬────────────────────────────────────────────────┘
│
│ gRPC / YAML HTTP
▼
┌─────────────────────────────────────────────────────────────────┐
│ Charybdis Core │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ gRPC API │ │
│ │ EntityService: CRUD + List (with field masks) │ │
│ │ ScanService: IngestScan (SARIF, CycloneDX, SPDX) │ │
│ │ AssessmentService: Triage, Accept, Remediate │ │
│ └────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼─────────────────────────────────────┐ │
│ │ Security Engine (native) │ │
│ │ - Vulnerability tracking per entity │ │
│ │ - Rules engine (auto-assessment) │ │
│ │ - Security gates (severity thresholds) │ │
│ │ - License compliance │ │
│ └────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼─────────────────────────────────────┐ │
│ │ Adapters │ │
│ │ - YAML Adapter (Backstage compat / migration) │ │
│ └────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼─────────────────────────────────────┐ │
│ │ Repository Layer │ │
│ │ - PostgreSQL with protobuf storage │ │
│ │ - JSONB annotations for fast queries │ │
│ │ - Zero-migration schema │ │
│ └────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼─────────────────────────────────────┐ │
│ │ Event Bus System │ │
│ │ - Memory backend (dev) │ │
│ │ - Redis backend (prod, future) │ │
│ └────────────────────┬─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ Entity / Vulnerability / Gate events
▼
┌─────────────────────────────────────────────────────────────────┐
│ Plugin Layer (integrations) │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Slack/Teams │ │ Jira/GitHub │ │ Custom Plugins │ │
│ │ Notifications │ │ Issue Track │ │ Your integrations │ │
│ └──────┬────────┘ └──────┬───────┘ └──────────┬─────────────┘ │
└─────────┼──────────────────┼─────────────────────┼──────────────┘
▼ ▼ ▼
Slack/Teams API Jira/GitHub API External APIs
Data Flow
Entity Creation Flow
1. Client (CI/CD)
↓ gRPC CreateEntityRequest
2. Charybdis gRPC API
↓ Validate entity
↓ Generate UUID, timestamps
3. Entity Repository
↓ Serialize to protobuf bytes
↓ INSERT INTO entities
4. PostgreSQL
↓ Entity persisted
5. Event Bus
↓ Publish EntityCreated event (with full entity data)
6. Plugin Handlers (parallel)
├─ DefectDojo: Create product → Store product_id in annotations
├─ DependencyTrack: Create project → Store project_uuid in annotations
└─ Custom: Custom actions
7. Repository.update_annotations()
↓ Merge annotations from plugins
↓ UPDATE entities SET annotations = ...
8. PostgreSQL
↓ Entity updated with external tool IDs
9. Return CreateEntityResponse to client
Backstage Integration Flow
1. Backstage Catalog Backend
↓ HTTP GET /yaml/locations
2. Charybdis YAML Adapter
↓ Query repository.list_all()
↓ Convert entities to Location YAML
3. Backstage receives location list
↓ Fetches each entity: GET /yaml/entities/:id
4. Charybdis YAML Adapter
↓ repository.get_by_id()
↓ Convert entity protobuf → Backstage YAML
5. Backstage ingests entities
↓ Displays in UI with all annotations
↓ Shows linked DefectDojo products, etc.
Core Entity Model
Protobuf Schema
message Entity {
// System-managed fields
string id = 1; // UUID (auto-generated)
google.protobuf.Timestamp created_at = 21;
google.protobuf.Timestamp updated_at = 22;
// Backstage-compatible fields
string api_version = 2; // "backstage.io/v1alpha1"
string kind = 3; // "Component", "API", "User", etc.
// Polymorphic metadata (identifying information)
oneof metadata {
// Core types (built-in)
ComponentMetadata component_metadata = 10;
APIMetadata api_metadata = 11;
UserMetadata user_metadata = 12;
GroupMetadata group_metadata = 13;
SystemMetadata system_metadata = 14;
DomainMetadata domain_metadata = 15;
ResourceMetadata resource_metadata = 16;
LocationMetadata location_metadata = 17;
TemplateMetadata template_metadata = 18;
// Plugin types (dynamically added)
// Example: DefectDojoProductMetadata defectdojo_product_metadata = 100;
}
// Polymorphic spec (configuration and behavior)
oneof spec {
// Core types (built-in)
ComponentSpec component_spec = 10;
APISpec api_spec = 11;
UserSpec user_spec = 12;
GroupSpec group_spec = 13;
SystemSpec system_spec = 14;
DomainSpec domain_spec = 15;
ResourceSpec resource_spec = 16;
LocationSpec location_spec = 17;
TemplateSpec template_spec = 18;
// Plugin types (dynamically added)
// Example: DefectDojoProductSpec defectdojo_product_spec = 100;
}
// Annotations - external tool references
// Format: "tool.com/resource-type-attribute"
// Examples:
// "defectdojo.com/product-id": "42"
// "dependencytrack.com/project-uuid": "550e8400-..."
// "github.com/repo-slug": "myorg/myrepo"
map<string, string> annotations = 20;
}
Backstage Entity Kinds Mapping
All Backstage entity kinds must be supported:
| Backstage Kind | Charybdis Proto Type | Status |
|---|---|---|
| Component | ComponentMetadata/Spec | ✅ Done |
| Service | ServiceMetadata/Spec | ✅ Done |
| API | APIMetadata/Spec | ✅ Done |
| User | UserMetadata/Spec | ✅ Done |
| Group | GroupMetadata/Spec | ✅ Done |
| System | SystemMetadata/Spec | ✅ Done |
| Domain | DomainMetadata/Spec | ✅ Done |
| Resource | ResourceMetadata/Spec | ✅ Done |
| Location | LocationMetadata/Spec | Not planned (entities are event-driven, not file-based) |
| Template | TemplateMetadata/Spec | Phase 3 (Scaffolder) |
YAML Adapter Layer
Purpose
Serve entities in Backstage-compatible YAML format over HTTP.
API Specification
1. List Locations
Endpoint: GET /yaml/locations
Purpose: Return a dynamic Location entity that lists all entities in Charybdis.
Response Format (Backstage Location YAML):
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: charybdis-all-entities
description: Dynamic location managed by Charybdis
spec:
type: charybdis
targets:
- http://charybdis.company.com/yaml/entities/service-payment
- http://charybdis.company.com/yaml/entities/service-auth
- http://charybdis.company.com/yaml/entities/api-payments-v1
- http://charybdis.company.com/yaml/entities/user-john-doe
# ... all entities
Implementation Notes:
- Query
SELECT id, kind FROM entities - Generate URL for each entity:
/yaml/entities/{kind}-{id} - Return as Backstage Location YAML
- Cache response (invalidate on entity CRUD)
2. Get Entity by ID
Endpoint: GET /yaml/entities/:id
Purpose: Return a specific entity in Backstage YAML format.
Response Format (Backstage Component YAML):
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-service
namespace: production
description: Core payment processing service
annotations:
github.com/repo-slug: myorg/payment-service
defectdojo.com/product-id: "42"
dependencytrack.com/project-uuid: "550e8400-e29b-41d4-a716-446655440000"
tags:
- payments
- critical
links:
- url: https://dashboard.company.com/payment-service
title: Dashboard
icon: dashboard
spec:
type: service
lifecycle: production
owner: payments-team
system: payment-system
dependsOn:
- component:auth-service
providesApis:
- payments-api-v1
Implementation Notes:
- Query
repository.get_by_id(id) - Convert Entity protobuf → Backstage YAML
- Map
oneof metadata/specto appropriate YAML structure - Include all annotations
- Return as
text/yamlcontent type
3. Configuration in Backstage
Backstage app-config.yaml:
catalog:
locations:
- type: url
target: http://charybdis.company.com/yaml/locations
rules:
- allow: [Component, API, User, Group, System, Domain, Resource]
How it works:
- Backstage fetches
/yaml/locationson catalog refresh - Parses Location YAML to get entity URLs
- Fetches each entity URL (
/yaml/entities/:id) - Ingests entities into Backstage catalog
- Displays in UI with all metadata and annotations
Plugin System
Plugin Architecture
Charybdis supports two types of plugins, both compile-time integrated:
1. Event-Driven Plugins
React to entity lifecycle events (create, update, delete):
- Example: DefectDojo, DependencyTrack
- Implement:
EventDrivenPlugintrait - Provide:
ResourceHandlerimplementations - Triggered by: Entity CRUD operations
2. Sync Plugins
Pull data from external sources on a schedule:
- Example: Okta, Keycloak, Active Directory
- Implement:
SyncPlugintrait - Scheduled by: Cron expressions
- Triggered by: Time-based schedule or manual API call
Generic Plugin Utilities (2025-01-15)
All plugins have access to reusable utilities in src/plugins/:
PluginHttpClient
Generic HTTP client with multiple auth methods:
- Token authentication (DefectDojo)
- Bearer authentication (modern APIs)
- API Key authentication (DependencyTrack)
- Basic authentication (JIRA, Bitbucket)
- Methods:
get(),post(),put(),patch(),delete()
AnnotationHelper
Consistent API for storing/retrieving plugin metadata:
set_id()/get_id()- Store external resource IDsset()/get()- Store arbitrary metadata- Enforces naming:
{plugin}.com/{resource}-id - Prevents annotation conflicts between plugins
FieldMapper
Maps entity fields to external tool formats:
- Direct field paths:
"metadata.name" - Static values:
{"value": "Web Application"} - Complex mappings with entity resolution
- Repository integration for cross-entity queries
DateUtils
Common date/time operations:
today(),today_plus_days(n)- Date formattingengagement_date_range(days)- For time-bound resourcesiso_timestamp()- ISO 8601 timestamps
Plugin Responsibilities
Plugins can:
- Define custom entity types (via protobuf)
- React to entity events (via ResourceHandler trait)
- Call external APIs (using PluginHttpClient)
- Store metadata (using AnnotationHelper)
- Map fields (using FieldMapper)
- Schedule syncs (SyncPlugins with cron)
- (Future) Extend gRPC API (custom services)
Plugin Lifecycle
1. Development
├─ Define protobuf schema (metadata/spec messages)
├─ Implement EventHandler trait
├─ Implement external API client
└─ Add configuration schema
2. Registration
├─ Add to plugins.toml (enabled = true)
└─ Add to Cargo.toml dependencies
3. Build Time
├─ build.rs reads plugins.toml
├─ Generates entities.proto with plugin types
├─ Compiles all protobuf to Rust code
└─ Links plugin into binary
4. Runtime
├─ Load plugin configuration from config file
├─ Initialize plugin handler
├─ Subscribe to event bus
└─ React to entity events
Plugin Configuration System
File: config.toml (TOML format with ${VAR} env var substitution)
# Server configuration
[server]
grpc_host = "[::1]"
grpc_port = 50051
[server.yaml_adapter]
enabled = true
host = "0.0.0.0"
port = 8080
# Database
[database]
url = "${DATABASE_URL}"
max_connections = 10
# Plugin configurations
[plugins.defectdojo]
enabled = true
base_url = "${DEFECTDOJO_API_URL}"
api_token = "${DEFECTDOJO_API_TOKEN}"
[plugins.defectdojo.default_engagement]
auto_create = true
name = "CI/CD Pipeline"
engagement_type = "CI/CD"
[plugins.keycloak]
enabled = false
# base_url = "https://keycloak.company.com"
# realm = "master"
[plugins.dependencytrack]
enabled = false
# base_url = "https://dependencytrack.company.com"
# api_key = "${DEPENDENCYTRACK_API_KEY}"
See config.toml.example for the full reference with all options documented.
Configuration loading (src/config.rs):
pub struct Config {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub security: SecurityConfig,
pub telemetry: TelemetryConfig,
pub plugins: PluginsConfig,
}
impl Config {
pub fn from_file(path: &str) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
// Substitute ${VAR} with environment variable values
let content = substitute_env_vars(&content);
let config: Config = toml::from_str(&content)?;
Ok(config)
}
}
Each plugin implements:
pub trait Plugin {
fn name(&self) -> &str;
fn plugin_type(&self) -> PluginType;
fn load_config(&mut self, config: PluginConfig) -> Result<()>;
fn validate_config(&self) -> Result<()>;
}
Event Handler Behavior Configuration
Plugins can configure different behaviors based on:
- Entity kind
- Event type (created/updated/deleted)
- Custom conditions
Example: DefectDojo plugin config (config.toml)
[plugins.defectdojo]
enabled = true
base_url = "${DEFECTDOJO_API_URL}"
api_token = "${DEFECTDOJO_API_TOKEN}"
[plugins.defectdojo.default_engagement]
auto_create = true
name = "CI/CD Pipeline"
engagement_type = "CI/CD"
Handler Implementation:
impl EventHandler for DefectDojoHandler {
async fn handle_event(&self, event: &EntityEvent) -> EventResult<()> {
// Check if plugin enabled
if !self.config.enabled {
return Ok(());
}
// Match event type to configured behavior
let behavior = match event.event_type {
EntityEventType::Created => &self.config.on_entity_created,
EntityEventType::Updated => &self.config.on_entity_updated,
EntityEventType::Deleted => &self.config.on_entity_deleted,
};
// Find matching rule for this entity kind
for rule in behavior {
if rule.matches(&event) {
return self.execute_action(&rule.action, event).await;
}
}
Ok(())
}
}
Future: Plugin API Extensions
Future capability: Plugins can expose custom gRPC endpoints.
Example: DefectDojo Query Plugin
Plugin extends API with custom service:
// plugins/defectdojo/proto/defectdojo_service.proto
service DefectDojoService {
// Get findings for a specific entity
rpc GetEntityFindings(GetEntityFindingsRequest) returns (GetEntityFindingsResponse);
// Get all active vulnerabilities
rpc GetActiveVulnerabilities(GetActiveVulnerabilitiesRequest) returns (GetActiveVulnerabilitiesResponse);
// Sync entity with DefectDojo
rpc SyncEntity(SyncEntityRequest) returns (SyncEntityResponse);
}
message GetEntityFindingsRequest {
string entity_id = 1;
string severity_filter = 2; // "Critical", "High", etc.
bool active_only = 3;
}
message GetEntityFindingsResponse {
repeated Finding findings = 1;
int32 total_count = 2;
}
message Finding {
int32 id = 1;
string title = 2;
string severity = 3;
string description = 4;
google.protobuf.Timestamp date = 5;
bool active = 6;
}
Implementation:
pub struct DefectDojoServiceImpl {
client: DefectDojoClient,
repository: EntityRepository,
}
#[async_trait]
impl DefectDojoService for DefectDojoServiceImpl {
async fn get_entity_findings(
&self,
request: Request<GetEntityFindingsRequest>,
) -> Result<Response<GetEntityFindingsResponse>, Status> {
let req = request.into_inner();
// Get entity to retrieve DefectDojo product ID
let entity = self.repository.get_by_id(&req.entity_id).await?;
let product_id = entity
.annotations
.get("defectdojo.com/product-id")
.ok_or_else(|| Status::not_found("No DefectDojo product linked"))?;
// Fetch findings from DefectDojo API
let findings = self.client
.get_findings(product_id, &req.severity_filter, req.active_only)
.await?;
Ok(Response::new(GetEntityFindingsResponse {
findings: findings.into_iter().map(|f| f.into()).collect(),
total_count: findings.len() as i32,
}))
}
}
Registration in main.rs:
// Register plugin's custom gRPC service
if defectdojo_plugin.is_enabled() {
let defectdojo_service = DefectDojoServiceServer::new(
DefectDojoServiceImpl::new(defectdojo_client, repository.clone())
);
server = server.add_service(defectdojo_service);
}
Client usage:
grpcurl -d '{
"entity_id": "service-payment",
"severity_filter": "Critical",
"active_only": true
}' localhost:50051 charybdis.plugins.defectdojo.DefectDojoService/GetEntityFindings
Technology Decisions
Why gRPC Only?
Decision: Charybdis core provides only gRPC. Teams add REST/GraphQL as needed.
Rationale:
- Strong typing: Protobuf schemas ensure correctness across languages
- Performance: Binary protocol, efficient for CI/CD use cases
- Multi-language: Official clients for Python, Go, Node.js, Java, Rust, C++, C#, etc.
- Streaming: Built-in bidirectional streaming (future: real-time updates)
- Code generation: Automatic client generation eliminates boilerplate
- Focus: Core team maintains one high-quality API surface
Teams can add REST if needed:
grpc-gateway or Envoy → Charybdis gRPC
Why PostgreSQL + JSONB?
Decision: Use PostgreSQL with protobuf BYTEA storage and JSONB annotations.
Rationale:
- Scale: Handles 1000s of entities easily (tested: 170k resources)
- JSONB performance: GIN indexes enable fast annotation queries
- Familiarity: Most teams already run PostgreSQL
- Reliability: ACID transactions, mature ecosystem
- Future-proof: Can add read replicas, partitioning if needed
Alternative considered: MongoDB
- Verdict: PostgreSQL with JSONB provides same flexibility with better consistency guarantees
Why Compile-Time Plugins?
Decision: Plugins are compile-time integrated, not runtime loaded.
Rationale:
- Type safety: Rust compiler ensures correctness
- Performance: No dynamic loading overhead
- Simplicity: No plugin version compatibility matrix
- Security: No arbitrary code execution
- Trade-off: Requires rebuild to add plugins (acceptable for infrastructure tool)
Community plugins: Published as crates, teams include in their build.
Why Event-Driven?
Decision: Plugins react to events after entity persistence.
Rationale:
- Decoupling: Plugins can't break core CRUD operations
- Resilience: Plugin failures are logged, not propagated
- Async: Long-running operations don't block API
- Extensibility: Add plugins without modifying core
- Observability: Events provide audit trail
Trade-off: Plugins can't prevent entity creation (validation must be in core or client).
Scale Estimates
Reference Deployment
Company: ~100 engineers
Backstage Entities:
- Components: ~1,000
- APIs: ~1,000
- Users: ~700
- Groups: ~200
- Systems: ~50
- Domains: ~150
- Resources: ~170,000
Charybdis Requirements:
- PostgreSQL: ~500MB storage (with JSONB annotations)
- Memory: ~256MB for Charybdis service
- CPU: Minimal (mostly I/O bound)
10x Scale (1,000 engineers, 1.7M resources):
- PostgreSQL: ~5GB storage
- Memory: ~512MB
- CPU: Still minimal with proper indexing
Bottlenecks:
- JSONB queries on annotations (solved with GIN indexes)
- Event bus throughput (solved with Redis/RabbitMQ backend)
- Plugin external API rate limits (solved with plugin-level queuing)
Development Roadmap
See VISION.md for the full product vision and strategic roadmap.
Phase 0: Catalog Foundation (Done)
- ✅ Zero-migration database architecture
- ✅ gRPC API with protobuf (full entity CRUD)
- ✅ Event bus system (MemoryEventBus)
- ✅ Event dispatcher architecture (generic + plugin-specific)
- ✅ All Backstage entity kinds (Component, System, API, User, Group, Domain, Resource)
- ✅ YAML adapter endpoints (Backstage compatibility)
- ✅ gRPC field masks for partial updates
- ✅ OpenTelemetry observability (traces, metrics, logs)
- ✅ mTLS + RBAC security framework
- ✅ Config loading from TOML with env var substitution
- ✅ Plugin trait architecture (EventDriven + Sync)
- ✅ Generic plugin utilities (HttpClient, AnnotationHelper, FieldMapper, DateUtils)
- ✅ DefectDojo plugin (products, engagements, owner resolution)
- ✅ Keycloak plugin (user/group sync with annotations)
Phase 1: Security Core (Current Focus)
Goal: Native vulnerability management and scan ingestion.
- Vulnerability/Observation entity kind (protobuf + storage)
- ScanService gRPC endpoint for scan ingestion
- SARIF parser (covers majority of modern scanners)
- CycloneDX parser (SBOMs + vulnerability data)
- SPDX parser (license data)
- Assessment workflow (triage, accept risk, remediate)
- Rules engine for auto-assessment
- Security gates (severity thresholds per product)
- License tracking and policy engine
Phase 2: Compliance & Integrations
Goal: Compliance frameworks and integration plugins.
- Compliance framework mappings (NIS2, SOC2, DORA)
- VEX document support (CSAF, OpenVEX)
- Export/reporting (PDF, Excel)
- Notification plugins (Slack, Teams, email)
- Issue tracker plugins (Jira, GitHub, GitLab)
- Redis event bus backend
Phase 3: Scaffolder & Ecosystem
Goal: Service scaffolding and community growth.
- Service scaffolder (Git-native templates)
- Event-driven provisioning on scaffold
- Plugin SDK documentation
- Helm chart & 1-click deploy
- Community plugin registry
Success Criteria
Phase 1 Success (Security Core)
- Ingest SARIF scan results via gRPC
- Vulnerabilities linked to catalog entities
- Rules engine auto-assesses based on severity/component
- Security gates block on threshold violations
- Assessment workflow (triage → accept/remediate)
- License data ingested from CycloneDX/SPDX
Production Success
- 1,000+ entities managed with security posture
- <100ms p99 latency for CRUD operations
- Single
helm installdeployment - Documentation complete
- 3+ integration plugins in production
Key Decisions Summary
| Decision | Choice | Rationale |
|---|---|---|
| API Protocol | gRPC only | Strong typing, multi-language, performance |
| Frontend | Backstage YAML adapter | Compatible with existing Backstage deployments |
| Security features | Native (core) | First-class, not plugins. Vulns, compliance, gates in the core |
| External integrations | Plugins | Slack, Jira, etc. as event-driven plugins |
| Database | PostgreSQL + JSONB | Scale, familiarity, ACID |
| Plugin Loading | Compile-time | Type safety, performance, security |
| Event Model | Post-persistence | Resilience, decoupling |
| Schema Evolution | Protobuf only | Zero migrations |
| Configuration | TOML + env vars | Flexibility, Rust-native |
| Deployment | Single binary + Docker + K8s | Cloud-native, minimal footprint |
References
Document Version History
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2025-01-03 | Initial comprehensive architecture document |
| 1.1 | 2025-01-15 | Added dual plugin architecture, generic utilities, updated roadmap |
| 2.0 | 2026-04-02 | Pivoted to security-native platform engineering tool. Extracted product vision to VISION.md. Added native vulnerability management, compliance frameworks to architecture. Updated roadmap and success criteria. |
This document covers architecture and technical decisions. For product vision and strategic direction, see VISION.md. All code and documentation must align with these documents.