ObjectStackObjectStack

Plugin Security Advanced — Kernel Protocol

Defines comprehensive security mechanisms for plugin isolation, permission management, and threat protection in the ObjectStack ecosystem.

Plugin Security and Sandboxing Protocol

Defines comprehensive security mechanisms for plugin isolation, permission management, and threat protection in the ObjectStack ecosystem.

Features:

  • Fine-grained permission system
  • Resource access control
  • Sandboxing and isolation
  • Runtime security monitoring

⛔ NOT security scanning. The scan-result family — KernelSecurityScanResult, KernelSecurityVulnerability, PluginSecurityManifest.scanResults and its sibling list .vulnerabilities — was retired under ADR-0049 enforce-or-remove (#15932), after the runtime scanner that was its last type-only importer went the same way. That scanner retirement was issue 14919 — a number DELETED from the board and no longer resolving; its live record is PR #15930, which landed the removal. Nothing on this platform scans a plugin.

Source: packages/spec/src/kernel/plugin-security-advanced.zod.ts

TypeScript Usage

import { KernelSecurityPolicySchema, PermissionActionSchema, PermissionScopeSchema, PluginPermissionSchema, PluginPermissionSetSchema, PluginSecurityManifestSchema, PluginTrustLevelSchema, ResourceTypeSchema, RuntimeConfigSchema, SandboxConfigSchema } from '@objectstack/spec/kernel';
import type { KernelSecurityPolicy, PermissionAction, PermissionScope, PluginPermission, PluginPermissionSet, PluginSecurityManifest, PluginTrustLevel, ResourceType, RuntimeConfig, SandboxConfig } from '@objectstack/spec/kernel';

// Validate data
const result = KernelSecurityPolicySchema.parse(data);

KernelSecurityPolicy

Properties

PropertyTypeRequiredDescription
csp{ directives?: Record<string, string[]>; reportOnly?: boolean }optional
cors{ allowedOrigins: string[]; allowedMethods: string[]; allowedHeaders: string[]; allowCredentials?: boolean; … }optional
rateLimit{ enabled?: boolean; maxRequests: integer; windowMs: integer; strategy?: Enum<'fixed' | 'sliding' | 'token-bucket'> }optional
authentication{ required?: boolean; methods: Enum<'jwt' | 'oauth2' | 'api-key' | 'session' | 'certificate'>[]; tokenExpirationSeconds?: integer }optional
encryption{ dataAtRest?: boolean; dataInTransit?: boolean; algorithm?: string; minKeyLength?: integer }optional
auditLog{ enabled?: boolean; events?: string[]; retentionDays?: integer }optional

Nested Shape: KernelSecurityPolicy.cors

PropertyTypeRequiredDescription
allowedOriginsstring[]✅
allowedMethodsstring[]✅
allowedHeadersstring[]✅
allowCredentialsbooleanoptional (default: false)
maxAgeintegeroptionalPreflight cache duration in seconds (unit per CORS Access-Control-Max-Age (WHATWG Fetch))

Nested Shape: KernelSecurityPolicy.rateLimit

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)
maxRequestsinteger✅
windowMsinteger✅Time window in milliseconds
strategyEnum<'fixed' | 'sliding' | 'token-bucket'>optional (default: "sliding")

Nested Shape: KernelSecurityPolicy.authentication

PropertyTypeRequiredDescription
requiredbooleanoptional (default: true)
methodsEnum<'jwt' | 'oauth2' | 'api-key' | 'session' | 'certificate'>[]✅
tokenExpirationSecondsintegeroptionalToken expiration in seconds
tokenExpirationneveroptional[REMOVED] KernelSecurityPolicy.authentication.tokenExpiration was renamed to tokenExpirationSeconds in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose, and the sibling rate-limit window on this same policy is already windowMs. Rename the key to tokenExpirationSeconds; the value (seconds) is unchanged.

Nested Shape: KernelSecurityPolicy.encryption

PropertyTypeRequiredDescription
dataAtRestbooleanoptional (default: false)Encrypt data at rest
dataInTransitbooleanoptional (default: true)Enforce HTTPS/TLS
algorithmstringoptionalEncryption algorithm
minKeyLengthintegeroptionalMinimum key length in bits

