Skip to content

Control utility (egctl)

egctl is the in-container command-line utility for the EnforceGate engine. It speaks to the engine's Control API over HTTPS and provides a Cisco-style verb hierarchy familiar to network and system administrators.

The binary is /usr/local/bin/egctl inside the enforcegate container. The shipped egctl.conf configures it to talk to the local engine on 127.0.0.1:11225. The recommended way to reach it from the host is the eghost cli operator command, which drops into an interactive egctl REPL with admin credentials already plumbed:

eghost cli
Control API specification

The Control API specification is currently proprietary. We plan to publish it once it reaches a production-ready stable state. As of this writing the API is still evolving and is not yet considered stable for public release.

Two front-ends, one set of verbs

egctl exposes the same operations through two front-ends:

  • nicli — non-interactive, scriptable. Each verb is a hyphenated subcommand typed directly after egctl:

    egctl <verb> [args...]
    

    Use from shell scripts, CI pipelines, automation, and docker exec one-liners. Output is plain text; exit code is zero on success.

  • libexocli — interactive REPL with tab completion, contextual help (?), command history, and a three-mode Cisco / Juniper Junos / PAN-OS-style flow: Operational (host>), Privileged (host#), and Configuration (host(config)#). See Modes below for what each carries and how to switch.

    host> show <subject>                 # Operational — read-only verbs
    host> enable                         # raise to Privileged (Administrator+ only)
    host# request <action> [args...]     # Privileged — destructive verbs
    host# configure                      # enter staged-edit mode (shorthand for configure terminal)
    host(config)# edit policy <name>     # Configuration — stage / commit / revert
    

    Reached via egctl --cli (or eghost cli on a deployed bundle, which prompts for credentials host-side and then enters the REPL at Operational host>).

The mapping between the two forms is one-to-one for the verbs. What differs is the modal gating: in libexocli, destructive request * verbs require enable first; in nicli, every verb is a flat one-shot and no mode switching is involved — your scripts, CI pipelines, and the eghost wrapper continue to work without changes. Examples below show the libexocli form (because that is what eghost cli opens) with the nicli equivalent in a callout where it differs meaningfully.

Modes

The libexocli interactive REPL exposes three modes, mirroring the Cisco / Juniper Junos / PAN-OS conventions network and system administrators already know. The modes apply to the interactive egctl --cli / eghost cli path onlynicli flat one-shot verbs (egctl <verb> ...) are unaffected and stay in their always-allowed scripted form.

Mode Prompt Carries Enter via
Operational host> Most show * verbs (engine status, policy introspection, system introspection), show policy match <url>, learning * verbs, and request neighbor teardown. Read-only and diagnostic surface. show users is the exception — it is gated to Privileged. Default mode on session start.
Privileged host# Destructive request * verbs — policy reload / rollback, license reload, URI engine restart, user management — plus show users. enable from Operational. Administrator (10) or above required.
Configuration host(config)# Staged-edit verbs — edit policy <name>, show policy diff, commit, revert, end. configure or configure terminal from Privileged.

enable

Raise the session from Operational to Privileged. Requires the authenticated user to be at Administrator (10) or above.

host> enable
host#

A Monitoring (1) or Standard (2) user sees the refusal and stays at host>:

host> enable
% Privilege denied  administrator rights required (you are at 2, admin threshold 10)
host>

The privilege check uses the engine's Control API; non-administrator operators keep their full show * surface in Operational and can use scripted nicli one-shots for anything the server-side privilege thresholds let them call.

disable / exit / quit

exit and quit are mode-aware and reliable from every mode — operators cannot get stranded in the REPL. quit is a hard alias of exit (Cisco IOS convention; the two are identical).

Mode What exit / quit does
Operational End the REPL session and return to the host shell.
Privileged Pop back to Operational.
Configuration Fully unwind to Privileged (discards no on-disk edits; use revert for that).

disable from Privileged is equivalent to exit from Privileged — pops back to Operational. end from Configuration unwinds to Privileged without committing or reverting (the on-disk edits persist but aren't loaded until a manual reload).

host# disable
host>
host(config)# end           # leaves Configuration without commit/revert; edits persist on disk but aren't loaded
host#
host> exit                  # ends the REPL session

configure / configure terminal

Enter Configuration mode from Privileged. The engine snapshots the current rules.d/ state as the candidate baseline — show policy diff and revert use this snapshot to compute / restore. configure is the short alias; both forms are equivalent and both appear in ? listings. See Staged edits for the full edit / commit / revert flow.

host# configure terminal
host(config)#

Re-entry is refused — once you are in Configuration mode, running configure terminal again returns:

host(config)# configure terminal
% configure terminal: already in configuration mode
host(config)#

Command shape

The list of available subcommands:

$ docker exec enforcegate egctl help
egctl sub-commands:
  show-status                          Display EnforceGate status
  show-version                         Display EnforceGate version information
  show-license                         Display licensed edition, expiry, connector counts
  show-clock                           Display engine wall time (UTC + local + epoch)
  show-tech-support                    Aggregate paste-into-ticket bundle of ten show-* outputs
  show-neighbor [N|<id>]               Display neighbor(s) — all, by entry-num, or by ID
  show-neighbor-detail [N|<id>]        Display neighbor(s) sessions details
  show-database-status                 Display database connection status
  show-users                           Display engine user accounts
  show-policy-summary                  Display one-glance overview — default action, rule + host totals, dirs
  show-policy-list                     Display loaded rules — id, name, action, type, source file, counts
  show-policy-detail <name>            Display per-rule detail (pattern, description, host counts, source file)
  show-policy-files                    Display .policy files under [policy].path — size, mtime, rule counts
  show-policy-file <name>              Display the raw contents of one .policy file
  show-policy-domain-lists             Display match-domain-list paths, per-file line counts, aggregate hashset size
  show-policy-backups                  Display policy snapshots available for rollback
  show-policy-match <url>              Dry-run a URL through the live policy engine
  show-policy-learning-sessions        Display all learning sessions
  show-policy-learning-session <id>    Display one learning session and its captured URIs
  show-system-uri-engine               Display URI scanner state — regex / domain backend / size
  show-system-uptime                   Display engine uptime
  show-system-memory                   Display process and URI-engine memory consumption
  show-system-version                  Display engine + runtime versions
  show-system-threads                  Display thread pool sizes + active connector sessions
  show-system-listeners                Display engine listeners (Control API, Defendr, captive portal)
  show-system-logs [N]                 Tail last N lines of the configured log file
  request-neighbor-teardown [N|<id>]   Request neighbor(s) session(s) teardown — all, by entry-num, or by ID
  request-system-uri-engine-restart    Request restart of the URI scanning engine
  request-system-reboot                Graceful engine shutdown (orchestrator restarts) — Super-Administrator only
  request-policy-reload [--dry-run]    Request reload of the policy graph (--dry-run to validate)
  request-policy-rollback              Restore policy from a previous snapshot
  request-license-reload               Re-read the license file from disk and atomically swap
  request-user-add                     Add a new user
  request-user-passwd <user>           Change a user's password
  request-user-remove <user>           Remove a user
  request-policy-learning-create <kind> <value> <uri-cap> [--keep-query-strings]
                                       Provision a learning session
  request-policy-learning-start <id>   Activate the session
  request-policy-learning-stop <id>    Stop the session
  request-policy-learning-delete <id>  Delete the session + its captured URIs
  request-policy-learning-analyze <id> <action> [--no-stats]
                                       Synthesise a .policy document from captures

Command naming convention

Commands that display information are prefixed with show. Commands that perform actions are prefixed with request. Learning-mode verbs are an exception kept short for ergonomics.

Verb renames in libexocli

Several historical verbs were folded into the canonical namespaces during the 2026.21.0 → 2026.24.x window. Every old form continues to dispatch for muscle-memory compatibility — they are hidden from ? listings but still callable, and scripts using the nicli flat forms are not affected. Use the canonical names in new operator scripts and documentation:

Old (still callable, hidden from ?) Canonical
show database-status show database status
test-policy-uri <url> show policy match <url>
request engine restart / request uriengine restart request system uri-engine restart
show learning sessions show policy learning sessions
show learning session <id> show policy learning session <id>
request learning create … request policy learning create …
request learning start <id> request policy learning start <id>
request learning stop <id> request policy learning stop <id>
request learning delete <id> request policy learning delete <id>
request learning analyze <id> <action> request policy learning analyze <id> <action>
show enforcegate <verb> (two-word prefix) bare <verb> form

The nicli flat verbs map symmetrically — egctl learning-createegctl request-policy-learning-create, etc. — and the old kebab-cased forms continue to dispatch unchanged.

request * and clear * are hidden from Operational ?

The request * and clear * namespaces dispatch only in Privileged mode and disappear entirely from the Operational ? listing — operators have to enable first to see them. The show * family stays visible at every mode. This is the modal-help-listing gate only; server-side AAA thresholds are unchanged.

Status

Retrieve the current engine + database + API status. Inside eghost cli:

host> show status
EnforceGate Engine Status:    UP
License:                      pro (18/25 connectors active)
EnforceGate Database Status:  UP
EnforceGate API Status:       UP

host> show version
EnforceGate vX Engine
  Engine version:          2026.30.0.328
  Client API version:      local 0.0.9.4 / remote 0.0.9.4
  License:                 Pro (active 18 of 25)
Copyright © 2024-2026 Exosys Sàrl. All rights reserved.

The License line on the third indented row of show version shows the licensed edition (Lite / Pro / Enterprise) and the active-vs-licensed connector-session count. For the full breakdown — expiry, bundled vs add-on split — use show license.

show version output format changed in 2026.23.2

Pre-2026.23.2 show version emitted three bare Label: Value lines (EnforceGate Engine Version: X, Local Control API Version: Y, Remote Control API Version: Z). The current format is a five-line Cisco IOS show ver-style block (shown above). Scripts that parse the output need to update their regex — the engine-version line now reads ^\s+Engine version: (two leading spaces) instead of ^EnforceGate Engine Version:. The nicli egctl show-version and the libexocli host> show version emit the same format.

Scripted equivalent (nicli)
docker exec enforcegate egctl show-status
docker exec enforcegate egctl show-version

The host-side eghost status and eghost version commands render augmented views — eghost version adds the host CLI's own version and the per-image versions read from each running container's OCI label, which is useful for catching a half-applied upgrade.

License

The licensing verbs surface the engine's licensed edition, expiry, and connector-session usage, and let the operator reload the license file without restarting the engine.

show license

Display the engine's licensed edition, expiry, and connector counts. Read-only; safe to run frequently. No arguments.

host> show license
Licensed edition:              pro
Expires at:                    2027-06-01 00:00:00 UTC
Active connector sessions:     18
Licensed connector cap:        25
  of which bundled:            25
  of which add-on:             0

Fields:

  • Licensed editionlite, pro, or enterprise. See editions for the feature mapping.
  • Expires at — when the license stops validating. UTC.
  • Active connector sessions — number of Defendr connectors currently registered against this engine. Live counter, refreshed at request time.
  • Licensed connector cap — total connector sessions this license allows. Bundled count plus any add-on bundles purchased.
  • of which bundled — connector sessions included with the edition (10 for Lite, 25 for Pro, 50 for Enterprise).
  • of which add-on — extra sessions purchased on top of the bundled count, in 5-session increments.

Privilege: Monitoring (1) — every operator role can read.

Scripted equivalent (nicli)
docker exec enforcegate egctl show-license

request license reload

Privileged. Re-read the license file from disk and atomically swap the engine's in-memory edition, cap, and expiry. The expected flow is:

  1. Operator receives a new license file (renewal, edition upgrade, add-on bundle purchase).
  2. Operator copies it over the existing license file path.
  3. Operator raises to Privileged (enable) and runs request license reload.

The engine validates the new file (signature + host fingerprint + expiry) and on success swaps it in without restarting — no traffic interruption, no connector sessions disconnect.

Success output:

host> enable
host# request license reload
License reloaded — now at pro edition, cap=25, active=18

Cap-underflow refusal. If the new license's cap is below the engine's current active connector count, the engine refuses the swap and keeps the previous license live:

host# request license reload
License reload refused (cap underflow): new license cap (10) is below
the current active connector count (18). Refusing to swap — stage the
reload when fewer sessions are active, or issue a higher-cap license.

Resolve by either draining active sessions before retrying (request neighbor teardown from Operational works for an orderly drain), or by issuing a license with a cap at least equal to the current active count — the typical case is a renewal at the same edition, which would not trigger underflow.

Privilege: Administrator (10) — same threshold as request policy reload.

Scripted equivalent (nicli)
docker exec enforcegate egctl request-license-reload

Neighbors

In the engine's vocabulary a neighbor is a connected squid-connector process. The standalone image spawns 5 connector processes by default (matching the url_rewrite_children 5 directive in squid.conf), so a healthy deployment shows five UP/ACTIVE neighbors.

show neighbor and request neighbor teardown both take a single optional positional argument that classifies its input — bare integer is the entry-num filter, anything else is a neighbor ID (IPv4 / IPv6 address). Omit the argument to operate on all neighbors.

host> show neighbor                 # list all
host> show neighbor 3               # entry-num 3
host> show neighbor 192.0.2.10      # neighbor ID 192.0.2.10
host> show neighbor 2001:db8::1     # neighbor ID (IPv6)
host> show neighbor 3 detail        # entry-num 3, detailed view
host> show neighbor
Types: C - Connector, CT - Connector over TLS, ? - Unknown

#   T    Neighbor ID      State        Address                Connected since
83  C    127.0.0.1        UP/ACTIVE    127.0.0.1:36392        0d 9h 55m 20s
84  C    127.0.0.1        UP/ACTIVE    127.0.0.1:36394        0d 9h 55m 20s
85  C    127.0.0.1        UP/ACTIVE    127.0.0.1:36396        0d 9h 55m 20s
86  C    127.0.0.1        UP/ACTIVE    127.0.0.1:36398        0d 9h 55m 20s
87  C    127.0.0.1        UP/ACTIVE    127.0.0.1:36400        0d 9h 55m 20s

For detailed per-session diagnostics on one neighbor:

host> show neighbor 83 detail
Neighbor 127.0.0.1, AS Number 64515
   Type is Connector (Squid)
   Using transport protocol tcp
     with plaintext connnection
   Session authentication method is SHA256
   Connected from address 127.0.0.1 port tcp/36392
               to address 127.0.0.1 port tcp/11224
   Session state is UP/ACTIVE
   Has sent defendr protocol messages:
        Open          1 msg     OpenConfirm           0 msg
        Request       112 msg   Acknowlegment         1 msg
        Response      0 msg     Keepalive           513 msg
        Teardown      0 msg     Notification          0 msg
        Unknown       0 msg
   Connected on Wed May 27 11:00:05.694 2026 CEST
   Last heared on Wed May 27 20:58:35.690 2026 CEST
   Current uptime is 0w 0d 9h 59m 23s
   Registered with ID #83 (uuid:117d43c6-4b88-4423-8b9f-a8d4805a9507)
   Advertised version: 2026.30.0.328 (EA) (capflag:0x0)

To manually terminate a neighbour session (the connector re-establishes shortly afterwards). Same positional shape as show neighbor, plus the Cisco IOS alias clear neighbor:

host> enable
host# request neighbor teardown          # tears down all (confirms first)
host# request neighbor teardown 3        # entry-num 3
host# request neighbor teardown 192.0.2.10
host# clear neighbor 3                    # IOS alias of `request neighbor teardown 3`
Tear down neighbor entry 3? [y/N] y
Requested neighbor session teardown: success

Policy introspection

Read-only verbs that let an operator see what policy is loaded right now without dumping the .policy files by hand. All are Operational > (Monitoring privilege and above) in libexocli and flat one-shots in nicli.

show policy summary

One-glance overview of the loaded policy — complementary to the per-rule show policy list below. Use this on a fresh login (or in a status check) to confirm the engine is enforcing what you expect without scrolling through individual rules.

host> show policy summary
Policy summary
  Default action (no rule matched): permit
  Rules loaded:   17 (7 regex + 8 domain-list + 2 pin)
  Domain hosts:   4,631,536
  Pinned hosts:   28 across 2 pin rule(s)
  By source:      12 operator + 5 toolbox
  Time-gated:     2 rule(s) with a time-window
  operator dir:   /etc/enforcegate/rules.d/          (9 file(s))  [loaded first]
  toolbox dir:    /etc/enforcegate-shared/rules.d/   (3 file(s))  [loaded after]
  Domain backend: sorted-arena

Fields:

  • Default action — the engine-synthesised no-match verdict from [policy].default_action. The permit / deny / warn / aup value reported here is what every request that no rule matches will get.
  • Rules loaded — total rule count with the regex / domain-list / pin split. Match-uri / match-sni / other action: rule kinds roll up into the parenthetical totals. The + N pin segment appears from 2026.35.0 onwards when at least one pin rule is loaded.
  • Domain hosts — aggregate count of distinct hostnames across every match-domain-list: referenced by action: rules.
  • Pinned hosts — aggregate count of distinct hostnames across pin: rules. Only printed when at least one pin rule is loaded.
  • By source — operator-authored rules ([policy].path) versus toolbox / shared-dir rules ([policy].shared_path).
  • Time-gated — how many rules carry a time-window: attribute.
  • operator dir / toolbox dir — resolved paths and per-directory file counts.
  • Domain backendhashset / sorted-arena / auto (configured via [policy].domain_backend). For sizing detail, see show system uri-engine.

show policy list

Lists every loaded rule with its id, name, action, match kind, window, source, source file, and aggregate match-attribute counts. Use this to confirm a recent reload produced the rule set you expected.

host> show policy list
 ID    Name                              Action     Type                   Window                 Source     File                                Hosts/Regexes
 ----  --------------------------------  ---------  ---------------------  ---------------------  ---------  ----------------------------------  -------------
 0001  allow-debian-mirror               permit     match-uri              —                      operator   05-allow-package-mirrors.policy     -
 0002  allow-ubuntu-mirror               permit     match-uri-regex        —                      operator   05-allow-package-mirrors.policy     1
 0003  block-malware-domains             deny       match-domain-list      —                      operator   20-block-threats.policy             28,453
 0020  pin-microsoft-update              splice     pin                    —                      operator   20-pinned.policy                    14
 0040  block-social-business-hours       deny       match-domain-list      weekdays 08:00-18:00   operator   40-block-social-media.policy        7
 0102  category-adult                    deny       match-domain-list      —                      toolbox    40-deny-categories.policy           412,903
 ...
 Total: 49 rules — 3 regexes, 41 domain-list rules, 3 other + 2 pin; 4.6M aggregate domain entries.
 Default action (no rule matched): permit

The footer reports the engine-synthesised no-match verdict ([policy].default_action) below the per-rule listing. The Default action line is the only place the no-match verdict shows up in show policy list — it does not occupy a rule id.

The Type column (renamed from Match-kind in 2026.35.0) shows what shape the rule has — match-uri, match-uri-regex, match-domain-list, match-sni, match-client-ip etc. for action: rules, and pin for Pinned destinations rules. When the Type is pin, the Action column carries the peek-step verdict (splice / bump / terminate) rather than the post-bump verdict.

The Source column reports where each rule was loaded from:

  • operator — hand-authored rules from [policy].path (typically /etc/enforcegate/rules.d/).
  • toolbox — rules loaded from the shared directory ([policy].shared_path, typically /etc/enforcegate-shared/rules.d/), written by the toolbox sidecar over a shared volume. Useful when debugging a verdict to understand whether the matching rule came from your own ruleset or a generated drop.

The Window column reports the rule's time-window: attribute in normalised compact form (weekdays 08:00-18:00, daily 22:00-06:00, mon,wed,fri 09:00-17:00). An em dash () means the rule is always active. The window is evaluated against the engine's [policy].time_window_tz (default local) — use show clock to confirm what "local" means on the engine.

The File column reports the source .policy file the rule was loaded from — the same value show policy detail <name> prints under Source file. Use it to jump straight from "this rule fired" to the file you need to edit, without round-tripping through show policy detail.

Lowest rule id wins. When two rules can match the same request, the engine applies the one with the lower id. Operators control precedence by number-prefixing policy filenames — 10-allow-internal.policy loads before 90-block-categories.policy, so its rules get lower ids and win conflicts. This is the mental model for layering a specific allow above a broader deny: prefix the allow's filename with a lower two-digit number than the deny's.

Migrating from a hand-rolled catch-all

Pre-2026.32.0 deployments commonly shipped (or hand-wrote) a 99-default-permit.policy rule to encode the no-match verdict. From 2026.32.0 that file is retired. If show policy list still reports a catch-all permit rule at a high id on your deployment, delete the file and set [policy].default_action = "permit" in engine.conf — the verdict is preserved, and lower-precedence rules in [policy].shared_path (typically toolbox-written rules) finally enforce instead of being shadowed by the catch-all.

show policy detail <name>

Per-rule deep view: the literal match value or compiled regex, the description shown to visitors on block / warn / aup, host counts for domain-list rules, the source .policy file the rule came from, and sibling counts when the same name appears in multiple files.

host> show policy detail block-malware-domains
Name:                 block-malware-domains
Action:               deny
Match kind:           match-domain-list
List path:            /etc/enforcegate/rules.d/lists/malware-domains.txt
Domain hosts:         28,453
Source file:          /etc/enforcegate/rules.d/20-block-threats.policy
Description:          Known malware distribution domain — blocked

The source-file field is what edit policy <name> resolves the rule name to when the operator enters Configuration mode — operators don't have to know which file their rule lives in to edit it.

show policy files

Lists every .policy file under [policy].path, with size, modification time, approximate rule count, and the disabled-file audit (files the engine skipped because of suffix or other rules).

host> show policy files
 File                                  Size      Modified              Rules
 ------------------------------------  --------  --------------------  -----
 05-allow-package-mirrors.policy         412 B  2026-05-30 09:14:02       3
 20-block-threats.policy               1.2 KiB  2026-06-02 17:02:11       2
 40-warn-social-media.policy             204 B  2026-06-01 11:30:55       1
 (Disabled: 0)

show policy file <name>

Returns the raw contents of one .policy file. Path-traversal guarded — only files under [policy].path are reachable.

host> show policy file 40-warn-social-media
warn-social-media: {
    match-domain-list: /etc/enforcegate/rules.d/lists/social-media.txt
    application: https
    action: warn
    description: Social media — proceed with caution
}

show policy domain-lists

Lists every match-domain-list: path referenced by a loaded rule, with per-file line count, last-modified timestamp, and the engine's live aggregate hashset size (the number of distinct domain entries the engine is matching against, deduplicated across all referenced files).

host> show policy domain-lists
 List path                                              Lines       Modified              Live hashset
 -----------------------------------------------------  ---------   --------------------  ------------
 /etc/enforcegate/rules.d/lists/malware-domains.txt        28,453   2026-06-02 17:00:00
 /etc/enforcegate/rules.d/lists/phishing-domains.txt        4,612   2026-05-29 22:14:00
 /etc/enforcegate/rules.d/lists/social-media.txt                7   2026-06-01 11:30:55
 ----------------------------------------------------- ---------   --------------------  ------------
 Aggregate                                              33,072                                  33,069
Scripted equivalents (nicli)
docker exec enforcegate egctl show-policy-summary
docker exec enforcegate egctl show-policy-list
docker exec enforcegate egctl show-policy-detail block-malware-domains
docker exec enforcegate egctl show-policy-files
docker exec enforcegate egctl show-policy-file 40-warn-social-media
docker exec enforcegate egctl show-policy-domain-lists

See also Inspecting the live policy for the show policy match flow and offline duckdb engine.db inspection.

System introspection

Read-only verbs that surface engine internals — runtime, memory, threads, listeners, log tail. Operators reach for these when sizing container memory limits, performance-triaging a slow deployment, or assembling the context to attach to a support ticket. All seven are Operational (Monitoring privilege and above) in libexocli and flat one-shots in nicli.

show system uri-engine

State of the URI scanning engine — regex rule count, compiled regex DB size, the in-memory domain backend (its name, total entries, distinct rule IDs, resident bytes), and the backend-selection mode (auto / hashset / sorted-arena — see [policy].domain_backend).

host> show system uri-engine
Regex rules:                   12 compiled
Regex DB size:                 12.3 KiB
Domain backend:                hashset
Domain entries:                4,612,083
Domain distinct rule IDs:      8
Domain resident:               287.4 MiB
Backend mode:                  auto (threshold 50,000,000)

show system uptime

Engine process uptime in Cisco IOS format, plus the ISO-8601 boot time:

host> show system uptime
Engine uptime:                 up 2 weeks, 3 days, 4 hours, 19 minutes
Boot time (UTC):               2026-05-13T07:41:18Z (1747122078)

show system memory

Process resident-set size from /proc/self/status, with the URI-engine subtotals broken out. Use this together with the memory sizing guide to size ENFORCEGATE_MEMORY against real numbers.

host> show system memory
Engine Memory
  Process VmRSS:                3.2 GiB
  URI engine: regex compiled DB: 12.3 KiB
  URI engine: domain backend:    287.4 MiB
  URI engine subtotal:           287.4 MiB

show system version

Engine build banner — engine version, Client API version, runtime libraries, regex runtime, build type. Attach the output to any support ticket so the engineering team can reproduce against the right build.

host> show system version
EnforceGate vX Engine
  Engine version:         2026.30.0.328
  Client API version:     0.0.9.5
  Database engine:        DuckDB 1.4.2
  Regex runtime:          <version>
  TLS:                    OpenSSL 3.5.1
  Build type:             release

show system threads

Sizes of the engine's two listener pools, the database and neighbor-monitor threads, and the live count of active connector sessions. Useful for performance triage — the thread counts are static (sized at boot), while the connector-session count is live (sessions multiplex onto the connector listener pool, so a healthy busy engine can show many more sessions than threads).

host> show system threads
Engine Threading Model
  Main thread:                     1
  Control-API listener pool:       4
  Connector listener pool:         4
  Database thread:                 1
  Neighbor monitor thread:         1
  Total threads:                  11
  Active connector sessions:      18

The two listener pools serve different traffic and are tuned separately:

  • Control-API listener pool — fixed at 4 threads; serves the engine's HTTPS REST API on tcp/11225 (what egctl talks to).
  • Connector listener pool — sized by [global].threads (default 4); serves the Defendr wire from connectors (enforcegate-squid-connector today; future TLS-proxy and ICAP connectors).

show system listeners

Confirms the engine is listening where the operator expects it to be — the Control API and Defendr bind addresses with their TLS state, plus the captive-portal base URL.

host> show system listeners
Control API (HTTPS):           0.0.0.0:11225  (TLS on, always)
Defendr listener:              127.0.0.1:11224  (TLS on)
Captive portal base URL:       https://portal.example.internal

The Control API is HTTPS unconditionally. The [global].tls knob in engine.conf toggles TLS on the Defendr wire only.

show system logs [N]

Tail the last N lines of the configured log file (defaults to 50; capped at 5000). Useful when the operator is in a eghost cli session and doesn't want to break out to eghost logs.

host> show system logs 5
[engine] 2026-06-05T11:42:18Z [info] policy reload: 4,612,094 rules applied (12 regex + 4,612,082 domain)
[engine] 2026-06-05T11:42:18Z [info] match engine swapped

show clock / show calendar

Engine wall time — UTC and local timezone with the abbreviation, plus epoch seconds for scripts that correlate against engine log timestamps. show calendar is a hard alias of show clock (Cisco IOS convention).

host> show clock
Engine clock
  UTC:                     2026-06-06T08:42:15Z
  Local:                   2026-06-06 10:42:15 CEST
  Epoch:                   1781340135 (unix seconds)

show tech-support

Single command that aggregates ten of the most-asked-for show * outputs into one paste-into-ticket block, each behind a === <section> === banner. The first thing to run before filing a support ticket — the engine team gets the engine version, uptime, memory, threads, listeners, status, license, neighbors, and the last 50 log lines in one paste.

host> show tech-support
EnforceGate Engine — Tech-Support Bundle
Paste-into-ticket aggregate of: version, uptime, memory, threads,
listeners, status, license, neighbors, and the last 50 log lines.

================================================================
=== show system version
================================================================
[…]
================================================================
=== show system uptime
================================================================
[…]
[… eight more sections …]
================================================================
=== end of tech-support bundle
================================================================

If any individual section errors during the bundle build (network blip, transient lock, etc.), the bundle still completes — the failed section is marked inline with a clear note, the rest is collected as normal. The value is "everything that worked plus a clear note about what didn't."

Scripted equivalents (nicli)
docker exec enforcegate egctl show-system-uri-engine
docker exec enforcegate egctl show-system-uptime
docker exec enforcegate egctl show-system-memory
docker exec enforcegate egctl show-system-version
docker exec enforcegate egctl show-system-threads
docker exec enforcegate egctl show-system-listeners
docker exec enforcegate egctl show-system-logs 50
docker exec enforcegate egctl show-clock
docker exec enforcegate egctl show-tech-support

URI engine restart

Privileged. Restart the in-engine URI scanning subsystem without restarting the whole engine. Useful for clearing accumulated state without dropping connector sessions:

host> enable
host# request system uri-engine restart
Requested URI Engine restart: success
host#

Engine reboot

Super-Administrator only (level 11). Graceful engine shutdown — the orchestrator restarts the container per its configured restart policy. The first verb that requires the Super-Administrator privilege level; an Administrator (10) who is in Privileged mode still gets a server-side refusal.

Two verbs invoke the same operation: request system reboot is the canonical form; reload is the Cisco IOS muscle-memory alias.

host> enable
host# request system reboot
Reboot the engine container (graceful shutdown; orchestrator restarts)? [y/N] y
Engine reboot requested: success
  (graceful shutdown in ~500ms; the container orchestrator restarts the engine)
host# reload                              # IOS alias of `request system reboot`
Reboot the engine container (graceful shutdown; orchestrator restarts)? [y/N] y
Engine reboot requested: success

Operator notes:

  • Privilege. Super-Administrator (11). An Administrator (10) in Privileged mode sees the refusal:

    host# request system reboot
    % Privilege denied  super-administrator rights required (you are at 10, threshold 11)
    host#
    
  • In-flight state. The graceful shutdown drains in-flight requests and flushes the policy snapshot. Learning sessions in the running state are stopped cleanly; captured URIs persist.

  • Per-engine, not cluster-wide. The verb operates on one engine. A deployment with N engines requires N separate invocations.
  • Restart guarantee. Coming back up is the orchestrator's job — Docker's --restart policy, Kubernetes' restartPolicy, the appliance's systemd unit. If the orchestrator is configured not to restart, the engine stays down.

Policy reload

Privileged. Re-parse *.policy files under /etc/enforcegate/rules.d/, validate, snapshot, and apply. Tells the engine to refresh its in-memory policy graph from the DuckDB store. Same path eghost policy new/edit/remove invoke automatically after writing a policy file.

host> enable
host# request policy reload
Parsed rules: 14
Snapshot taken: /var/lib/enforcegate/policy-backups/20260528-1503.001
Policy reload: success

To validate without applying (the recommended first step on any policy edit) — dry-run is now a bare modifier flag, no argument:

host# request policy reload dry-run
Parsed rules: 14
Validation: success (no snapshot taken, no live state changed)
Scripted equivalent (nicli)
docker exec enforcegate egctl request-policy-reload                # apply
docker exec enforcegate egctl request-policy-reload --dry-run      # validate only

For policy authoring operators should prefer eghost policy … (see policies reference) — this manual reload is only needed when .policy files were modified by some external path (bind-mount, docker cp, external orchestration).

Policy rollback

Privileged for the request half; Operational for show policy backups. Snapshots of rules.d/ are taken before every successful request-policy-reload and stored under /var/lib/enforcegate/policy-backups/. List them (no enable required — it's a show verb):

host> show policy backups
Gen  Snapshot                      Files   Note
1    20260528-1503.001             12      (currently applied)
2    20260528-1320.001             11
3    20260527-0945.001             10

Preview a rollback before applying — shows which files would be removed (orphans added since the snapshot) and which would be overwritten:

host> enable
host# request policy rollback 2 dry-run true
Rollback dry-run from gen 1 → gen 2:
  - would remove orphan file: 75-temp-experiment.policy
  - would overwrite operator-edited file: 50-deny-shorteners.policy

Apply (interactive — the client prompts with the diff before proceeding):

host# request policy rollback 2

To skip the interactive prompt in scripted use:

host# request policy rollback 2 yes true
Scripted equivalent (nicli)
docker exec enforcegate egctl request-policy-rollback 2 --dry-run
docker exec enforcegate egctl request-policy-rollback 2 --yes

The engine refuses to roll back over operator-added files unless yes true (or the nicli --yes flag) is present — the gate is intentional so a careless rollback doesn't wipe new policy work. Snapshot retention is bounded by [policy].backup_depth in engine.conf (default 10 — older snapshots are pruned after every successful reload).

Policy history (git tracking)

Optional. When [policy].path contains a .git/ directory and [policy].git_mode allows it (default auto), every successful policy reload writes a git commit into the same repository before the snapshot is taken. The commit is authored as <user>@<engine-hostname> so the log shows who made the change and which engine processed it. The result is a per-rule audit trail that sits on top of the existing snapshot system — see Policy audit and history for the enable / migration / disable workflow.

Six read-only verbs (Operational) and two mutating verbs (Privileged) expose the audit trail. The verb shape is modelled on Junos so operators with that muscle memory translate directly — show policy log lines up with Junos show configuration log; show policy diff rollback <N> lines up with show configuration | compare; show policy commit <N> lines up with show system commit. Cisco IOS / PAN-OS operators get the same verbs without leaning on the network-gear analogy.

show policy log

The entry-point verb. Lists policy commits in reverse chronological order — generation number, short SHA, date, author, and the operator's comment (the message they typed when they ran the reload, captured automatically into the commit).

host> show policy log limit 5
Gen  SHA       Date              Author             Comment
1    a7f3e2c   2026-06-07 14:22  admin@host01       tighten t.co regex (SEC-1421)
2    b910d4f   2026-06-06 09:13  ops-bot@host01     quarterly review
3    c4d8a01   2026-06-05 16:48  jdoe@host01        add allow-list for partner CDN
4    d2e0b7c   2026-06-03 11:02  admin@host01       Revert "broaden gambling category"
5    e6f1c9b   2026-06-03 10:55  admin@host01       broaden gambling category
host>

Gen 4 is the auto-revert that paired with gen 5's bad deploy — on a reload failure the engine git reverts the just-made commit so the recorded history matches the engine's live state (honest audit trail, never rewritten).

show policy blame <rule>

Per-rule attribution. The killer triage verb — operators don't need to remember which file the rule lives in. The engine resolves the rule name server-side, opens the right .policy file, and renders per-line author + commit attribution for the rule block.

host> show policy blame gambling-extras
File: rules.d/30-categories/gambling.policy
Rule: gambling-extras
SHA       Date          Author             Line  Content
a7f3e2c   2026-06-07    admin@host01         41  {
a7f3e2c   2026-06-07    admin@host01         42    name: "gambling-extras",
a7f3e2c   2026-06-07    admin@host01         43    match-uri-regex: "(bet365|stake|pinnacle)\\.com",
c4d8a01   2026-06-05    jdoe@host01          44    action: deny,
c4d8a01   2026-06-05    jdoe@host01          45  }
host>

show policy commit <N>

Metadata plus diff for one specific commit — the equivalent of git show wrapped behind the operator-friendly verb. <N> is the generation number from show policy log.

host> show policy commit 1
Generation: 1
SHA:        a7f3e2c
Date:       2026-06-07 14:22
Author:     admin@host01
Comment:    tighten t.co regex (SEC-1421)

--- a/rules.d/40-warn-social-media.policy
+++ b/rules.d/40-warn-social-media.policy
@@ -3,7 +3,7 @@
 warn-social-media: {
-    match-uri-regex: "^https?://(www\\.)?t\\.co/.+",
+    match-uri-regex: "^https?://t\\.co/[A-Za-z0-9]+/?$",
     application: https,
     action: warn,
 }
host>

show policy diff rollback <N>

Preview what request policy rollback <N> would change — the "are you sure?" verb operators run before pulling the trigger.

host> show policy diff rollback 1
--- a/rules.d/30-categories/gambling.policy   (HEAD~1)
+++ b/rules.d/30-categories/gambling.policy   (HEAD)
@@ -42,7 +42,7 @@
   {
     name: "gambling-extras",
-    match-uri-regex: "(bet365|stake)\\.com",
+    match-uri-regex: "(bet365|stake|pinnacle)\\.com",
     action: deny,
   }
host>

After reviewing the preview, run request policy rollback <N> to actually apply the change.

show policy fingerprint

Snapshot of the current HEAD — useful for cross-referencing against an external audit log or pasting into a change-management ticket.

host> show policy fingerprint
Current HEAD
  SHA:           a7f3e2c2...
  Author:        admin@host01
  Date:          2026-06-07 14:22
  Signature:     (unsigned)
host>

The Signature: line is reserved for a future release that will support signed commits (GPG or SSH-signed). Today every row reads (unsigned).

show policy tags

Lists annotated tags — named baselines the operator created at specific commits via request policy tag (below).

host> show policy tags
Name              SHA       Tagged at         Author             Annotation
pre-blackfriday   a7f3e2c   2026-06-07 14:25  admin@host01       snapshot before BlackFriday rules
gold-baseline     b910d4f   2026-06-06 09:14  admin@host01       end-of-Q2 vetted configuration
host>

request policy git-init

Privileged. Initialise git tracking of the policy directory directly from the REPL — equivalent to running git init inside [policy].path by hand, but without shelling out of eghost cli. The engine creates the repo, stages every current .policy file, writes the auto-managed .gitignore, and produces a baseline commit. Subsequent reloads start writing commits on top.

host> enable
host# request policy git-init
Initialising git tracking at /etc/enforcegate/rules.d/...
  baseline commit: a7f3e2c (12 files, 47 rules tracked)
  .gitignore written (8 patterns)
Git mode is now active. Reloads will be recorded as commits.
host#

If [policy].path/.git/ already exists, the engine refuses politely and reports the existing repo's HEAD SHA — no destructive re-init.

See policy audit and history for the migration shape and the fallback shell-out command if the operator prefers to drive git init from the host.

request policy tag <name> comment <text>

Privileged. Create an annotated tag at the current HEAD. The comment is mandatory — lightweight tags without an annotation are refused at the server (they don't carry the metadata operators need for audit). Tag names are unique; the engine refuses to overwrite an existing tag.

host> enable
host# request policy tag pre-blackfriday comment "snapshot before BlackFriday rules"
Tag `pre-blackfriday` created at HEAD (a7f3e2c).
  Annotation: snapshot before BlackFriday rules
host#

Use tags to name important baselines you may want to roll back to — the snapshot generation numbers shift as new reloads accumulate, but a tag's SHA is stable.

Failure semantics

If a request policy reload fails after the engine has already written the commit (a validation pass succeeded but the apply step hit a runtime issue), the engine **git revert**s the just-made commit and records the revert as a new commit on top — the recorded history mirrors what actually happened. The engine never git reset --hards history; the audit trail stays honest about both the failed attempt and the recovery.

Scripted equivalents (nicli)
docker exec enforcegate egctl show-policy-log
docker exec enforcegate egctl show-policy-blame gambling-extras
docker exec enforcegate egctl show-policy-commit 1
docker exec enforcegate egctl show-policy-diff-rollback 1
docker exec enforcegate egctl request-policy-git-init
docker exec enforcegate egctl show-policy-fingerprint
docker exec enforcegate egctl show-policy-tags
docker exec enforcegate egctl request-policy-tag pre-blackfriday \
    --comment "snapshot before BlackFriday rules"

Policy match — test a URL against the live policy

The canonical "why is this URL blocked?" diagnostic. Sends a URL through the live policy engine and reports the verdict, matched rule, action, reason — without the operator actually browsing to the URL. Operational mode; lives under the show policy * namespace alongside the introspection verbs.

host> show policy match https://example.com/foo
URI:        https://example.com/foo
Matched:    yes
Code:       300 (deny — redirect to portal)
Rule ID:    14
Rule name:  deny-some-rule_e14
Action:     deny
Reason:     <the rule's description field>
Pattern:    ^(https?:\/\/)?([^/]+\.)?example\.com(/|$)

The Pattern field (the matched regex) is admin-only — monitoring and standard users see the verdict and reason but not the underlying regex. The rule_name suffix _e<N> is the global rule ID; the same suffix appears on the captive portal's verdict pages so operators can cross-reference.

For a non-matched URL:

host> show policy match https://www.exosys.ch/
URI:        https://www.exosys.ch/
Matched:    no (permit-by-default)
Code:       200 (permit)

For a host that matches a pin rule — independent of any action: match — show policy match prints an additional TLS pin: line reporting the peek-step verdict the SslBump helper would return, and which pin rule produced it:

host> show policy match https://download.windowsupdate.com/
URI:        https://download.windowsupdate.com/
Matched:    no (permit-by-default)
Code:       200 (permit)
TLS pin:    splice (pass through, hostname-only)   [rule: pin-microsoft-update]

The TLS pin: line is omitted when the host is unpinned (no pin rule matched) — the engine defaults to bump (normal inspection) in that case.

Scripted equivalent (nicli)
docker exec enforcegate egctl show-policy-match "https://example.com/foo"

# Legacy form (still dispatches, hidden from `?` listings in libexocli):
docker exec enforcegate egctl test-policy-uri "https://example.com/foo"

This is the first tool to reach for on any "URL blocked unexpectedly" ticket — see troubleshooting.

Learning mode

Learning mode lets the engine observe URLs hit by a specific client / subnet / user-agent for a window, then synthesises a starter .policy document from the captures. Useful for first-deployment baseline discovery. See learning mode for the full operator workflow.

The verbs live under the policy learning namespace — show * forms are Operational, request * forms are Privileged. clear policy learning session <id> is the Cisco-style alias of request policy learning delete <id>.

host> enable
host# request policy learning create subnet 10.1.0.0/16 5000
host# request policy learning start <id>
host> show policy learning sessions               # Operational — back at `>`
host> show policy learning session <id>
host# request policy learning stop <id>           # Privileged
host# request policy learning analyze <id> warn
host# clear policy learning session <id>          # IOS alias of `request policy learning delete <id>`

Per-session detail:

host> show policy learning session 3
Session 3:
  Filter:        subnet 10.1.0.0/16
  URI cap:       5000
  State:         stopped
  URIs captured: 4827
  Created:       2026-05-26T09:14:00Z
  Stopped:       2026-05-26T17:42:00Z

  Top URIs (by hit count):
  hits  host                          path
  1247  www.google.com                /search
   913  mail.google.com               /
   708  www.youtube.com               /watch
   ...

request policy learning analyze writes a .policy document to stdout — redirect to a file under rules.d/ then reload:

docker exec enforcegate egctl request-policy-learning-analyze 3 warn --no-stats \
    > /tmp/50-warn-learned.policy
docker cp /tmp/50-warn-learned.policy enforcegate:/etc/enforcegate/rules.d/
docker exec enforcegate egctl request-policy-reload

The --no-stats flag strips the (N hits during learning session M) provenance tag from the synthesised rule descriptions — use it for production-bound policies.

User management

request user * verbs are Privileged in libexocli; show users stays Operational. The host-side wrapper eghost users add <name> performs the same operation with a friendlier prompt — use that path when possible (it dispatches the nicli flat verb under the hood, no enable needed there).

The Available User types: menu is privilege-aware — operators only see the types they are allowed to create. An Administrator (10) sees Monitoring User, Standard User, and the reload-only service account; a Super Administrator (11) additionally sees Administrator and S) Super Administrator, so a Super Administrator can mint a peer Super Administrator. The bootstrap admin was previously the only Super Administrator possible because the must-outrank invariant has no level above 11.

host> enable
host# request user add
You are about to add a new user to EnforceGate. Enter '.' if you want to leave a field blank.
-----
Available User types: 1) Monitoring User, 2) Standard User, R) Service account (policy-reload only),
                     A) Administrator, S) Super Administrator
