CNSL maintains a per-entity trust score that reflects how much it trusts an IP address or username based on observed behavior. Low-trust entities trigger alerts with fewer events -- a suspicious entity doesn't get the same benefit of the doubt as a known-good one.
This implements the zero-trust architecture support gap from the CNSL research paper:
"Future work includes zero-trust architecture support: identity-based trust scoring so the same IP from an admin account and an attacker are treated differently."
Traditional CNSL treats every IP identically. If the default SSH brute-force threshold is 8 failures, an attacker needs exactly 8 failures to trigger an alert -- whether they have been blocked 10 times before or this is their first connection.
Zero-trust says: an entity that has already demonstrated risky behavior should need to do less to trigger an alert. If an IP is seen launching a port scan (lowering its trust score), subsequent SSH failures from that same IP should alert sooner.
Every new entity starts at initial_score (default 0.8 -- trusted but
not implicitly). The score lives in the range [min_score, 1.0].
| Score | Label | What it means |
|---|---|---|
| 0.8 - 1.0 | Trusted | Known entity, good behavior history |
| 0.5 - 0.8 | Moderate | Some anomalies or unknown elements |
| 0.2 - 0.5 | Suspicious | Multiple anomalies or risk signals |
| 0.05 - 0.2 | Untrusted | Active threat signals -- alert aggressively |
Trust signals are named events that increase or decrease the score:
| Signal | Delta | Trigger |
|---|---|---|
known_ip_login |
+0.05 | SSH login from an IP the user has used before |
normal_hour_login |
+0.02 | Login at a normal hour for this user |
unknown_ip_login |
-0.10 | SSH login from a new, unseen IP |
ueba_anomaly |
-0.20 | UEBA engine detects behavioral anomaly |
brute_force_fail |
-0.05 | SSH or web auth failure (per event) |
mfa_failure |
-0.25 | Cloud MFA challenge failed or bypassed |
cloud_risk_flag |
-0.30 | Azure AD risk engine flags the sign-in |
impossible_travel |
-0.35 | Two sign-ins from geographically impossible locations |
block_applied |
-0.40 | CNSL blocks this entity |
correlation_alert |
-0.15 | Cross-source correlation alert fires |
Scores recover toward initial_score at a rate of recovery_per_day
per day of inactivity. An entity that goes quiet after suspicious
behavior will gradually return to trusted status.
Example: entity with score 0.3 (suspicious), recovery_per_day=0.05:
- After 2 days quiet: score = 0.3 + (2 * 0.05) = 0.4
- After 10 days quiet: score = min(0.8, 0.3 + (10 * 0.05)) = 0.8 (restored)
When apply_to_threshold is true (default), the detection threshold for
an entity is multiplied by its trust score:
effective_threshold = ceil(normal_threshold * trust_score)
Examples with ssh.brute_force default threshold = 8:
| Trust score | Label | Effective threshold | Events needed to alert |
|---|---|---|---|
| 1.0 | Trusted | ceil(8 * 1.0) = 8 | 8 failures |
| 0.75 | Moderate | ceil(8 * 0.75) = 6 | 6 failures |
| 0.5 | Suspicious | ceil(8 * 0.5) = 4 | 4 failures |
| 0.2 | Untrusted | ceil(8 * 0.2) = 2 | 2 failures |
| 0.05 | Untrusted | ceil(8 * 0.05) = 1 | 1 failure |
The effective threshold is always at least 1 -- a zero-threshold is never applied.
ssh.brute_force-- IP trust scoressh.credential_stuffing-- IP trust scorecloud.signin_brute_force-- IP trust score
Rules with threshold = 1 (MFA failure, risky signin, impossible travel)
are unaffected since ceil(1 * any_score) = 1.
{
"zero_trust": {
"enabled": true,
"initial_score": 0.8,
"min_score": 0.05,
"recovery_per_day": 0.05,
"persist": true,
"max_entities": 50000,
"apply_to_threshold": true
}
}| Key | Default | Description |
|---|---|---|
enabled |
true |
Enable zero-trust scoring |
initial_score |
0.8 |
Starting score for new entities |
min_score |
0.05 |
Floor -- score never drops below this |
recovery_per_day |
0.05 |
Score improvement per day of quiet |
persist |
true |
Save scores to SQLite |
max_entities |
50000 |
Max entities in memory (oldest evicted) |
apply_to_threshold |
true |
Whether to scale detection thresholds |
To disable threshold scaling while keeping scoring visible:
{"zero_trust": {"apply_to_threshold": false}}GET /api/zero-trust/stats
{
"enabled": true,
"total_entities": 142,
"trusted": 98,
"moderate": 30,
"suspicious": 12,
"untrusted": 2,
"avg_score": 0.71,
"min_score_seen": 0.12
}GET /api/zero-trust/scores?type=ip&max_score=0.5&limit=50
Query parameters:
| Parameter | Default | Description |
|---|---|---|
type |
(all) | Filter by entity type: ip or user |
max_score |
1.0 |
Return only entities with score <= this value |
limit |
100 |
Maximum rows to return |
Returns entities sorted by score ascending (lowest trust first).
GET /api/zero-trust/scores/{entity_id}?type=ip
{
"entity_id": "45.33.32.1",
"entity_type": "ip",
"score": 0.35,
"label": "suspicious",
"signal_count": 14,
"last_updated": "2024-11-15T02:14:33Z",
"recent_signals": [
{"ts": "2024-11-15T02:01:00Z", "signal": "brute_force_fail", "delta": -0.05},
{"ts": "2024-11-15T02:01:05Z", "signal": "brute_force_fail", "delta": -0.05},
{"ts": "2024-11-15T02:14:33Z", "signal": "ueba_anomaly", "delta": -0.20}
]
}POST /api/zero-trust/scores/{entity_id}/reset?type=ip
Returns {"ok": true}. Use this when an entity is known good and has
been unfairly penalized (e.g. a legitimate administrator tripped the
unusual-hour detector).
The Zero-Trust Trust Scores panel appears in the Settings tab, above the Cloud Identity Connectors section.
Stats bar: total entities, count per label, average score.
Entity table: sorted by score ascending (untrusted first). Columns: entity ID, type (IP or user), trust label (color-coded), score bar, signal count, last signal received, Reset button.
Reset button: restores the entity's score to initial_score and
records a manual_reset signal. Does not delete the signal history.
CREATE TABLE zt_scores (
entity_id TEXT NOT NULL,
entity_type TEXT NOT NULL DEFAULT 'ip',
score REAL NOT NULL DEFAULT 0.8,
last_updated REAL NOT NULL,
signal_count INTEGER DEFAULT 0,
last_signal TEXT DEFAULT '',
PRIMARY KEY (entity_id, entity_type)
);UEBA: when UEBA detects a behavioral anomaly on successful login,
UEBA_ANOMALY is applied to the user's trust score. When login is
normal from a known IP, KNOWN_IP_LOGIN is applied (small trust boost).
Cloud identity: MFA failures apply MFA_FAILURE to both the IP
and the user. Azure risky sign-ins apply CLOUD_RISK_FLAG to both.
Kill chain: zero-trust does not directly update the kill chain, but because it lowers the effective threshold for suspicious entities, it causes earlier kill chain stage transitions (e.g. Delivery fires sooner when an IP already has a low trust score).
Pattern learner: zero-trust does not interact with pattern learning directly, but because trust-adjusted thresholds cause earlier alerting, patterns for low-trust IPs will have lower threshold values in their suggested rules.
Zero-trust support was listed as future work in the research paper, specifically noting that "the same IP from an admin account and an attacker" should be treated differently. This implementation uses behavioral evidence (UEBA, cloud risk, MFA) to build the identity-based trust score the paper described.
CNSL originated as a research prototype. The original research paper is available in the separate cnsl-research repository.