///|
pub(all) enum RuntimeAuditSeverity {
RuntimeAuditInfo
RuntimeAuditWarning
RuntimeAuditError
} derive(Debug, Eq)
///|
pub(all) enum RuntimeAuditKind {
RuntimeAuditDeniedCommand
RuntimeAuditMissingHandler
RuntimeAuditUndeclaredHandler
RuntimeAuditOpenOrigin
} derive(Debug, Eq)
///|
pub struct RuntimeAuditFinding {
severity : RuntimeAuditSeverity
kind : RuntimeAuditKind
route : String
message : String
} derive(Debug, Eq)
///|
pub struct RuntimeRouteExposure {
route : String
mode : CommandMode
registered : Bool
exposed : Bool
windows : Array[String]
reasons : Array[String]
} derive(Debug, Eq)
///|
pub struct RuntimeAudit {
command_manifest : CommandManifest
permission_manifest : PermissionManifest
registered_routes : Array[String]
route_exposures : Array[RuntimeRouteExposure]
findings : Array[RuntimeAuditFinding]
} derive(Debug, Eq)
///|
pub fn RuntimeAuditSeverity::name(self : RuntimeAuditSeverity) -> String {
match self {
RuntimeAuditInfo => "info"
RuntimeAuditWarning => "warning"
RuntimeAuditError => "error"
}
}
///|
pub fn RuntimeAuditKind::name(self : RuntimeAuditKind) -> String {
match self {
RuntimeAuditDeniedCommand => "denied-command"
RuntimeAuditMissingHandler => "missing-handler"
RuntimeAuditUndeclaredHandler => "undeclared-handler"
RuntimeAuditOpenOrigin => "open-origin"
}
}
///|
pub fn RuntimeAuditFinding::new(
severity~ : RuntimeAuditSeverity,
kind~ : RuntimeAuditKind,
route~ : String,
message~ : String,
) -> RuntimeAuditFinding {
{ severity, kind, route, message }
}
///|
pub fn RuntimeAuditFinding::severity(
self : RuntimeAuditFinding,
) -> RuntimeAuditSeverity {
self.severity
}
///|
pub fn RuntimeAuditFinding::kind(
self : RuntimeAuditFinding,
) -> RuntimeAuditKind {
self.kind
}
///|
pub fn RuntimeAuditFinding::route(self : RuntimeAuditFinding) -> String {
self.route
}
///|
pub fn RuntimeAuditFinding::message(self : RuntimeAuditFinding) -> String {
self.message
}
///|
pub fn RuntimeAuditFinding::to_json(self : RuntimeAuditFinding) -> String {
[
"{",
"\"severity\":\{self.severity.name().json_string()},",
"\"kind\":\{self.kind.name().json_string()},",
"\"route\":\{self.route.json_string()},",
"\"message\":\{self.message.json_string()}",
"}",
].join("")
}
///|
fn RuntimeRouteExposure::from_entry(
entry : CommandManifestEntry,
registered_routes : Array[String],
check_handlers~ : Bool,
) -> RuntimeRouteExposure {
let reasons : Array[String] = []
let registered = registered_routes.contains(entry.route())
if entry.allowed_windows().is_empty() {
reasons.push("no-window-capability")
}
if !entry.mode().requires_registered_handler() {
reasons.push("event-route")
}
if check_handlers && entry.mode().requires_registered_handler() && !registered {
reasons.push("missing-handler")
}
let exposed = entry.mode().requires_registered_handler() &&
!entry.allowed_windows().is_empty() &&
(!check_handlers || registered)
if exposed {
reasons.push("exposed")
}
{
route: entry.route(),
mode: entry.mode(),
registered,
exposed,
windows: entry.allowed_windows(),
reasons,
}
}
///|
pub fn RuntimeRouteExposure::route(self : RuntimeRouteExposure) -> String {
self.route
}
///|
pub fn RuntimeRouteExposure::mode(self : RuntimeRouteExposure) -> CommandMode {
self.mode
}
///|
pub fn RuntimeRouteExposure::registered(self : RuntimeRouteExposure) -> Bool {
self.registered
}
///|
pub fn RuntimeRouteExposure::exposed(self : RuntimeRouteExposure) -> Bool {
self.exposed
}
///|
pub fn RuntimeRouteExposure::windows(
self : RuntimeRouteExposure,
) -> Array[String] {
self.windows.copy()
}
///|
pub fn RuntimeRouteExposure::reasons(
self : RuntimeRouteExposure,
) -> Array[String] {
self.reasons.copy()
}
///|
pub fn RuntimeRouteExposure::to_json(self : RuntimeRouteExposure) -> String {
[
"{",
"\"route\":\{self.route.json_string()},",
"\"mode\":\{self.mode.name().json_string()},",
"\"registered\":\{self.registered.json_bool()},",
"\"exposed\":\{self.exposed.json_bool()},",
"\"windows\":[\{self.windows.map(fn(window) { window.json_string() }).join(",")}],",
"\"reasons\":[\{self.reasons.map(fn(reason) { reason.json_string() }).join(",")}]",
"}",
].join("")
}
///|
pub fn RuntimeAudit::from_plan(plan : RuntimePlan) -> RuntimeAudit {
RuntimeAudit::build(plan, registered_routes=[], check_handlers=false)
}
///|
pub fn RuntimeAudit::from_registered_routes(
plan : RuntimePlan,
registered_routes : Array[String],
) -> RuntimeAudit {
RuntimeAudit::build(plan, registered_routes~, check_handlers=true)
}
///|
fn RuntimeAudit::build(
plan : RuntimePlan,
registered_routes~ : Array[String],
check_handlers~ : Bool,
) -> RuntimeAudit {
let command_manifest = plan.command_manifest()
let permission_manifest = plan.permission_manifest()
let registered_routes = sorted_unique_routes(registered_routes)
let route_exposures : Array[RuntimeRouteExposure] = []
let findings : Array[RuntimeAuditFinding] = []
for entry in command_manifest.entries() {
route_exposures.push(
RuntimeRouteExposure::from_entry(
entry,
registered_routes,
check_handlers~,
),
)
if entry.allowed_windows().is_empty() {
findings.push(
RuntimeAuditFinding::new(
severity=RuntimeAuditError,
kind=RuntimeAuditDeniedCommand,
route=entry.route(),
message="command is declared but no window capability grants access: \{entry.route()}",
),
)
} else if entry.allowed_origins().is_empty() {
findings.push(
RuntimeAuditFinding::new(
severity=RuntimeAuditInfo,
kind=RuntimeAuditOpenOrigin,
route=entry.route(),
message="command has no origin restriction: \{entry.route()}",
),
)
}
if check_handlers &&
entry.mode().requires_registered_handler() &&
!registered_routes.contains(entry.route()) {
findings.push(
RuntimeAuditFinding::new(
severity=RuntimeAuditError,
kind=RuntimeAuditMissingHandler,
route=entry.route(),
message="command is declared but no native handler is registered: \{entry.route()}",
),
)
}
}
if check_handlers {
let declared_routes = command_manifest.routes()
for route in registered_routes {
if !declared_routes.contains(route) {
findings.push(
RuntimeAuditFinding::new(
severity=RuntimeAuditWarning,
kind=RuntimeAuditUndeclaredHandler,
route~,
message="native handler is registered but no plugin command declares it: \{route}",
),
)
}
}
}
{
command_manifest,
permission_manifest,
registered_routes,
route_exposures,
findings,
}
}
///|
pub fn RuntimePlan::audit(self : RuntimePlan) -> RuntimeAudit {
RuntimeAudit::from_plan(self)
}
///|
pub fn RuntimePlan::audit_with_registered_routes(
self : RuntimePlan,
registered_routes : Array[String],
) -> RuntimeAudit {
RuntimeAudit::from_registered_routes(self, registered_routes)
}
///|
pub fn RuntimeAudit::command_manifest(self : RuntimeAudit) -> CommandManifest {
self.command_manifest
}
///|
pub fn RuntimeAudit::permission_manifest(
self : RuntimeAudit,
) -> PermissionManifest {
self.permission_manifest
}
///|
pub fn RuntimeAudit::registered_routes(self : RuntimeAudit) -> Array[String] {
self.registered_routes.copy()
}
///|
pub fn RuntimeAudit::findings(
self : RuntimeAudit,
) -> Array[RuntimeAuditFinding] {
self.findings.copy()
}
///|
pub fn RuntimeAudit::route_exposures(
self : RuntimeAudit,
) -> Array[RuntimeRouteExposure] {
self.route_exposures.copy()
}
///|
pub fn RuntimeAudit::exposed_routes(self : RuntimeAudit) -> Array[String] {
self.route_exposures
.filter(fn(exposure) { exposure.exposed() })
.map(fn(exposure) { exposure.route() })
}
///|
pub fn RuntimeAudit::findings_by_severity(
self : RuntimeAudit,
severity : RuntimeAuditSeverity,
) -> Array[RuntimeAuditFinding] {
self.findings.filter(fn(finding) { finding.severity() == severity })
}
///|
pub fn RuntimeAudit::errors(self : RuntimeAudit) -> Array[RuntimeAuditFinding] {
self.findings_by_severity(RuntimeAuditError)
}
///|
pub fn RuntimeAudit::warnings(
self : RuntimeAudit,
) -> Array[RuntimeAuditFinding] {
self.findings_by_severity(RuntimeAuditWarning)
}
///|
pub fn RuntimeAudit::infos(self : RuntimeAudit) -> Array[RuntimeAuditFinding] {
self.findings_by_severity(RuntimeAuditInfo)
}
///|
pub fn RuntimeAudit::error_count(self : RuntimeAudit) -> Int {
self.errors().length()
}
///|
pub fn RuntimeAudit::warning_count(self : RuntimeAudit) -> Int {
self.warnings().length()
}
///|
pub fn RuntimeAudit::info_count(self : RuntimeAudit) -> Int {
self.infos().length()
}
///|
pub fn RuntimeAudit::ok(self : RuntimeAudit) -> Bool {
self.error_count() == 0
}
///|
pub fn RuntimeAudit::problems(self : RuntimeAudit) -> Array[String] {
self.errors().map(fn(finding) { finding.message() })
}
///|
pub fn RuntimeAudit::to_json(self : RuntimeAudit) -> String {
[
"{",
"\"ok\":\{self.ok().json_bool()},",
"\"errorCount\":\{self.error_count()},",
"\"warningCount\":\{self.warning_count()},",
"\"infoCount\":\{self.info_count()},",
"\"registeredRoutes\":[\{self.registered_routes.map(fn(route) { route.json_string() }).join(",")}],",
"\"exposedRoutes\":[\{self.exposed_routes().map(fn(route) { route.json_string() }).join(",")}],",
"\"routeExposures\":[\{self.route_exposures.map(fn(exposure) { exposure.to_json() }).join(",")}],",
"\"findings\":[\{self.findings.map(fn(finding) { finding.to_json() }).join(",")}],",
"\"commandManifest\":\{self.command_manifest.to_json()},",
"\"permissionManifest\":\{self.permission_manifest.to_json()}",
"}",
].join("")
}
///|
fn sorted_unique_routes(routes : Array[String]) -> Array[String] {
let unique : Array[String] = []
for route in routes {
if !unique.contains(route) {
unique.push(route)
}
}
unique.sort()
unique
}
///|
fn CommandMode::requires_registered_handler(self : CommandMode) -> Bool {
match self {
Sync | Async | Stream => true
Event => false
}
}