A security prototype, 110 passing tests, real local socket experiments, 12 Observer findings, and one important rule: a passing test is not a production deployment.
Thank you for your support! Every test, every bit of feedback, and every conversation helps move this project forward.
https://www.npmjs.com/package/sentinel-ir-mcp/v/0.3.0
Repository: "Agentix Lite at commit ddf626d" (https://gitlab.com/jakubrojicek11/agentixlite/-/tree/ddf626d1c3e9a7f4b2ee91d019f7c624266b4c2d/)
https://www.npmjs.com/package/agentixlite/v/0.8.1
The 30-second version
What happens when you try to audit a Linux security prototype using an Android phone?
I spent this audit testing Agentix Lite v0.8.1 in Termux. We examined its event pipeline, UNIX socket authentication, reputation engine, SQLite storage, TCP honeypot, MCP interfaces, Shadow Observer, and systemd integration.
The complete Python test suite reported 110 passed. A real local TCP-to-engine experiment passed 8/8 checks. The watchdog integration experiment passed 5/5.
We also found 12 Observer issues and several deployment limitations.
One important detail: this was a prototype tested on Android/Termux, not a production VPS deployment. No real production firewall enforcement or seven-day server observation was claimed.
Now let's unpack what those numbers actually mean.
Part 1: The 2-minute explanation for everyone
What is Agentix Lite trying to do?
Imagine a small security guard for a Linux server.
It watches incoming activity, looks for suspicious behavior, evaluates the source, and records what happened.
Its basic architecture looks like this:
- A sensor notices something suspicious.
- It creates a small event describing what happened.
- The event travels through a local UNIX socket.
- The main engine evaluates the event and updates reputation.
- The result is stored in SQLite.
- In Shadow Mode, the system can recommend an action without actually blocking anyone.
That final point matters.
Shadow Mode is observation, not enforcement.
The system can say:
"This IP looks suspicious. It would qualify for a temporary ban."
But it does not have to touch the firewall.
Think of it as a fire alarm being tested without actually flooding the building with water.
What did we test?
We did not just inspect Python functions and declare victory.
We created temporary databases, local sockets, separate processes, synthetic events, and a local TCP connection.
One experiment followed this complete route:
TCP connection → honeypot listener → authenticated UNIX datagram → EventReceiver → Agentix Engine → SQLite.
The actual TCP connection came from the local IPv6 loopback address "::1".
Then we injected a separate synthetic event using "8.8.8.8" to verify the Shadow Mode decision.
The results:
- The real TCP event reached the engine.
- The loopback address was not turned into a ban candidate.
- The synthetic external address became "BAN_CANDIDATE".
- No real firewall operation happened.
- Both actors remained stored in SQLite.
Result: 8/8 checks passed.
Why not just say it works?
Because there is a difference between a working prototype and a production security system.
Our tests ran on Android through Termux. We did not install Agentix on a real Debian or Ubuntu VPS during this audit.
We tested many internal mechanisms, but we did not prove how the whole system behaves under real server traffic, real systemd supervision, or long-term operating conditions.
That distinction is not a weakness in the report. It is the point of the report.
Part 2: The 8-minute developer deep dive
- Test environment and scope
The audit was performed against the local repository at:
Commit: ddf626d1c3e9a7f4b2ee91d019f7c624266b4c2d
Version: Agentix Lite v0.8.1
Platform: Android / Termux
Architecture: aarch64
Python: 3.13.13
Node.js: 26.3.1
Go: 1.26.4
Python tests used an isolated virtual environment.
The repository remained clean throughout the audit. No application source code was changed as part of these experiments.
Temporary databases, sockets, test directories, and synthetic inputs were used to isolate the test runs.
Full Python suite
The complete repository test suite reported:
110 passed, 1 warning in 14.23s
The warning concerned the deprecation of the current HTTPX integration with Starlette's TestClient. It was not a failed test.
The targeted experiments below were separate test runs. Their counts should not be added together as though they represented one unique global test suite.
- Core engine and reputation
We examined event validation, pattern detection, reputation scoring, and engine decisions.
The component-level checks included:
Component| Result
Detection, events, patterns, reputation| 16/16
Engine integration with temporary SQLite and FakeFirewall| 13/13
Storage and ghost actor behavior| 10/10
RollingBuffer| 5/5
One important test used a fake firewall:
class FakeFirewall:
def init(self):
self.batches = []
def block_many(self, ips, timeout_seconds):
self.batches.append((list(ips), timeout_seconds))
return FirewallResult(len(ips), len(ips), 0)
This allowed the engine's decisions to be observed without applying real firewall rules.
For Shadow Mode, a synthetic honeypot event from an external IP was expected to produce:
BAN_CANDIDATE
The fake firewall remained untouched.
This distinction is important when testing security software. We want to verify decision logic without accidentally turning an experiment into a real blocking operation.
- SQLite, WAL, and bounded storage
Storage testing covered:
- SQLite persistence.
- WAL behavior under sustained reading.
- Capacity pressure.
- Ghost actor admission and eviction.
- IPv6 fingerprint handling.
- Rolling event buffers.
Two important experiments showed why configured limits must be interpreted carefully.
In one WAL test:
Configured hard limit: 1 MiB
Observed physical WAL: 1,050,632 bytes
In another:
Configured hard limit: 256 KiB
Observed physical WAL: 276,072 bytes
The logical write guard rejected additional work, but the physical WAL file could exceed the configured threshold.
This means the guard is useful for controlling application-level storage pressure. It should not be advertised as an exact physical filesystem limit.
That is a small but important wording difference.
- UNIX sockets and authenticated event delivery
Agentix separates telemetry into three ingress lanes:
- Normal events.
- Honey critical events.
- Host critical events.
The receiver enables Linux peer credential passing:
sock.setsockopt(
socket.SOL_SOCKET,
socket.SO_PASSCRED,
1,
)
It extracts the credentials from the received ancillary data and checks the sender UID against the expected identity for the lane.
This is more meaningful than trusting an event's JSON field claiming to be from a particular service.
We also tested concurrency, queue limits, and per-IP fairness.
One initial concurrency assertion expected 60 accepted host events. The actual configured per-IP queue limit produced 32.
That was a test expectation error, not proof of a product defect. We corrected the interpretation and separately exercised fairness and rate limiting.
A real local TCP-to-engine experiment
The end-to-end experiment used an actual local TCP listener and the existing honeypot implementation.
The observed event included:
source = honeypot_port
ip = ::1
path = tcp/
The receiver reported:
received: 1
queued: 1
credential_rejected: 0
listener_errors: 0
The engine then processed the event and persisted the actor.
A second synthetic event from "8.8.8.8" was used to verify the external ban-candidate decision.
Final result:
PASSED: 8
FAILED: 0
TOTAL: 8
No real firewall rules were changed.
One honeypot limitation
We also tested a failed telemetry delivery.
The honeypot listener updates its per-IP cooldown before sending the event. The telemetry client performs one non-blocking send and does not retry.
If the receiver is missing, the event is lost, but the cooldown is still consumed.
A second connection from the same IP during the cooldown therefore does not generate another event.
This was reproduced with a controlled local experiment.
Finding: "HONEYPOT-001".
It is an event availability limitation, not evidence of a firewall bypass.
- MCP interfaces and runtime truth
We tested the Python MCP server and the Go gateway separately.
Results included:
Area| Result
Python MCP STDIO| 16/16
Go gateway unit tests| 6/6
Go gateway HTTP checks| 15/15
Go gateway STDIO checks| 13/13
The gateway's health endpoint checks whether the runtime status exists, whether its process is alive, and whether its status is stale.
However, we found an important runtime reporting issue.
MCP-STATUS-001: Enforcement status is not authoritative
The Go gateway's shadow summary hardcodes:
mode = shadow
enforcement_enabled = false
It does not derive these values from an authoritative daemon runtime snapshot.
The Python MCP interface reads its own process configuration, which is not necessarily the same thing as the running daemon's actual state.
An AI client could therefore receive an inaccurate description of the system's enforcement state.
A read-only interface still needs trustworthy status data.
Read-only does not automatically mean reliable.
- Shadow Observer: twelve findings
The Observer reads runtime status, storage metrics, system information, service state, and optional MCP health.
It writes measurement samples to JSONL. It does not directly change firewall rules or Agentix configuration.
We tested its coverage calculations, counters, restart handling, malformed data, schema versions, and timestamps.
The audit identified twelve findings:
ID| Finding| Why it matters
OBSERVER-001| Drop counters can be double-counted| Inaccurate loss reporting
OBSERVER-002| Malformed JSONL lines are silently skipped| Missing evidence can go unnoticed
OBSERVER-003| Failed systemd inspection can appear as empty data| Service state may be misrepresented
OBSERVER-004| Disabling observation during a later installer run does not explicitly stop an already enabled timer| Possible unexpected sampling
OBSERVER-005| Service restarts are recorded but do not generate anomalies| Operational explanations can be missed
OBSERVER-006| Controller-level firewall shedding is not independently classified as an anomaly| Some firewall losses can remain unnoticed
OBSERVER-007| Structurally invalid JSON records can crash reporting| One bad record can break a summary
OBSERVER-008| Unknown schema versions are accepted| Compatibility is not guaranteed
OBSERVER-009| Future timestamps can pass coverage checks| False evidence of observation
OBSERVER-010| Agentix version changes are not flagged| Mixed-version measurements go unnoticed
OBSERVER-011| Numeric and ISO timestamps are not cross-validated| Contradictory time information is accepted
OBSERVER-012| Duplicate timestamps can make counter calculations order-dependent| Reports can become nondeterministic
These findings have different levels of operational importance. They are not twelve security exploits.
Example: duplicate timestamps
We deliberately created two input files containing samples with an overlapping timestamp.
The same records in a different file order produced different counter deltas:
Input order A: events delta = 1100
Input order B: events delta = 2100
The report accepted four records with three unique timestamps and raised no anomaly.
This matters because monitoring output should be deterministic when the underlying evidence is identical.
Example: coverage is not health
We also tested stale, unknown, and stopped runtime states.
The time coverage gate could still be true even when runtime health was not good.
That is not necessarily a violation of the gate's narrow definition. It means operators must never interpret:
coverage_gate = true
as:
the system was healthy
Those are different claims.
- Deployment and systemd checks
The deployment review covered:
- "deploy/setup.sh"
- "systemd" service and timer units
- runtime directory creation
- Docker configuration
- UNIX socket permissions
- nftables preflight
- MCP binary architecture
- watchdog integration
The installer passed its shell syntax check:
bash -n deploy/setup.sh
The runtime directories are created through:
systemd/agentix-tmpfiles.conf
The Honey API container is configured to run as UID "65534", with the Agentix group added. The receiver expects the Honey sender UID to be "65534".
The design is consistent on paper, but the full Docker-to-host socket delivery was not tested on a production VPS.
Watchdog integration
The main systemd service declares:
Type=notify
NotifyAccess=main
WatchdogSec=30s
The engine derives its heartbeat interval as half the configured systemd watchdog timeout.
For a 30-second timeout:
Expected heartbeat interval = 15 seconds
The main loop calls:
engine.periodic_cleanup(receiver)
even when there are no events. During idle periods it waits for 20 milliseconds before continuing.
We then tested the actual engine heartbeat through a local UNIX notification socket.
Results:
PASS | Watchdog interval is half systemd timeout
PASS | Engine sends watchdog heartbeat
PASS | Engine sends status after heartbeat
PASS | Heartbeat is throttled between intervals
PASS | Heartbeat is sent again after interval
PASSED: 5
FAILED: 0
TOTAL: 5
This verifies the local engine-to-notification path. It does not replace a live systemd deployment test.
- Deployment limitations discovered
Architecture mismatch
The bundled gateway is:
bin/agentix-mcp-linux-amd64
ELF 64-bit LSB x86-64
The Android test environment is:
aarch64
The native validator includes both x86-64 and ARM64 builds, but the Go MCP gateway currently has only the AMD64 binary in this checkout.
The installer checks whether the binary is executable, not whether its architecture matches the host.
This could cause the MCP service to fail on an ARM64 VPS if enabled with that binary.
Existing nftables table
The installer applies the nftables ruleset only if the Agentix table does not already exist.
If an older table exists but lacks one of the expected sets or chains, the preflight can fail rather than automatically reconciling it.
That is safer than silently assuming the firewall is correct, but it is an installation and upgrade limitation.
Android runtime path
The prototype uses:
/run/agentix/status.json
In our Termux environment "/run" did not exist and the write failed with:
errno 30: Read-only file system
Writing to a temporary writable directory succeeded.
The Linux deployment installs a systemd tmpfiles configuration to create "/run/agentix" and its subdirectories. Therefore, this Android failure is an environment difference, not proof that the Linux deployment has the same problem.
Final verdict
Agentix Lite v0.8.1 has a substantial amount of locally tested functionality.
The audit demonstrated:
- Event processing and reputation decisions.
- Authenticated local event transport.
- SQLite persistence and capacity handling.
- A real local TCP honeypot-to-engine path.
- Shadow Mode behavior without real firewall operations.
- Python and Go MCP interface tests.
- Watchdog integration logic.
- A set of reproducible Observer edge cases.
But there is a boundary we must respect.
This was a prototype tested in Termux on Android. It was not a production VPS audit.
We did not perform:
- A live Debian/Ubuntu systemd deployment.
- A real production Docker-to-host socket integration.
- A real nftables enforcement test.
- A 24-hour Shadow Mode observation.
- A seven-day observation period.
- A DDoS-resilience assessment.
The next meaningful validation step is a dedicated Debian/Ubuntu AMD64 VPS running Agentix in Shadow Mode, with enforcement disabled.
Collect real runtime evidence, explain anomalies, inspect candidate-ban precision, and only then consider further operational claims.
No automatic enforcement should be enabled just because the local test suite is green.
A green test suite is a useful signal. It is not a production certificate.
Experiment first. Headline second.

Top comments (0)