with one click
api-authz
Kibana API route authorization patterns. Use when configuring route security, working with requiredPrivileges, using authzResult for privilege-based branching, opting out of authorization, or naming custom privileges.
Menu
Kibana API route authorization patterns. Use when configuring route security, working with requiredPrivileges, using authzResult for privilege-based branching, opting out of authorization, or naming custom privileges.
| name | api-authz |
| description | Kibana API route authorization patterns. Use when configuring route security, working with requiredPrivileges, using authzResult for privilege-based branching, opting out of authorization, or naming custom privileges. |
All API routes in Kibana must have authorization checks. Authorization is not optional, even for
internalroutes.
Routes declare authorization via the security option in KibanaRouteOptions:
router.get({
path: '/api/path',
security: {
authz: {
requiredPrivileges: ['<privilege_1>', '<privilege_2>'],
},
},
...
}, handler);
Privilege names follow the <operation>_<subject> convention using underscores only.
| Incorrect | Why | Correct |
|---|---|---|
read-entity-a | Uses - instead of _ | read_entity_a |
delete_entity-a | Mixes _ and - | delete_entity_a |
entity_manage | Subject before operation | manage_entity |
authzResultWhen a route handler branches logic based on user privileges (returns different data, enables different features), it must use request.authzResult. Do not use capabilities.resolveCapabilities() or other authorization checks for branching — authzResult is the single source of truth.
Look for: routes with anyRequired (OR logic), handlers that conditionally expose data based on permissions, or functions that check capabilities and return booleans for branching.
Correct — use authzResult:
router.get({
path: '/api/path',
security: {
authz: {
requiredPrivileges: ['privilege_3', { anyRequired: ['privilege_1', 'privilege_2'] }],
},
},
...
}, (context, request, response) => {
const authzResult = request.authzResult;
// { "privilege_3": true, "privilege_1": true, "privilege_2": false }
if (authzResult.privilege_1) {
return response.ok({ body: ... });
} else if (authzResult.privilege_2) {
return response.ok({ body: ... });
}
return response.ok({ body: { data: ... } });
});
Wrong — using capabilities for authorization branching:
const canReadDecryptedParams = async (routeContext: RouteContext) => {
const { request, server } = routeContext;
const capabilities = await server.coreStart.capabilities.resolveCapabilities(request, {
capabilityPath: 'my_capability.*',
});
return capabilities.my_capability?.canReadParams ?? false;
};
if (await canReadDecryptedParams(routeContext)) {
return getDecryptedParams(routeContext, paramId);
} else {
return getBasicParams(routeContext, paramId);
}
Fix: declare both privileges in the route config with anyRequired and branch on request.authzResult:
router.get({
path: '/api/params',
security: {
authz: {
requiredPrivileges: [{ anyRequired: ['read_params_decrypted', 'read_params'] }],
},
},
}, (context, request, response) => {
if (request.authzResult.read_params_decrypted) {
return getDecryptedParams(routeContext, paramId);
} else {
return getBasicParams(routeContext, paramId);
}
});
When a route must opt out, use the predefined AuthzOptOutReason enum or AuthzDisabled helpers from @kbn/core-security-server:
import { AuthzDisabled, AuthzOptOutReason } from '@kbn/core-security-server';
// Predefined helper
router.get({
path: '/api/path',
security: { authz: AuthzDisabled.delegateToSOClient },
...
}, handler);
// Predefined enum
router.get({
path: '/api/path',
security: {
authz: { enabled: false, reason: AuthzOptOutReason.DelegateToSOClient },
},
...
}, handler);
// Custom reason — only when no predefined reason applies
router.get({
path: '/api/health',
security: {
authz: {
enabled: false,
reason: 'This route is a health check endpoint that returns no sensitive information',
},
},
...
}, handler);
Invalid opt-out reasons — flag these:
"Opt out from authorization" — too generic, no context"This route does not need authorization" — no explanation why"Authorization not required" — no context provided"Authorization is delegated to SO Client" — use AuthzOptOutReason.DelegateToSOClient insteadRegister and implement custom workflow triggers from an external Kibana plugin using `@kbn/workflows-extensions`. Use when adding or modifying an event-driven trigger with `registerTriggerDefinition`, designing `eventSchema` Zod schemas, writing `documentation` and KQL `snippets`, wiring `emitEvent` via request context or `getClient`, choosing sync vs async public loader registration, updating `APPROVED_TRIGGER_DEFINITIONS`, or reviewing PRs that touch any of these. Always ask for the user's plugin id first to locate the correct plugin and file paths.
Register and roll out managed workflows from a Kibana plugin using `@kbn/workflows-extensions` and `@kbn/workflows/managed`. Use when adding or modifying a code-owned workflow definition, `registerManagedWorkflowOwner`, `initManagedWorkflowsClient`, `install` / `uninstall` / `ready`, choosing `lifecycle` / `versionStrategy` / `enablement`, authoring `yaml` vs `yamlTemplate`, space-scoped vs global installs, `getWorkflowStatus`, or `execute`, or reviewing PRs that touch managed workflow definitions or rollout. Always ask for the user's plugin id first to locate the correct plugin and definition file paths.
Implement and quality-check OpenTelemetry metric instrumentation in Kibana code that uses `@kbn/metrics`. Use whenever the user wants to add, change, or review OTel metrics — including any call to `metrics.getMeter`, `meter.createCounter`/`createUpDownCounter`/`createGauge`/`createHistogram`/`createObservable*`/`addBatchObservableCallback`, edits to `kibana.yml` `telemetry.metrics` config, or questions like "is this metric well-designed?", "what should I name this counter?", or "which instrument type is right here?". Trigger this skill even when the user does not say "OTel" or "OpenTelemetry" but is clearly adding observability to Kibana server code and already knows what they want to measure.
Primary guided playbook for Elasticsearch search in Kibana Agent Builder: intent → data → mapping → Dev Tools API snippets (SENSE), with one question at a time. Load this skill whenever the user wants to learn Elasticsearch search, get started, begin building, take first steps, onboard, follow a walkthrough or tutorial, go from zero to a working query, or get structured help setting up indices and search — including casual openers like hi, help, getting started, new to Elasticsearch, how do I build search, or I want to try search. Use when they need end-to-end onboarding, not a single narrow API answer. If they only ask what they can build with Elastic (exploration without the full playbook), prefer invoking /use-case-library first; you can still load this skill afterward for the guided build.
Topic-driven, hands-on Elasticsearch tutorial flow that runs in Kibana Dev Console. Use whenever the user says "walk me through", "give me a tutorial for", "teach me", "show me how X works", "tutorial on", or similar topical learning intent — and they are NOT asking you to build their real, specific use case. Topics are open-ended: any Elasticsearch / Kibana search concept the user names (e.g. mappings, analyzers, bool queries, semantic_text, kNN, RRF, aggregations, ingest pipelines, reranking, data streams, ES|QL). Tutorials use sample data on isolated resources, present every step as a SENSE snippet to run in Dev Tools, and end with cleanup plus pointers to docs and the onboarding / pattern skills.
GitHub interactions via gh CLI for the Kibana repo. Use when performing any GitHub interaction — creating, viewing, or modifying PRs or issues, posting comments or reviews, checking CI status, applying labels, creating releases, or making any gh/API call.