- Java 97.1%
- Shell 2.7%
- HTML 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Marked task 011 (authenticated viewer sessions) as complete and updated next steps for task 012. Revised task details, dependencies, and metrics to reflect the updated implementation status. Included references to verification evidence and clarified completion criteria. |
||
| docs | ||
| intruvia-app | ||
| intruvia-core | ||
| intruvia-rest | ||
| intruvia-web | ||
| probes | ||
| scripts | ||
| .gitignore | ||
| AGENTS.md | ||
| LICENSE | ||
| next-task.sh | ||
| pom.xml | ||
| README.md | ||
Intruvia
Intruvia is a self-hosted security-event viewer under development. See the specification, backlog, and status ledger for scope and implementation progress.
Run the next implementation task
The runner needs Bash, flock, and an authenticated Codex CLI. It finds Codex on
PATH or uses the desktop app's bundled /usr/lib/chatgpt/resources/codex executable.
To select another installation, set CODEX_BIN=/full/path/to/codex.
If authentication is needed, run the selected executable with login (for example,
/usr/lib/chatgpt/resources/codex login). From the project directory, run:
./next-task.sh --dry-run
./next-task.sh
You can also invoke the script by absolute path from any directory. Each invocation starts a fresh Codex CLI session using your configured model and authentication. It instructs Codex to resume an in-progress task or choose the first eligible TODO, implement and verify it, and update the ledger. Run it again for the following task. This does not continue the desktop conversation; project files provide its context.
The run can edit project files and execute checks in the workspace-write sandbox. If permissions or prerequisites prevent completion, the agent should record the blocker. Review its final report and the status ledger: a successful CLI exit alone does not prove that the task is DONE. Avoid editing the same task concurrently in another session. The script lock prevents overlapping invocations of this script.
See runner details and the official Codex non-interactive documentation.
License
Intruvia is licensed under the GNU Affero General Public License, version 3.0
(SPDX: AGPL-3.0-only). See LICENSE for the full terms.
Third-party components retain their respective licenses.
Verified runtime baseline
Task 001 pins Java 25, Strolch, Jersey and Jetty in an isolated compatibility probe. See the baseline decision for exact versions, integration conventions and verification limits. Run it with Java 25:
JAVA_HOME=/path/to/jdk-25 PATH=/path/to/jdk-25/bin:$PATH \
mvn -B -f probes/runtime-baseline/pom.xml clean verify
The probe starts Strolch/Jetty on loopback, verifies REST and WebSocket exchange, and shuts both down. It needs Maven dependency access but no PostgreSQL server. The application skeleton is available below; the event viewer remains pending.
To retry a blocked task after fixing its environment:
./next-task.sh --retry-blocked
The runner enables outbound network access for dependency downloads and permits
writes to ~/.m2/repository while retaining the workspace filesystem sandbox.
If Maven uses a custom local repository, set MAVEN_REPOSITORY to that absolute path.
The retry checks the blocker again and preserves previous failure evidence.
Build and run the application
Use Java 25 and Maven (verified with Maven 3.9.16). From the repository root:
JAVA_HOME=/path/to/jdk-25 PATH=/path/to/jdk-25/bin:$PATH scripts/verify-postgresql.sh
# Configure an external runtime and PostgreSQL as described below, then:
/path/to/jdk-25/bin/java -jar intruvia-app/target/intruvia/intruvia-app-0.0.1.jar /path/to/runtime 8080
Open http://127.0.0.1:8080/ for the packaged local landing page.
http://127.0.0.1:8080/health/live returns {"status":"UP"}.
Stop with Ctrl+C or SIGTERM; Jetty stops before Strolch is stopped and destroyed.
The port is optional (default 8080); 0 selects an ephemeral port printed in the log.
The runtime path is required and can be absolute. Keep the application JAR, lib/
and runtime/ together when copying the assembled directory.
Locally installed Strolch artifacts
The historical baseline uses a timestamped Strolch snapshot; the current POM selects
2.8.0-SNAPSHOT for the required PAT lifecycle fix. Maven cannot satisfy an exact
timestamped coordinate with an installed 2.8.0-SNAPSHOT, even when its JARs are present.
For development with the local framework referenced by AGENTS.md, use the existing
version property override (offline mode uses the installed artifacts without a mirror):
scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT
Use Java 25 as above. If the local artifacts need rebuilding, install the required framework modules and their reactor dependencies first:
mvn -f /home/eitch/src/git/atx-dev/strolch/pom.xml \
-pl strolch-service,strolch-persistence-postgresql -am install -DskipTests
The bootstrap command skips framework tests; Intruvia's full verification still runs.
Omit -o when other dependencies need downloading. Record the override and artifact
checksums with verification evidence: local snapshot results do not verify the original
timestamped binaries. Do not rename local JARs to impersonate timestamped artifacts.
The application binds only to loopback and now requires durable PostgreSQL storage.
The ingestion HTTP endpoint and viewer sessions are available. The viewer UI and
readiness endpoint remain pending. The packaged production environment rejects
transient stores; there is no memory-only fallback.
mvn verify requires a disposable PostgreSQL service. scripts/verify-postgresql.sh
starts a loopback Docker PostgreSQL container, runs mvn -B clean verify, and removes
the container on exit. Each test provisions and drops a unique database. Docker and
Java 25 must already be installed. The default image is postgres:18-bookworm; set
INTRUVIA_TEST_POSTGRES_IMAGE to a local image or immutable digest when needed.
Alternatively, supply INTRUVIA_TEST_DB_URL, INTRUVIA_TEST_DB_USERNAME and
INTRUVIA_TEST_DB_PASSWORD for a dedicated test PostgreSQL role with CREATEDB and
run mvn -B clean verify. Never point tests at production. No browser/Node build is required.
For a persistent runtime, copy the packaged runtime/ outside target/, copy its
config/Privilege*.xml.example files to the corresponding .xml names, and replace
both CHANGE-ME values in PrivilegeConfig.xml with independently generated secrets.
Keep runtime configuration and the process environment protected; example roles contain
a system agent and a Viewer role; provision viewer users as described below.
Set DB_URL (a PostgreSQL JDBC URL),
DB_USERNAME and DB_PASSWORD through protected service configuration, not command-line
arguments. Use a dedicated empty database owned by the application role.
On the first start only, set allowSchemaCreation to true in
config/StrolchConfiguration.xml; after successful startup, stop the application and
restore it to false. Strolch installs its pinned schema and the application runs model
migration 0.0.1. Keep allowSchemaDrop and allowDataInitOnSchemaCreate false.
Schema upgrades require a backup and an explicit maintenance change to
allowSchemaMigration; it defaults to false. Startup fails on an absent/incompatible
schema when creation/migration is disabled. Model migration versions, events, receipts
and stream state survive restart. See persistence design and checks.
Event contracts (v1)
The core module now provides immutable event, GeoIP, receipt and stream-state contracts
and a typed Strolch Resource mapper. See the field mapping,
v1 API schema and complete synthetic fixture.
Sequences are decimal strings on the wire; absent coordinates remain null. The fixture
uses a documentation IPv6 address with invented geography solely for serialization tests;
real ingestion classifies that address as NON_PUBLIC. The ingestion HTTP endpoint is available below. Run scripts/verify-postgresql.sh with Java 25 to verify JSON and
Strolch XML round trips, including IPv6, partial locations and 64-bit sequence precision.
Machine ingestion credentials
Machine authentication uses Strolch Personal Access Tokens (PATs). The ingestion
endpoint is POST /api/v1/fail2ban/events. Send Authorization: Bearer <tokenId>:<tokenValue> over
HTTPS. Tokens must have exactly the event:ingest privilege; viewer/session credentials
and broad PATs are rejected.
Provision one ENABLED Strolch technical user per server, with an ingestion-only role. Use supported Strolch management APIs to issue a scoped PAT and persist it. Store the returned secret directly in a protected producer file; never put it in arguments, logs or source control. See the verified APIs, role configuration and rotation procedure.
Create the optional external runtime/config/machine-identities.conf with mode 0600
(or 0400), containing one username-to-stable-instance mapping per line:
producer-a=server-a
producer-b=server-b
It contains identities only, no credentials. Both names must match
[A-Za-z0-9][A-Za-z0-9._-]{0,127}; duplicate users or instance IDs are rejected.
Blank lines and # comments are allowed. Protect the runtime and parent directories.
Missing mapping disables machine access; malformed/insecure files fail startup.
Restart to change mappings.
Rotate by issuing a replacement PAT for the same technical user, persisting it, switching the producer file, then revoking and persisting removal of the old PAT. The stable server ID is unchanged. Revocation, expiry and disabled owners are checked even for cached PATs, without an Intruvia restart. The obsolete custom TSV verifier is no longer loaded. No public token-administration endpoint is exposed.
Task 005 was verified using local Strolch 2.8.0-SNAPSHOT with the upstream revocation
fix; see source/binary identities
and full regression results.
Fail2ban input validation
Task 006 adds a strict, bounded JSON parser and canonical Fail2ban adapter. The public ingestion endpoint uses this parser. Inputs are limited to 16 KiB by default; unknown/duplicate fields, invalid literals and malformed values are rejected. IPv4-mapped IPv6 becomes IPv4, timestamps become UTC, and equivalent payloads share a stable digest. Jail text remains plain data, with a 128-character Unicode limit. Accepted retries can be checked before the separate seven-day age / five-minute future policy. See the validation table and integration contract for limits, constructor configuration, digest format and trusted envelope mapping.
Run focused validation and contract tests without PostgreSQL using Java 25:
mvn -B -o -Dstrolch.version=2.8.0-SNAPSHOT -pl intruvia-rest -am clean test
Local GeoIP enrichment
Task 007 provides a lifecycle-managed local MaxMind City reader. In your external
runtime/config/StrolchConfiguration.xml, set the GeoIp component property:
<Properties><databasePath>/srv/intruvia/geoip/GeoLite2-City.mmdb</databasePath></Properties>
Supply your own licensed GeoLite2 City or GeoIP2 City database and keep it readable
by the service account. No database, license key, download or per-event network
lookup is bundled. The default empty path, missing file or failed load leaves
GeoIP degraded (UNAVAILABLE) while the application starts. Private/reserved
addresses yield NON_PUBLIC; missing records and lookup failures have distinct
statuses. Partial locations remain unmapped, and legitimate zero coordinates are
preserved. GeoIP is approximate network location, not a person's physical location.
The reader keeps a reusable in-memory snapshot; budget heap for the database size.
For live replacement, publish a complete file by atomic rename to
<databasePath>.staged. Intruvia checks it every 60 seconds, validates every record,
and atomically replaces the working database and reader. Failed updates retain the
working reader; lookups continue during validation. Build/load time, age and the
default 14-day stale flag are available through the component diagnostics API.
Budget roughly three database sizes of heap during validation. See the
update procedure and external geoipupdate example
for directory permissions, configuration, recovery and shutdown behavior.
Enrichment is used by the ingestion service and public ingestion endpoint.
See policy, configuration, provenance and synthetic test fixtures.
Atomic ingestion service
Task 009 provides a Strolch service for validated Fail2ban events. Application code
invokes a fresh IngestService through the registered ServiceHandler, using the
producer's framework certificate and the protected identity mapping. It requires
event:ingest; identity comes from the certificate username. GeoIP runs before the
write transaction, and event, receipt and sequence commit atomically. Equal retries
return the original ID/sequence, including after event pruning; changed payloads
conflict. An accepted retry bypasses timestamp-age rejection and new enrichment.
The public POST endpoint delegates to this service. See the service contract and locking
protocol for result handling and later
retention integration. Run scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT
with Java 25 for the real-database concurrency, rollback and retry checks.
Fail2ban ingestion HTTP endpoint
Send the v1 JSON payload from the specification
to POST /api/v1/fail2ban/events, with Content-Type: application/json and the
Strolch PAT Authorization header described above. Use HTTPS at the reverse proxy;
the application remains bound to loopback. The request body must contain only the
producer fields, never a server identity or geography. The certificate and protected
mapping select the server identity, and the same certificate authorizes the service.
A committed event returns 201 with id, decimal-string sequence, duplicate:false
and geoStatus. An identical retry returns 200 with the original id, sequence
and duplicate:true; it omits geoStatus because a retained receipt may outlive its
event. Changed retries return 409. Validation, authentication, privilege, size and
media failures return 400, 401, 403, 413 and 415; database failures return
503 without acknowledging success. Errors have {error:{code,message,requestId}}.
Responses use Cache-Control: no-store and do not echo payloads or credentials.
Optionally copy runtime/config/ingestion.properties.example to ingestion.properties
in the external runtime. Defaults are a 16 KiB UTF-8 body, seven-day maximum event age,
five-minute future allowance, and 10 requests/second with burst 100 per configured
instance. All authenticated attempts, including retries and rejected payloads, consume
that instance's bucket. PAT rotation shares the bucket. A separate global bucket allows
100 API requests/second with burst 1000 before authentication; Jetty caps connections at
256 with a 30-second idle timeout. Request-rate rejection returns 429 with integer
Retry-After seconds; connection saturation pauses accepts. Reverse-proxy connection
and request limits should also cover internet-facing traffic. No request queues or rate
state are persisted; restart resets buckets. Compressed request bodies are unsupported.
See HTTP design, setting bounds and checks.
Verify with Java 25 and scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT.
Receipt-capacity admission is task 013; WebSocket delivery remains pending.
Viewer sessions
Copy runtime/config/viewer.properties.example to viewer.properties in your
external runtime and set origin=https://your-intruvia-host (no trailing slash).
Missing configuration disables viewer sessions. HTTP is allowed only for explicit
loopback development origins. HTTPS origins receive Secure, HttpOnly,
SameSite=Strict cookies restricted to /api/v1; no token belongs in browser storage.
Provision an ENABLED Strolch user with the supplied Viewer role (event:read)
and a Strolch password hash in protected external privilege configuration. Existing
runtimes must also add the SessionHandler component and agent GetCertificates
permission from the updated examples. See the provisioning steps and HTTP contract.
The same-origin client logs in with POST /api/v1/session, JSON {username,password},
Content-Type: application/json and X-Intruvia-CSRF: 1; the browser sends Origin.
GET /api/v1/session checks the cookie session. DELETE /api/v1/session logs out
with the same CSRF header and Origin. Both login and session checks return only
{username}. Sessions expire after at most 30 minutes by default, and logout
invalidates the shared REST/future WebSocket validator immediately. Authorization
headers and machine PATs cannot authenticate viewer routes.
The login/map UI is task 015; read APIs and the WebSocket endpoint are still pending.
Verify sessions and the full regression suite with Java 25 and
scripts/verify-postgresql.sh -o -Dstrolch.version=2.8.0-SNAPSHOT.