User Type: A
[i] Valid usernames must start with a lowercase character, can include special characters such as '_' or '-', and must be between 3 and 128 characters long.
Username: backup-admin
User Description: Secondary administrator
Password: **********
Repeat Password: **********

R) Service account — least-privilege automation

The reload-scoped service account (new in 2026.33.0) carries a tightly scoped capability set — it can call request policy reload and the show status read-only verbs, and nothing else: no user management, no license operations, no neighbor teardown, no engine reboot. The intended use is automation that only needs to trigger reloads — most notably the toolbox sidecar's helper library, which currently authenticates with an Administrator credential. Migrating that automation to a service account follows the principle of least privilege without losing functionality. See Privilege model for how this slots between the existing levels.

If the engine cannot determine the operator's privilege level, the command aborts cleanly with a clear message instead of presenting a menu.

See privilege model for the four levels and what each can do. Self-target password change works without admin privilege; other-target requires admin + outrank.

Password file

When a new user is added, the password file at /etc/enforcegate/passwd is automatically updated with a fresh per-user salt + PBKDF2-HMAC-SHA256 hash. Existing accounts on older sha256 / sha256-salted hashes continue to authenticate and migrate to pbkdf2-sha256 the next time their password is changed.

Username naming convention

  • Usernames must begin with a lowercase letter (a–z).
  • Permitted characters: lowercase letters, digits (0–9), underscore (_), hyphen (-).
  • Length: 3 to 128 characters.

