Reference¶
This page defines the configuration, CLI options, search syntax, network ports, and rule fields supported by the current API Observer code. For task-oriented examples, see the Guide.
Query language¶
Log Explorer and Analysis use the same query language. A query is a comparison, or comparisons joined with AND, OR, and NOT. Parentheses group expressions. The search is applied to records returned for the selected time range; an empty query matches every returned record.
Syntax¶
| Element | Accepted form |
|---|---|
| Comparison | field operator value |
| Operators | =, !=, >, >=, <, <= where supported by the field |
| Boolean operators | NOT, AND, OR (case insensitive) |
| Grouping | Parentheses, including nested groups |
| String | Double-quoted text, for example "POST" |
| Number | Unquoted, nonnegative integer, for example 404 |
| Precedence | Parentheses, then NOT, then AND, then OR |
Field names are lowercase and case sensitive. Use spaces between tokens; tabs and newlines outside quoted strings are not accepted by the lexer. Strings must be quoted. The parser reports malformed expressions such as missing values or unmatched parentheses.
Fields¶
| Field | Value | Operators | Applies to | Notes |
|---|---|---|---|---|
type |
"request" or "response" |
=, != |
Both | Value is case insensitive. |
node |
Quoted node name | =, != |
Both | Compares the readable node name, case insensitively. |
method |
Quoted HTTP method | =, != |
Requests | Comparison is case insensitive. |
path |
Quoted URL path | =, != |
Requests | Case sensitive; a {name} segment matches any one nonempty segment. |
status |
Integer HTTP status | All comparison operators | Responses | Request rows do not have a status. |
timestamp |
Quoted RFC 3339 timestamp | All comparison operators | Both | Example: "2026-10-29T12:00:00Z". |
findings |
Integer count | All comparison operators | Both | Explorer counts saved findings; Analysis counts findings from that run. |
A comparison using a request-only field does not match a response row, and vice versa. path = "/users/{id}" is a search pattern for one segment; rule YAML string_equal does not use this pattern behavior.
Valid examples:
type = "request" AND method = "POST"
node = "payments" AND findings > 0
(type = "response" AND status >= 500) OR path = "/health"
| Invalid query | Reason |
|---|---|
status >= "400" |
status requires an integer. |
method = POST |
Strings require double quotes. |
path ~ "/admin" |
~ is not a query operator. |
(status >= 400 |
Missing closing parenthesis. |
host = "api" |
host is not a supported field. |
API Observer configuration¶
Observer reads the YAML file named by API_OBSERVER_CONFIG. If that environment variable is unset, it uses api-observer/config.yaml beneath the operating system's user configuration directory (normally ~/.config/api-observer/config.yaml on Linux). If the default file is missing, Observer creates it with the defaults below. An explicitly selected file must already exist. Values are not filled in individually when a user-supplied YAML file omits them; required fields are validated.
| YAML field | Type | Generated default | Meaning |
|---|---|---|---|
app_log |
Path | User config directory api-observer/logs/api-observer.log |
Application log; must be nonempty. |
findings_log |
Path | User config directory api-observer/logs/findings.jsonl |
JSONL findings log; must be nonempty. |
rules |
Path or empty string | User config directory api-observer/rules.yaml |
Rule set read at startup. "" loads an empty set. |
ingest_address |
TCP listen address | :24899 |
gRPC listener for Collector ingest and heartbeats. |
dashboard_port |
TCP listen address | :8080 |
HTTP dashboard listener. |
queue_size |
Positive integer | 1000 |
Maximum queued incoming audit jobs. |
worker_count |
Positive integer | 5 |
Number of concurrent rule evaluation workers. |
nodes |
List | [] |
Persisted Collector IDs, names, and query addresses. |
A nodes item has id, name, and optional addr. IDs must be nonempty and unique; names must be nonempty. Observer writes new nodes, address changes, and renamed nodes back to the config file, so its service account needs write access to the file and its directory. Log directories must also be writable. Use absolute paths to avoid dependence on the process working directory. If rules is nonempty, that file must exist and contain parseable rule-set YAML when Observer starts.
The CLI is api-observer start or api-observer stop. start runs in the background by default; use start --background=false under a service manager. A PID file is kept under the user configuration directory. stop sends SIGTERM to the PID in that file.
API Collector¶
Collector reads one ModSecurity JSON transaction per line from its audit file. It tails new lines for ingest and reads the current file directly for historical queries.
| Command | Purpose |
|---|---|
api-collector start |
Run the file watcher, ingest client, and historical query service. |
api-collector stop |
Send SIGTERM to the process recorded in its PID file. |
api-collector index --path FILE [--block-size 256] |
Build a separate .idx file for an audit file. The current historical query service reads the JSON audit file directly. |
start options¶
| Option | Default | Meaning |
|---|---|---|
--background, -b |
true |
Run as a detached process; use false under a service manager. |
--data-dir, -d |
User configuration directory | Parent directory for api-collector/collector.id. |
--file-path, -f |
Empty | ModSecurity audit file to watch and query. It must exist at startup. |
--observer-ingest-addr, -o |
localhost:24899 |
Observer gRPC ingest address reachable from Collector. |
--query-addr, -q |
localhost:24900 |
Local TCP listener for historical queries. |
--advertise-query-addr, -a |
Empty | Address reported to Observer in heartbeats. Set this to a reachable host:24900. |
Collector creates a persistent UUID in <data-dir>/api-collector/collector.id; keep that file across restarts to retain the same node identity. Its PID file is under the operating system's user configuration directory, independent of --data-dir.
Set --advertise-query-addr to a nonempty address: Observer rejects heartbeats without a query address. If you set --data-dir, run with --background=false under a service manager; the current background launcher does not pass --data-dir to its child process.
Collector starts tailing at the end of the file. It does not resend earlier lines as new ingest events. Earlier records remain searchable while they are in the current audit file. The watcher reopens a rotated file, but historical queries do not search rotated archives. Collector sends a heartbeat every 15 seconds; Observer considers the node disconnected after 45 seconds without one. Reconnection uses exponential backoff, starting at about one second and capped at a 30-second base delay, with jitter.
Query port
Observer currently uses port 24900 for historical queries even when an advertised address contains a different port. Keep the Collector query listener and advertised address on 24900.
Networking¶
| Source | Destination | Default port | Protocol and purpose |
|---|---|---|---|
| Browser or reverse proxy | Observer dashboard | 8080 |
HTTP dashboard, Explorer, Analysis, Rules, and Nodes. |
| Collector | Observer ingest listener | 24899 |
gRPC request/response events and node heartbeats. |
| Observer | Collector query listener | 24900 |
gRPC historical log requests and streamed results. |
The dashboard listener can be bound to loopback with dashboard_port: 127.0.0.1:8080 and placed behind an authenticated HTTPS reverse proxy. The current dashboard has no built-in authentication or TLS. The gRPC clients use plaintext, so allow only participating hosts on those ports and use a trusted private network or encrypted tunnel. The Collector's advertised query host must resolve and be reachable from Observer.
Rule reference¶
A rule file is YAML with a top-level rules map. Each map key is a rule ID. The Rules dashboard page reads Observer's loaded set; the Analysis editor accepts a separate rule set for that run.
rules:
admin-request:
disabled: false
scope: [request]
description: Flag access to the admin path.
match:
mode: all
conditions:
- target: path
operator: string_equal
value: /admin
finding:
title: Admin path requested
message: A client requested /admin.
severity: medium
tags: [admin, review]
Rule fields¶
| Field | Type | Behavior |
|---|---|---|
disabled |
Boolean | Defaults to false. A disabled rule is skipped. |
scope |
List | Accepts request and response; defaults to both when omitted. See the behavior note below. |
description |
String | Optional display text on the Rules page. |
match.mode |
all or any |
Defaults to all. Requires all or at least one condition, respectively. |
match.conditions |
Nonempty list | Conditions evaluated against an event. |
finding.title |
String | Finding title. |
finding.message |
String | Finding explanation. |
finding.severity |
String | Optional label; no fixed severity enum is enforced. |
finding.tags |
List of strings | Optional labels. |
chain |
Rule ID | Optional reference to another rule in the same set; both matches are required for the referring rule. Cycles are rejected. |
Each condition has a required target and operator, an optional value, an optional key for a specific query parameter, header, or cookie, and optional negated: true to invert its result. Regex values must compile when the rule set loads. Numeric comparisons parse both operands as numbers; a nonnumeric value does not match.
| Target | Supported operator families | What it reads |
|---|---|---|
path |
string_equal, regex, detect_sqli |
Request URL path. string_equal is literal. |
method |
string_equal, regex |
Request method. |
body |
string_equal, regex, detect_sqli |
Request body. |
query, header, cookie |
string_equal, regex, detect_sqli, numeric |
Values; key narrows to one parameter, header, or cookie. |
cookie_name |
string_equal, regex, detect_sqli |
Cookie names. |
body_length |
Numeric | Request content length. |
Numeric operators are equal, not_equal, less_than, less_than_or_equal, greater_than, and greater_than_or_equal. detect_sqli uses the built-in SQL injection detector. Accepted operator names are validated when YAML loads, but not every target/operator combination has matching behavior; use the table above.
Current scope behavior
The parser accepts scope: [request] and scope: [response], but the current evaluator does not use scope to filter events. Match conditions read request fields, so response-only events do not satisfy them. Do not rely on scope for response-only detection in this version.
The Import Rules page translates pasted CRS SecRule directives into YAML for review. It does not save or activate translated rules. Observer loads active rules at startup; Analysis evaluates its entered rules for that run and does not append those findings to the persisted findings log.