# Technical Architecture

<figure><img src="/files/dA2cWtS8ID5sHilwdVcH" alt=""><figcaption></figcaption></figure>

**Audience:** IT admins and technical staff. This page describes the **technical architecture** of Keeper Privilege Manager: how the system is **event-driven**, how **MQTT** and **HTTPS** are used, how **plugins** are loaded and monitored, and how **jobs** are launched and executed.

***

### Overview

Keeper Privilege Manager is a **central orchestration service** that runs on each endpoint. It does not perform policy evaluation or logging by itself; instead it:

* **Hosts an MQTT broker** and an **HTTP/HTTPS API** so that plugins and jobs can communicate.
* **Loads and monitors plugins** (separate processes such as KeeperPolicy, KeeperAPI, KeeperLogger) and can restart them if they fail.
* **Runs the job service**, which executes jobs when **events** fire (via MQTT), when **schedules** match, or when triggered **manually** via the API.

The system is **event-driven**: most workflows start when something **publishes a message** to the MQTT broker (e.g. “policy evaluation needed,” “launch elevation”). Subscribers (plugins or the job service) react to those messages and either handle the request or trigger a job that runs a sequence of tasks.

***

### High-Level Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                    KeeperPrivilegeManager (main service)                  │
├─────────────────────────────────────────────────────────────────────────┤
│  HTTP/HTTPS API (ports 6888 / 6889)  │  MQTT Broker (port 8675)         │
│  - Health, status, plugins, jobs     │  - Event routing                 │
│  - Settings, registration            │  - Plugin ↔ service messaging   │
│  - Plugin start/stop/restart         │  - Job triggers                 │
├─────────────────────────────────────────────────────────────────────────┤
│  Plugin orchestration        │  Job service                             │
│  - Load plugin JSON          │  - Event triggers (MQTT)                 │
│  - Launch plugin processes   │  - Schedule triggers (timer)            │
│  - Health checks             │  - Manual triggers (API)                │
│  - Auto-restart on failure   │  - Run tasks (executable, HTTP, etc.)   │
└─────────────────────────────────────────────────────────────────────────┘
         │                                          │
         │ launch & monitor                          │ execute
         ▼                                          ▼
┌──────────────────┐  ◄──── MQTT ────►  ┌──────────────────────────────────┐
│  Plugins         │                    │  Job executables / tasks          │
│  KeeperPolicy    │                    │  - Policy controls (MFA, etc.)    │
│  KeeperAPI      │                    │  - LaunchPrivilegeElevation        │
│  KeeperLogger   │                    │  - Inventory, risk, config         │
│  KeeperClient   │                    │  - Run as Service / User / HTTP    │
│  keeperAgent    │                    └──────────────────────────────────┘
│  ...            │
└──────────────────┘
```

* **Plugins** connect to the MQTT broker to receive and send messages. They may also call the HTTP API (e.g. to get settings, trigger a job, or launch a process).
* **Jobs** are triggered by events (MQTT), by schedule, or by API. When a job runs, it executes a list of **tasks** (run an executable, call HTTP, etc.). Task output can be merged into context so later tasks or routing decisions use it.

***

### Event-Driven Model

The system is **event-driven**: actions are triggered by **events** rather than by a single sequential program.

#### How events flow

1. **Something happens** on the endpoint (e.g. user requests elevation, a hook or agent detects it, or the service starts up).
2. A **message is published** to the MQTT broker on a specific **topic** (e.g. a policy evaluation request to the topic that KeeperPolicy subscribes to, or a custom event such as `PolicyEvaluationPending` or `LaunchPrivilegeElevation` to the topic the job service listens on).
3. **Subscribers** receive the message:
   * **Plugins** (e.g. KeeperPolicy) subscribe to topics and process requests (e.g. evaluate policy, return ALLOW/DENY/PENDING).
   * The **job service** subscribes (or is notified) when a **custom event** is published; it finds all jobs that list that event and **launches each matching job** with the event context.
4. **Jobs** run **tasks** in sequence. Tasks can publish more MQTT messages (e.g. send response back to a topic), call the HTTP API (e.g. launch elevated, get settings), or run executables (e.g. KeeperMFA, KeeperApproval). That can trigger **further events** (e.g. after controls complete, publish `LaunchPrivilegeElevation` so the launch job runs).

So one event leads to a job run; that job can publish another event, which leads to another job, and so on. Policy evaluation, controls (MFA, approval, justification), and launch flows are all wired this way.

#### Typical event names (custom events)

* **PolicyEvaluationPending** — A policy evaluation returned PENDING (controls required). Policy control jobs (e.g. privilege-elevation-policy-controls, file-access-policy-controls, default-policy-controls) subscribe to this and run MFA/justification/approval workflows.
* **LaunchPrivilegeElevation** — The user is allowed to elevate; the job that actually launches the process (LaunchPrivilegeElevation) is triggered with context (file path, command line, user, etc.).
* **Startup** — Fired when the service starts. Jobs such as registration or cleanup subscribe to this.
* **LaunchApprovedRequest**, **ShowAgent**, and others — Used by the UI or workflow to trigger specific jobs.

Events can carry **context** (parameters) that is passed into the job as its initial context so tasks can use it (e.g. `{RequestId}`, `{FilePath}`, `{UserName}`).

***

### How MQTT is Used

#### Role of the MQTT Broker

The main service runs an **embedded MQTT broker** (default port **8675**, localhost only). It is the **message bus** for the product:

* **Plugins** connect to the broker as MQTT clients. Each plugin has a **subscription** configuration (topic(s) to subscribe to) and **publish** permissions (topic(s) it can publish to).
* **Process-based security:** Only processes that were **launched by KeeperPrivilegeManager** (and that present a valid certificate) are allowed to connect. This prevents arbitrary applications from publishing or subscribing.
* **Topic-based routing:** Messages are delivered to every client that is subscribed to that topic. So when someone publishes to **EventMessages** (or the topic used for custom events), the job service receives it and can trigger jobs; when someone publishes to **KeeperPolicy**, the KeeperPolicy plugin receives it and evaluates policy.

#### Typical Topics (conceptual)

* **KeeperPolicy** — Policy evaluation requests (in); policy responses (out).
* **KeeperLogger** — Log messages (plugins and jobs publish here so logs are centralized).
* **KeeperApi** — Backend sync, registration, audit (KeeperAPI plugin).
* **EventMessages** (or equivalent) — Custom events that trigger jobs (e.g. PolicyEvaluationPending, LaunchPrivilegeElevation). The job service listens and matches event name to job definitions.
* **JobService** — Job-related messages (e.g. job completion, trigger).
* **keeperAgent**, **KeeperClient**, **RequestApproval**, etc. — Used by UI and approval workflows.

Exact topic names and which plugin publishes/subscribes to what are defined in each plugin’s JSON (`Subscription`, `metadata.mqttTopics`). The important point for architecture is: **all inter-component communication for events and policy flows over MQTT** on localhost, with process and topic checks.

#### MQTT and Event-Driven Jobs

When a **custom event** must trigger a job:

1. Some component (e.g. KeeperPolicy or another job) **publishes** a message to the event topic, with **event name** and **payload** (context).
2. The **job service** receives the message and calls **TriggerJobByEvent(eventName, context)** (or equivalent).
3. Every **job** whose `events` array contains that event name (e.g. `customEvent: "LaunchPrivilegeElevation"`) is **matched** and **started** with that context.
4. Each such job runs its **task list**; tasks can in turn publish to MQTT or call the HTTP API, producing further events or side effects.

So MQTT is used both for **request/response** (e.g. policy request → KeeperPolicy → response) and for **fire-and-forget event** delivery to the job service to **launch jobs**.

***

### How HTTPS (and HTTP) is Used

#### HTTP/HTTPS API Server

The main service exposes an **HTTP/HTTPS API** (Kestrel) bound to **localhost only** (127.0.0.1):

* **HTTP** default port **6888**
* **HTTPS** default port **6889** (recommended; uses a self-signed certificate for localhost)

The API is used for:

* **Health and status** — `GET /health`, `GET /`, `GET /api/system/status` (no auth; monitoring and scripts).
* **Plugin management** — Start/stop/restart plugins, list plugins (Admin or Plugin auth depending on endpoint).
* **Job management** — List jobs, run a job, trigger a job with context, validate job JSON (Plugin or Admin).
* **Settings** — Read/update application and plugin settings; revert plugin settings from JSON (Admin for write).
* **Registration** — Register or unregister the agent with the Keeper backend (Admin or as configured).
* **Other operations** — File access, user session launch, ephemeral account, audit, etc., as documented in [Local management endpoints](/keeperpam/endpoint-privilege-manager/reference/local-endpoints.md).

#### Who Calls the API

* **Plugins** — To load their settings (`GET /api/PluginSettings/{pluginName}`), to trigger jobs (`POST /api/Jobs/{id}/trigger`), or to call launch/ephemeral endpoints. They use the HTTPS base URL (e.g. from config or parameter).
* **Job tasks** — **HTTP tasks** inside a job call arbitrary URLs (often the local API) to trigger jobs, launch processes, create execution grants, or revert settings. **Service** tasks that run executables (e.g. KeeperMFA, RedirectEvaluator) may receive the API base URL as an argument so the executable can call back.
* **Admins / scripts** — Management scripts or operators call the API (with Admin privileges or client certificate as required) to check health, restart plugins, run jobs, or change settings.

**Authorization levels** (conceptual): **Public** (health, status), **Plugin** (process launched by KPM + certificate), **Admin** (elevated user or trusted process + certificate). So HTTPS is used for **synchronous** management and integration; MQTT is used for **asynchronous** event and message delivery between plugins and the job system.

***

### How Plugins are Loaded and Monitored

#### Loading Plugins

1. **Discovery** — At startup, the main service scans the **Plugins** directory for **plugin JSON files** (e.g. `KeeperPolicy.json`, `KeeperApi.json`). Each file describes one plugin: id, name, executable path, subscription topic(s), startup priority, autoStart, and other metadata.
2. **Validation** — The service validates the plugin (e.g. path security, certificate if required). Plugins that fail validation are not started.
3. **Startup order** — Plugins are ordered by **startupPriority** (lower number = start first). **autoStart: true** plugins are started in that order; **autoStart: false** plugins are loaded but not started until requested (e.g. via API or on demand).
4. **Launch** — For **Executable** plugins, the service launches the plugin process (the path in **executablePath**). The process connects to the MQTT broker and, typically, to the HTTP API to load settings. The plugin then subscribes to its configured topics and processes messages.

So **plugins are loaded from JSON**; they are **not** “registered” via API at runtime—the JSON files are the source of truth at startup. To add a plugin, you add its JSON (and binaries) and restart the service (or start the plugin via API if it has autoStart: false).

#### Monitoring and Restart

* **Health checks** — The main service periodically checks whether each plugin process is still running and optionally checks liveness (e.g. via a simple ping or health callback). Interval and behavior are configurable (e.g. in PluginMonitoring in appsettings).
* **Auto-restart** — If a plugin has **autoRestart: true** and the monitoring detects that it has exited or is unhealthy, the service can **automatically restart** it. This keeps critical plugins (e.g. KeeperPolicy, KeeperLogger) running.
* **Manual control** — An admin can **stop** or **restart** a plugin via the HTTP API (`POST /api/plugins/{name}/stop`, `POST /api/plugins/{name}/restart`) without restarting the whole service.

So: **plugins are loaded from Plugins/\*.json, started in priority order, and monitored; failed plugins can be restarted automatically or manually via the API.**

***

### How Jobs are Launched and Executed

#### Job Definition and Registration

* **Jobs** are defined by **JSON files** in the **Jobs** directory. Each file has an **id**, **events** (optional), **schedule** (optional), **parameters**, **tasks**, **condition**, and optional **alternateJobId**.
* At startup (and when files change, depending on implementation), the **job service** loads all job JSONs, validates them, and **registers** each job. A job is “launched” when one of its **triggers** fires.

#### Trigger Types

Jobs are **launched** when one of these happens:

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="129.66668701171875">Trigger type</th><th>How it fires</th><th>Job matches when</th></tr></thead><tbody><tr><td><strong>Event</strong></td><td>A message is published to the event topic with an <strong>event name</strong> (e.g. PolicyEvaluationPending, LaunchPrivilegeElevation). The job service calls <strong>TriggerJobByEvent(eventName, context)</strong>.</td><td>The job’s <strong>events</strong> array contains an event with that name (e.g. <code>eventType: "Custom"</code>, <code>customEvent: "LaunchPrivilegeElevation"</code>).</td></tr><tr><td><strong>Schedule</strong></td><td>An internal <strong>timer</strong> runs (e.g. every minute or per cron). The job service evaluates each job’s <strong>schedule</strong> (interval, cron, runAt, calendar).</td><td>The current time matches the job’s schedule.</td></tr><tr><td><strong>Manual</strong></td><td>An admin or script calls <strong>POST /api/Jobs/{jobId}/run</strong> or <strong>POST /api/Jobs/{jobId}/trigger</strong> with optional body.</td><td>The trigger’s job id matches the job’s <strong>id</strong>.</td></tr></tbody></table>

A job can have both **events** and **schedule**; it runs whenever **either** an event matches **or** the schedule matches.

#### Job Execution Flow (high level)

1. **Trigger fires** → Job service finds all jobs that match (by event name, schedule, or job id).
2. For each matching job (often one), the service **starts** the job run with the **trigger context** (event payload, parameters, defaults).
3. **Job-level condition** (if defined) is evaluated. If it fails, the job is skipped (or **alternateJobId** is run). If it passes, the **task loop** runs.
4. **Task loop** — For each task in order:
   * **Task condition** (if defined) is evaluated against the current **context** (trigger context + parameters + outputs from previous tasks). If false, the task is **skipped** and the next task in list order runs.
   * If the task references a **plugin setting** (e.g. “skip when metadata.redirect.enabled is false”), that is evaluated; if the setting says skip, the task’s command is not run and optional **ContextWhenSkipped** is merged into context.
   * The task is **executed**:
     * **Service** — Run an executable in the service session (e.g. RedirectEvaluator, KeeperMFA). Output (e.g. JSON on stdout) can be merged into context for later tasks.
     * **HTTP** — Issue an HTTP/HTTPS request (e.g. to the local API to launch a process or trigger another job). No executable.
     * **User / UserDesktop / UserElevated** — Run in user session (non-elevated, desktop, or elevated).
   * **Routing** — On success, the next task can be **OnSuccess** (explicit task id) or the next in list. On failure, the next can be **OnFailure** or the next in list; if **ContinueOnFailure** is false, the job stops. If a task is skipped, the next is always the **next in list**.
5. When the task list is done (or a task routes to “stop”), the job run **completes**. No task runs after that unless the job is triggered again.

So **jobs are launched by events (MQTT), schedule (timer), or API**. Each run executes a **sequence of tasks** with optional conditions and routing; tasks can run executables, call HTTP, or run in user context, and can publish MQTT or call the API to trigger further jobs or actions.

#### Task Execution Types (summary)

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="137.6666259765625">Type</th><th width="246.3333740234375">Where it runs</th><th>Typical use</th></tr></thead><tbody><tr><td><strong>Service</strong></td><td>Service session (no user desktop)</td><td>Back-end executables (RedirectEvaluator, risk evaluators), scripts, built-in commands (echo, publish-mqtt).</td></tr><tr><td><strong>Http</strong></td><td>HTTP client</td><td>Call local API or external URL (trigger job, launch elevated, revert settings).</td></tr><tr><td><strong>User</strong></td><td>User session, non-elevated</td><td>Process in user context.</td></tr><tr><td><strong>UserDesktop</strong></td><td>User’s desktop, non-elevated</td><td>UI apps (KeeperMFA, KeeperJustification, KeeperApproval) that need to show dialogs.</td></tr><tr><td><strong>UserElevated</strong></td><td>User context, elevated</td><td>Run a process elevated in the user session.</td></tr></tbody></table>

***

### End-to-End Flow Example: Privilege Elevation

To tie MQTT, HTTPS, plugins, and jobs together:

1. **User** requests elevation (e.g. right-click “Run as administrator” on an app). The OS or an agent (e.g. KeeperRunAs, hook) captures this and needs a policy decision.
2. **Request to KeeperPolicy** — The component that needs the decision **publishes a message** to the **KeeperPolicy** topic (or equivalent) with context (user, machine, application, command line, event type).
3. **KeeperPolicy** (plugin) receives the message, evaluates policies, and decides **ALLOW**, **DENY**, or **PENDING** (controls required). It **publishes the response** (e.g. to a response topic or EventMessages).
4. If the result is **PENDING**, the job service (or the component that published the event) **triggers** the **PolicyEvaluationPending** event with context (ControlList, SessionId, RespondToTopic, etc.). The **privilege-elevation-policy-controls** (or similar) job **matches** and **runs**.
5. That job runs **tasks**: e.g. PendingApprovals check, then **KeeperMfa** / **KeeperJustification** / **KeeperApproval** (UserDesktop tasks), then **publish-mqtt** to send audit and, when controls succeed, **publish** a **LaunchPrivilegeElevation** event with context (FilePath, CommandLine, UserName, etc.).
6. The **LaunchPrivilegeElevation** job **matches** that event and runs. Its tasks may include **check-redirect** (reads RedirectEvaluator setting; runs RedirectEvaluator executable if redirect enabled), then either **launch-substitute** (HTTP call to launch the substitute exe elevated) or **launch-elevated** (HTTP call to launch the requested exe elevated), then **publish-mqtt** to send the final response (e.g. DidElevate or Deny) to the caller.
7. The **caller** (e.g. hook or agent) receives the response and allows or blocks the user action accordingly.

Throughout, **MQTT** carries the events and request/response messages; **HTTPS** is used for plugin settings, job triggers, and launch API calls; **plugins** are loaded from JSON and monitored; **jobs** are triggered by events (or schedule/manual) and run tasks that can invoke executables and HTTP.

***

### Summary

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="134.6666259765625">Topic</th><th>Summary</th></tr></thead><tbody><tr><td><strong>Event-driven</strong></td><td>Actions are driven by <strong>events</strong> (MQTT messages). Policy requests, control workflows, and launch flows are all triggered by publishing to the broker; subscribers (plugins, job service) react and run logic or jobs.</td></tr><tr><td><strong>MQTT</strong></td><td>Embedded broker on port 8675 (localhost). Used for policy request/response, custom events (job triggers), logging, and inter-plugin messaging. Process and topic restrictions apply.</td></tr><tr><td><strong>HTTPS</strong></td><td>Kestrel on ports 6888 (HTTP) and 6889 (HTTPS), localhost only. Used for health, plugin/job management, settings, registration, launch, and other API operations.</td></tr><tr><td><strong>Plugins</strong></td><td>Loaded from <strong>Plugins/*.json</strong> at startup; started by priority; <strong>monitored</strong> and <strong>auto-restarted</strong> if configured. They connect to MQTT and optionally call the HTTP API.</td></tr><tr><td><strong>Jobs</strong></td><td>Defined in <strong>Jobs/*.json</strong>; <strong>launched</strong> by <strong>events</strong> (MQTT), <strong>schedule</strong> (timer), or <strong>manual</strong> (API). Each run executes a <strong>task list</strong> (Service, HTTP, User, UserDesktop, UserElevated) with conditions and routing.</td></tr></tbody></table>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.keeper.io/keeperpam/endpoint-privilege-manager/reference/technical-architecture.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
