Skip to content

enh(security): Improve whitelisting and threshold applications#136

Merged
nfebe merged 8 commits into
mainfrom
fix/security-whitelist-and-settings
Jun 6, 2026
Merged

enh(security): Improve whitelisting and threshold applications#136
nfebe merged 8 commits into
mainfrom
fix/security-whitelist-and-settings

Conversation

@nfebe

@nfebe nfebe commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

No description provided.

nfebe added 2 commits June 6, 2026 14:51
Events ingested from nginx were classified and could auto-block an IP
even when that IP or path was whitelisted. The whitelist was only
honored by the nginx Lua cache, which is empty right after a restart,
so a whitelisted source could still end up blocked. Whitelisted
networks now match by CIDR containment and paths by prefix, and
whitelisted traffic never generates events or blocks.
Security detection thresholds updated through the key-based config API
now take effect on the running detector immediately instead of waiting
for the agent to restart.
@sourceant

sourceant Bot commented Jun 6, 2026

Copy link
Copy Markdown

Code Review Summary

This PR significantly improves security by implementing trusted proxy validation and improving the efficiency of the whitelisting system with an in-memory cache.

🚀 Key Improvements

  • Secure client IP identification via trusted proxy lists.
  • In-memory caching of whitelist entries in internal/security/whitelist.go.
  • Dynamic configuration reloading for security thresholds and proxy settings.
  • Improved IP/CIDR handling using the modern netip package.

💡 Minor Suggestions

  • Consolidate CIDR matching logic in Lua templates to ensure consistency between tests and production.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread internal/security/whitelist.go
Whitelist matching no longer reads the database and re-parses CIDR
ranges for every ingested event. The cache is rebuilt after any
whitelist change, so additions and removals still take effect
immediately.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread internal/security/whitelist.go

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread internal/api/system_terminal.go Outdated
The capture tests send requests from inside the Docker network, so
their source addresses fall in the private ranges that are whitelisted
by default and are now correctly skipped during ingestion. The tests
now present public client addresses through the forwarding header, and
the e2e copy of the capture script resolves the real client IP from
forwarded headers the same way the production template does.
@nfebe nfebe force-pushed the fix/security-whitelist-and-settings branch from a9930c3 to 978346f Compare June 6, 2026 16:29

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread internal/api/config_handlers.go Outdated
Comment thread test/e2e/nginx/lua/security.lua
The realtime capture script read the client address straight from
X-Forwarded-For and CF-Connecting-IP, so any client could spoof those
headers to impersonate another address. Combined with default
whitelisting of private ranges, a request could forge a whitelisted or
internal source and bypass detection, evade an existing block, or get
an innocent address blocked.

Forwarded headers are now honored only when the connecting peer is one
of the operator-configured trusted proxies, and the real client is
taken as the first forwarded hop that is not itself a trusted proxy.
With no proxies configured the connecting peer is always used, so
forwarded headers cannot be spoofed out of the box.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread internal/security/manager.go
Comment thread templates/infra/nginx/lua/security.lua
Changing the trusted proxy list or Cloudflare-header trust through the
config API now regenerates the capture script and reloads nginx so the
new trust rules take effect immediately, instead of waiting for a
manual script regeneration. If nginx is not running the value is still
saved and the response reports that it was not applied.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread test/e2e/nginx/lua/security.lua
The e2e capture script only does IPv4 CIDR matching. An IPv6 range now
explicitly marks the peer untrusted rather than relying on incidental
nil handling, so a forwarded header is never honored for it. The
production template already matches IPv6 ranges directly.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

}
}

m.wlMu.Lock()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This write lock is acquired too late to prevent redundant database queries during high concurrency. If multiple requests see m.wlCache == nil, they will all proceed to query the database. While the existing review mentioned a 'double-check' pattern, the current implementation still performs the heavy lifting (DB fetch) before the lock.

Suggested change
m.wlMu.Lock()
m.wlMu.Lock()
defer m.wlMu.Unlock()
if m.wlCache != nil {
return m.wlCache, nil
}
entries, err := m.db.GetWhitelist()
if err != nil {
return nil, err
}
cache = &whitelistCache{ips: make(map[string]struct{})}
// ... (rest of parsing logic) ...
m.wlCache = cache
return cache, nil


result := &IngestResult{}

whitelisted, err := m.IsRequestWhitelisted(event.SourceIP, event.RequestPath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performing the whitelist check before the block check is correct for performance (whitelists are usually smaller). However, ensure that event.SourceIP is already normalized (e.g. trimmed) before this call to avoid bypasses using whitespace.

Suggested change
whitelisted, err := m.IsRequestWhitelisted(event.SourceIP, event.RequestPath)
sourceIP := strings.TrimSpace(event.SourceIP)
whitelisted, err := m.IsRequestWhitelisted(sourceIP, event.RequestPath)
if err != nil {
return nil, err
}
if whitelisted {
return result, nil
}

A trusted-proxy entry containing a quote could break out of the Lua
string literal it is rendered into and inject code into the nginx
worker. Entries are now validated as IP or CIDR values and reduced to
their canonical form before injection, and zoned IPv6 addresses (which
can carry arbitrary characters) are rejected, so a malformed or hostile
entry is dropped rather than executed.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

@nfebe nfebe merged commit 0537084 into main Jun 6, 2026
5 checks passed
@nfebe nfebe deleted the fix/security-whitelist-and-settings branch June 6, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant