SK

Semantic Kernel runtime authorization

Wrap Microsoft Semantic Kernel plugins and kernel functions with Veto. Each governed function invocation is evaluated before dispatch: allow, review, or deny, with an exportable decision record per governed decision.

Why Semantic Kernel needs guardrails

Microsoft Semantic Kernel exposes plugin functions to a model. When the kernel is configured with FunctionChoiceBehavior.Auto, the model autonomously selects functions and supplies arguments. The kernel executes them. The function attribute, parameter types, and description do not encode enforcement policy: they only describe shape.

Enterprise Semantic Kernel deployments often expose operations-grade plugins: kubectl, internal incident management, database admin. The kernel's automatic function-calling loop can compose a destructive sequence if a prompt injection steers it. Enforcement at the kernel function boundary is the reliable way to stop the side effect deterministically.

Auto function-calling

The kernel's auto behavior invokes plugin functions without a guardrail step. The model decides; the kernel runs.

Ops-grade plugins

Common Semantic Kernel plugins wrap kubectl, Azure CLI, and internal admin APIs. One destructive call costs uptime.

Evidence gap

Application Insights captures telemetry. Veto adds the decision record enterprises need for SOC 2.

Before and after Veto

The left tab shows a standard Semantic Kernel plugin with three kernel functions. The right tab adds Veto inside each function. Same kernel, same plugin, each governed call evaluated against policy first.

py
import os
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior

class IncidentPlugin:
    @kernel_function(description="Page the on-call engineer")
    def page_oncall(self, severity: str, message: str) -> str:
        return pagerduty.create_incident(severity=severity, summary=message)

    @kernel_function(description="Restart a production service")
    def restart_service(self, service_name: str, region: str) -> str:
        return k8s.rollout_restart(service_name, region)

    @kernel_function(description="Run a kubectl command")
    def kubectl(self, command: str) -> str:
        return shell.run(f"kubectl {command}")

kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(ai_model_id=os.environ["OPENAI_MODEL"]))
kernel.add_plugin(IncidentPlugin(), plugin_name="incidents")

settings = kernel.get_prompt_execution_settings_from_service_id("default")
settings.function_choice_behavior = FunctionChoiceBehavior.Auto()

# The model picks kubectl with command="delete deployment payments".
# Semantic Kernel calls the function. The deployment is gone.
result = await kernel.invoke_prompt(prompt=user_request, settings=settings)

Kernel-wide filters

Semantic Kernel exposes a filter API that wraps every function invocation. Register one Veto filter and every plugin in the kernel is guarded: no per-function edits required.

kernel_filter_with_veto.py
import os
from semantic_kernel.filters import FunctionInvocationContext, FilterTypes
from semantic_kernel import Kernel
from veto_sdk import Veto

veto = Veto(api_key=os.environ["VETO_API_KEY"])
kernel = Kernel()

@kernel.filter(FilterTypes.FUNCTION_INVOCATION)
async def veto_filter(context: FunctionInvocationContext, next):
    arguments = {k: v for k, v in context.arguments.items()}
    decision = veto.guard(
        tool=context.function.fully_qualified_name,
        arguments=arguments,
        context={"plugin": context.function.plugin_name},
    )
    if decision.decision == "deny":
        context.result = f"Blocked by policy: {decision.reason}"
        return
    if decision.decision == "require_approval":
        context.result = f"Awaiting approval (ID: {decision.approval_id})"
        return
    await next(context)

# Filters can wrap kernel function calls before dispatch.
# Plugins do not need per-function Veto code when using a filter.

Policy configuration

Author policies in declarative YAML. Match on the fully qualified function name (plugin.function) when using a kernel-wide filter, or on the unqualified function name when guarding inside the function body.

veto/policies.yaml
rules:
  - name: protect_production_restarts
    description: Production restarts need an SRE approval
    tool: restart_service
    when: context.environment == "production"
    action: require_approval
    approvers: [sre-oncall]
    timeout: 15m

  - name: block_destructive_kubectl
    description: Forbid delete and exec commands
    tool: kubectl
    when: "args.command.match(/^(delete|exec|patch|edit)\\b/)"
    action: deny
    message: "Destructive kubectl verbs require a human operator"

  - name: rate_limit_oncall_pages
    description: At most 5 on-call pages per hour
    tool: page_oncall
    when: context.hourly_pages >= 5
    action: deny
    message: "Page rate limit reached for this hour"

  - name: severity_allowlist
    description: Severity must be sev1-sev4
    tool: page_oncall
    when: "!['sev1', 'sev2', 'sev3', 'sev4'].includes(args.severity)"
    action: deny
    message: "Invalid severity: must be sev1, sev2, sev3, or sev4"

How Veto fits

1

Install the SDK

pip install veto-sdk semantic-kernel
2

Define policies

Create veto/policies.yaml. Match on the kernel function name. Set actions per plugin or per environment.

3

Guard with a filter or per-function

Register a kernel filter for blanket coverage, or call veto.guard() inside individual kernel functions for fine-grained control.

Use cases

SRE incident assistant

Kernel plugin that pages on-call and triggers runbooks. Rate-limit pages per hour, block destructive kubectl verbs, require approval before any production rollout-restart.

Microsoft 365 copilot extensions

Custom plugins that read mail and create Teams channels. Restrict recipient domains, block channels with external members, log each governed action with the user's identity.

Azure resource management

Plugins that wrap Azure CLI. Restrict to specific subscriptions, block resource deletion, require change approval for scaling decisions in prod.

Enterprise decision record

Veto emits a decision record for each governed kernel function call. Exportable to your SIEM. Pairs with Microsoft Purview for compliance evidence.

Frequently asked questions

Does Veto work with Semantic Kernel filters?
Yes. Register a single kernel filter that calls veto.guard() inside FilterTypes.FUNCTION_INVOCATION. Every plugin function in the kernel becomes guarded without per-function changes. You can also call veto.guard() inside individual kernel functions when you need plugin-specific context.
Does this work with the .NET and Java versions of Semantic Kernel?
Use the Veto Python or TypeScript SDKs where they fit. For .NET and Java Semantic Kernel deployments, call the Veto HTTP API directly from your kernel filter: it accepts the same tool name and arguments and returns the same decision shape.
Does Veto interact with Semantic Kernel's planner?
Planners produce a sequence of function calls that the kernel then executes. Veto guards each invocation as the planner runs it, so a denied step interrupts plan execution. The planner sees the denial as a normal function result and can adapt its next step.
Can I share policies between Semantic Kernel and other frameworks?
Yes. Policies match on tool name and arguments, not on framework identity. The same veto/policies.yaml file can cover Semantic Kernel functions, LangChain tools, and OpenAI function calls side by side.

Related integrations

Wrap one Semantic Kernel plugin path and inspect the decision record.