List users — Privileged mode in libexocli (the user-list metadata is gated behind enable); still callable as a flat nicli one-shot at the server-side AAA threshold:

host> enable
host# show users
Username       UID    Privilege              Hash
admin          1000   Super Administrator    pbkdf2-sha256$<params>:hash
monitor        1001   Monitoring User        pbkdf2-sha256$<params>:hash
backup-admin   1002   Administrator          pbkdf2-sha256$<params>:hash
legacy-ops     1003   Standard User          sha256$salt:hash

No secrets are exposed. The Hash column shows the algorithm — new and password-changed accounts use pbkdf2-sha256 (deliberately slow, brute-force-resistant); accounts that haven't had a password change since the upgrade keep their legacy sha256 / sha256-salted hash until the next change. Change a password (uses getpass — no echo, never on argv):

host# request user passwd monitor

Remove a user (refuses self-remove, refuses if you don't outrank the target):

host# request user remove monitor

Staged edits

Configuration mode. A configure terminaleditcommit flow that lets an Administrator-level operator stage .policy edits inside the REPL, diff them against the engine-snapshotted baseline, and either atomically apply or roll back — all without leaving libexocli.

The flow:

host> enable
host# configure terminal
host(config)# edit policy 40-warn-social-media
host(config)# show policy diff
host(config)# commit
host#

Entering Configuration

configure terminal from Privileged. The engine takes a candidate snapshot of rules.d/ at entry — this is what show policy diff and revert compare against.

edit policy <name>

<name> is resolved as a rule name first, then as a file basename:

  • If <name> matches the Name column of show policy list (the bareword in a .policy block like block-malware-domains or warn-social-media), the engine opens the .policy file containing that rule. Operators don't have to know which file their rule lives in.
  • If no rule by that name exists, <name> is treated as a file basename — edit policy 40-warn-social-media opens 40-warn-social-media.policy under [policy].path.

Either form shells out to your preferred editor. The editor lookup chain is $VISUAL$EDITORvi. The REPL session pauses while the editor is open and resumes (with terminal state restored) on editor exit. The file is opened in place — saves on disk are immediate, but the engine does not load them until commit.

host(config)# edit policy warn-social-media
Resolved rule `warn-social-media` → 40-warn-social-media.policy
[ editor opens; operator edits the file; editor exits ]
host(config)#

If neither form resolves, the engine refuses:

host(config)# edit policy nonsense
% No rule named `nonsense` is currently loaded, and no file `nonsense.policy` exists under [policy].path.
% Run `show policy list` to see the canonical names of loaded rules.
host(config)#

show policy diff

Engine-computed diff between the entry-time candidate snapshot and the current on-disk state. Available at any point during the Configuration session.

host(config)# show policy diff
-warn-social-media: action=warn, list=social-media.txt (7 hosts)
+warn-social-media: action=warn, list=social-media.txt (12 hosts)
+block-tiktok: action=deny, host=tiktok.com

commit

Atomically apply via the policy-reload path (same compile + DuckDB import + in-memory reload as request policy reload). On success, the candidate snapshot is consumed and the session returns to Privileged.

host(config)# commit
Parsed rules: 16
Snapshot taken: /var/lib/enforcegate/policy-backups/20260604-0918.001
Policy reload: success
host#

If validation fails, the engine keeps the previously-loaded policy live and reports the diagnostic; the candidate stays open so the operator can fix and retry.

revert

Restore rules.d/ from the candidate snapshot taken at configure terminal entry. Discards any on-disk edits made during the session. The engine state does not change (it was never reloaded), so no policy-reload is performed.

host(config)# revert
Reverted to candidate baseline (3 file changes discarded).
host#

end

Leave Configuration without committing or reverting. On-disk edits persist; the engine state is unchanged. Useful when you want to keep edits for review later but not load them yet.

host(config)# end
host#

Authentication

egctl authenticates per request with HTTP basic against the engine's /etc/enforcegate/passwd file. Each verb additionally checks the requesting user's privilege level against a per-verb threshold.

Passwords are read in this order: command-line -P argv → EGCTL_PASSWORD env var → interactive getpass prompt (no echo). Never pass a password on argv outside CI — it appears in ps and shell history.

Fail-fast on bad credentials at REPL entry

egctl --cli (and eghost cli) authenticates once at session start and refuses to open the REPL on a credential failure. Previously the shell opened, the prompt rendered, and the first command failed with an auth error — which looked like the operator was logged in. Current behaviour exits immediately with a clear message:

$ egctl --cli
% Authentication failed: user `monitor` was rejected by the engine.
% Check -U / -P arguments, the `EGCTL_PASSWORD` env var, or the [control] section of egctl.conf.
$ echo $?
1

The same message renders on eghost cli; the host-side wrapper exits non-zero and the operator stays on their shell prompt. Pre-2026.23.1.279 versions opened the shell anyway and surfaced the failure on the first command — operators upgrading from that line will see the failure earlier now.

Configuration file

egctl reads its configuration from /etc/enforcegate/egctl.conf.

[global]

The [global] section contains settings that apply globally to egctl.

Name Type Description Default
host string Destination hostname or IP address of the engine. 127.0.0.1
port integer Destination port for the Control API. 11225
cert string Path to the engine's TLS certificate (for pinning). /etc/enforcegate/ssl/cert.pem
username string Username used for Control API authentication. admin

The password is never in the config file — it always comes from -P argv, the EGCTL_PASSWORD env var, or an interactive prompt.

The shipped default is suitable for the standalone container, where egctl runs inside the same container as the engine and reaches it over loopback.