Nested Shape: KernelSecurityPolicy.auditLog

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)
eventsstring[]optionalEvents to log
retentionDaysintegeroptionalLog retention in days
retentionneveroptional[REMOVED] KernelSecurityPolicy.auditLog.retention was renamed to retentionDays in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to retentionDays; the value (days) is unchanged.

PermissionAction

Type of action being permitted

Allowed Values

  • create
  • read
  • update
  • delete
  • execute
  • manage
  • configure
  • share
  • export
  • import
  • admin

PermissionScope

Scope of permission application

Allowed Values

  • global
  • tenant
  • user
  • resource
  • plugin

PluginPermission

Properties

PropertyTypeRequiredDescription
idstring✅Unique permission identifier
resourceEnum<'data.object' | 'data.record' | 'data.field' | 'ui.view' | 'ui.dashboard' | 'ui.report' | 'system.config' | 'system.plugin' | 'system.api' | 'system.service' | … +6 more>✅Type of resource being accessed
actionsEnum<'create' | 'read' | 'update' | 'delete' | 'execute' | 'manage' | 'configure' | 'share' | 'export' | 'import' | 'admin'>[]✅
scopeEnum<'global' | 'tenant' | 'user' | 'resource' | 'plugin'>optional (default: "plugin")Scope of permission application
filter{ resourceIds?: string[]; condition?: string | object; fields?: string[] }optional
descriptionstring✅
requiredbooleanoptional (default: true)
justificationstringoptionalWhy this permission is needed

Allowed Values: PluginPermission.resource

  • data.object
  • data.record
  • data.field
  • ui.view
  • ui.dashboard
  • ui.report
  • system.config
  • system.plugin
  • system.api
  • system.service
  • storage.file
  • storage.database
  • network.http
  • network.websocket
  • process.spawn
  • process.env

Nested Shape: PluginPermission.filter

PropertyTypeRequiredDescription
resourceIdsstring[]optional
conditionstring | { dialect: Enum<'cel' | 'cron' | 'template'>; source: string; ast?: any; meta?: object }optionalPredicate (CEL) filter, e.g. Precord.owner == os.user.id.
fieldsstring[]optionalAllowed fields for data resources

PluginPermissionSet

Properties

PropertyTypeRequiredDescription
permissions{ id: string; resource: Enum<'data.object' | 'data.record' | 'data.field' | 'ui.view' | 'ui.dashboard' | …>; actions: Enum<'create' | 'read' | 'update' | 'delete' | 'execute' | 'manage' | 'configure' | …>[]; scope?: Enum<'global' | 'tenant' | 'user' | 'resource' | 'plugin'>; … }[]✅
groups{ name: string; description: string; permissions: string[] }[]optional
defaultGrantEnum<'prompt' | 'allow' | 'deny' | 'inherit'>optional (default: "prompt")

Nested Shape: PluginPermissionSet.permissions[number]

PropertyTypeRequiredDescription
idstring✅Unique permission identifier
resourceEnum<'data.object' | 'data.record' | 'data.field' | 'ui.view' | 'ui.dashboard' | …>✅Type of resource being accessed
actionsEnum<'create' | 'read' | 'update' | 'delete' | 'execute' | 'manage' | 'configure' | …>[]✅
scopeEnum<'global' | 'tenant' | 'user' | 'resource' | 'plugin'>optional (default: "plugin")Scope of permission application
filter{ resourceIds?: string[]; condition?: string | object; fields?: string[] }optional
descriptionstring✅
requiredbooleanoptional (default: true)
justificationstringoptionalWhy this permission is needed

Nested Shape: PluginPermissionSet.groups[number]

PropertyTypeRequiredDescription
namestring✅Group name
descriptionstring✅
permissionsstring[]✅Permission IDs in this group

PluginSecurityManifest

Properties

PropertyTypeRequiredDescription
pluginIdstring✅
trustLevelEnum<'verified' | 'trusted' | 'community' | 'untrusted' | 'blocked'>✅Trust level of the plugin
permissions{ permissions: object[]; groups?: object[]; defaultGrant?: Enum<'prompt' | 'allow' | 'deny' | 'inherit'> }✅
sandbox{ enabled?: boolean; level?: Enum<'none' | 'minimal' | 'standard' | 'strict' | 'paranoid'>; runtime?: object; filesystem?: object; … }✅
policy{ csp?: object; cors?: object; rateLimit?: object; authentication?: object; … }optional
scanResultsneveroptional[REMOVED] PluginSecurityManifest.scanResults was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing on this platform ever produced, stored or read a security scan result, so an authored array parsed cleanly and changed nothing. Delete the key. There is no replacement key: plugin security scanning is not a platform capability. What this manifest still enforces is permissions and sandbox; artifact provenance is answered by the plugin signature verifier, which tells you an artifact is the one its publisher signed and never that it is safe. Audit dependencies with a tool built for it (npm audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV).
vulnerabilitiesneveroptional[REMOVED] PluginSecurityManifest.vulnerabilities was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it was an array of KernelSecurityVulnerability, the other half of the scan-result family, and leaves with it: nothing ever wrote the list and nothing ever read it, so declaring a known vulnerability against a plugin warned nobody and blocked no install. Delete the key. There is no replacement key: plugin security scanning is not a platform capability. Publish vulnerability contact and disclosure terms through the surviving securityContact and vulnerabilityDisclosure blocks on this same manifest, and audit dependencies with a tool built for it (npm audit, pnpm audit, Dependabot, the GitHub Advisory Database, OSV).
codeSigning{ signed: boolean; signature?: string; certificate?: string; algorithm?: string; … }optional
certifications{ name: string; issuer: string; issuedDate: string; expiryDate?: string; … }[]optional
securityContact{ email?: string; url?: string; pgpKey?: string }optional
vulnerabilityDisclosure{ policyUrl?: string; responseTimeHours?: integer; bugBounty?: boolean }optional

Nested Shape: PluginSecurityManifest.sandbox

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)
levelEnum<'none' | 'minimal' | 'standard' | 'strict' | 'paranoid'>optional (default: "standard")
runtime{ engine?: Enum<'v8-isolate' | 'wasm' | 'container' | 'process'>; engineConfig?: object; resourceLimits?: object }optionalExecution environment and isolation settings
filesystem{ mode?: Enum<'none' | 'readonly' | 'restricted' | 'full'>; allowedPaths?: string[]; deniedPaths?: string[]; maxFileSize?: integer }optional
network{ mode?: Enum<'none' | 'local' | 'restricted' | 'full'>; allowedHosts?: string[]; deniedHosts?: string[]; allowedPorts?: number[]; … }optional
process{ allowSpawn?: boolean; allowedCommands?: string[]; timeoutMs?: integer }optional
memory{ maxHeap?: integer; maxStack?: integer }optional
cpu{ maxCpuPercent?: number; maxThreads?: integer }optional
environment{ mode?: Enum<'none' | 'readonly' | 'restricted' | 'full'>; allowedVars?: string[]; deniedVars?: string[] }optional

Nested Shape: PluginSecurityManifest.certifications[number]

PropertyTypeRequiredDescription
namestring✅Certification name (e.g., SOC 2, ISO 27001)
issuerstring✅
issuedDatestring✅
expiryDatestringoptional
certificateUrlstringoptional

Nested Shape: PluginSecurityManifest.vulnerabilityDisclosure

PropertyTypeRequiredDescription
policyUrlstringoptional
responseTimeHoursintegeroptionalExpected response time in hours
responseTimeneveroptional[REMOVED] PluginSecurityManifest.vulnerabilityDisclosure.responseTime was renamed to responseTimeHours in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. This one is HOURS, while the same bare name on PluginHealthReport.metrics was milliseconds — which is the confusion the rule exists to remove. Rename the key to responseTimeHours; the value (hours) is unchanged.
bugBountybooleanoptional (default: false)

PluginTrustLevel

Trust level of the plugin

Allowed Values

  • verified
  • trusted
  • community
  • untrusted
  • blocked

ResourceType

Type of resource being accessed

Allowed Values

  • data.object
  • data.record
  • data.field
  • ui.view
  • ui.dashboard
  • ui.report
  • system.config
  • system.plugin
  • system.api
  • system.service
  • storage.file
  • storage.database
  • network.http
  • network.websocket
  • process.spawn
  • process.env

RuntimeConfig

Properties

PropertyTypeRequiredDescription
engineEnum<'v8-isolate' | 'wasm' | 'container' | 'process'>optional (default: "v8-isolate")Execution environment engine
engineConfig{ wasm?: object; container?: object; v8Isolate?: object }optional
resourceLimits{ maxMemory?: integer; maxCpu?: number; timeoutMs?: integer }optional

Nested Shape: RuntimeConfig.resourceLimits

PropertyTypeRequiredDescription
maxMemoryintegeroptionalMaximum memory allocation
maxCpunumberoptionalMaximum CPU usage percentage
timeoutMsintegeroptionalMaximum execution time in milliseconds
timeoutneveroptional[REMOVED] RuntimeConfig.resourceLimits.timeout was renamed to timeoutMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Its unit (milliseconds) lived in a source JSDoc only and the published description read "Maximum execution time", naming no unit at all, so a reader of the reference page could not tell 60000 milliseconds from 60000 seconds. Rename the key to timeoutMs; the value (milliseconds) is unchanged.

SandboxConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: true)
levelEnum<'none' | 'minimal' | 'standard' | 'strict' | 'paranoid'>optional (default: "standard")
runtime{ engine?: Enum<'v8-isolate' | 'wasm' | 'container' | 'process'>; engineConfig?: object; resourceLimits?: object }optionalExecution environment and isolation settings
filesystem{ mode?: Enum<'none' | 'readonly' | 'restricted' | 'full'>; allowedPaths?: string[]; deniedPaths?: string[]; maxFileSize?: integer }optional
network{ mode?: Enum<'none' | 'local' | 'restricted' | 'full'>; allowedHosts?: string[]; deniedHosts?: string[]; allowedPorts?: number[]; … }optional
process{ allowSpawn?: boolean; allowedCommands?: string[]; timeoutMs?: integer }optional
memory{ maxHeap?: integer; maxStack?: integer }optional
cpu{ maxCpuPercent?: number; maxThreads?: integer }optional
environment{ mode?: Enum<'none' | 'readonly' | 'restricted' | 'full'>; allowedVars?: string[]; deniedVars?: string[] }optional

Nested Shape: SandboxConfig.runtime

PropertyTypeRequiredDescription
engineEnum<'v8-isolate' | 'wasm' | 'container' | 'process'>optional (default: "v8-isolate")Execution environment engine
engineConfig{ wasm?: object; container?: object; v8Isolate?: object }optional
resourceLimits{ maxMemory?: integer; maxCpu?: number; timeoutMs?: integer }optional

Nested Shape: SandboxConfig.filesystem

PropertyTypeRequiredDescription
modeEnum<'none' | 'readonly' | 'restricted' | 'full'>optional (default: "restricted")
allowedPathsstring[]optionalWhitelisted paths
deniedPathsstring[]optionalBlacklisted paths
maxFileSizeintegeroptionalMaximum file size in bytes

Nested Shape: SandboxConfig.network

PropertyTypeRequiredDescription
modeEnum<'none' | 'local' | 'restricted' | 'full'>optional (default: "restricted")
allowedHostsstring[]optionalWhitelisted hosts
deniedHostsstring[]optionalBlacklisted hosts
allowedPortsnumber[]optionalAllowed port numbers
maxConnectionsintegeroptional

Nested Shape: SandboxConfig.process

PropertyTypeRequiredDescription
allowSpawnbooleanoptional (default: false)Allow spawning child processes
allowedCommandsstring[]optionalWhitelisted commands
timeoutMsintegeroptionalProcess timeout in ms
timeoutneveroptional[REMOVED] SandboxConfig.process.timeout was renamed to timeoutMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to timeoutMs; the value (milliseconds) is unchanged.

Nested Shape: SandboxConfig.memory

PropertyTypeRequiredDescription
maxHeapintegeroptionalMaximum heap size in bytes
maxStackintegeroptionalMaximum stack size in bytes

On this page