OraFail
A live, terminal-based dashboard that polls multiple Oracle databases concurrently for failed logon attempts. Designed for DBAs and security administrators, it helps identify potential brute-force attacks, misconfigured connection scripts, and unauthorized access attempts in real-time.
Features
- Concurrent Polling: Uses a thread pool (
ThreadPoolExecutor) to monitor multiple Oracle databases concurrently without blocking the UI. - Terminal UI Dashboard: A beautiful, responsive console interface built with Rich featuring:
- Live Aggregates: Displays logon failure counts grouped by sliding time-windows: 1 minute, 10 minutes, and 1 hour.
- Trend Indicators: Up, down, and neutral arrows comparison to the previous polling cycle.
- Connection Latency: Tracks response time (in milliseconds) for each target database.
- Highlight Aging: Color-codes new failure events in bold red and fades them through bold yellow over customizable cycles before returning to normal text.
- Integrated Log Viewer: Real-time log capture and display directly on the dashboard.
- Headless Daemon Mode: Run the monitor as a background service (
--headless) that streams structured log entries to stdout and log files. - Demo Mode: Run with synthetic data (
--demo) to explore the dashboard without Oracle connectivity. - Sortable Failure View: Sort the unified failure table by time, user, database, or count via
--sort-byor thesort_byconfig option. - Connection Caching & Resiliency: Caches connections, performs active ping health-checks, handles automatic reconnects, and enforces TCP connect and query execution timeouts.
- Auditing Source: Queries Oracle's
unified_audit_trail(whereaction_name = 'LOGON'andreturn_code != 0).
Installation
This project is built using Python 3.13+ and managed via the uv package manager.
-
Clone the repository:
git clone https://github.com/balddba/orafail.git cd orafail
-
Sync dependencies: This automatically configures a virtual environment and installs required packages (
oracledb,pydantic,pyyaml,rich,loguru):uv sync
Configuration
Copy the example configuration to create your local config file:
cp config.example.yaml config.yaml
Update config.yaml with your connection parameters. Do not commit config.yaml to source control, as it contains sensitive database credentials.
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
databases |
list |
Required | List of databases to monitor (each requiring name, dsn, user, and password). |
max_workers |
int |
5 |
Maximum number of concurrent database query worker threads. |
refresh_seconds |
int |
15 |
Polling frequency / UI refresh interval in seconds. |
highlight_ttl |
int |
3 |
Number of refresh cycles a new logon failure row remains highlighted. |
log_file |
str |
null |
Path to a log file for logging output. |
log_level |
str |
"INFO" |
Logging severity threshold (TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL). |
tcp_connect_timeout |
int |
10 |
Timeout in seconds when establishing database TCP connections. |
query_timeout |
int |
10 |
Timeout in seconds for executing the database audit query. |
sort_by |
str |
"time" |
Default sort column for the unified failure table (time, user, database, count). |
Sample config.yaml
databases:
- name: homelab
dsn: oracle26ai.aaronslab.net:1521/homelab
user: system
password: "YourSecurePassword"
max_workers: 5
refresh_seconds: 15
highlight_ttl: 3
sort_by: time
log_file: "monitor.log"
log_level: "INFO"
tcp_connect_timeout: 10
query_timeout: 10
Usage
You can use the bundled helper script orafail to run the application. The launcher will automatically prefer your uv environment, falling back to a virtual environment or system Python if necessary.
Command Line Interface
./orafail --help
usage: orafail [-h] [--config CONFIG] [--headless] [--demo]
[--sort-by {time,user,database,count}]
Oracle Login Failure Monitor
options:
-h, --help show this help message and exit
--config CONFIG Path to the YAML configuration file (default: config.yaml)
--headless Run in headless daemon mode (no terminal UI)
--demo Run in demo mode with synthetic data (no database connection required)
--sort-by {time,user,database,count}
Column to sort failures by (default: time)
Running the Live TUI Dashboard
./orafail
Running as a Headless Daemon
To run the monitor in the background or stream events to standard logging (e.g., for ingestion by SIEM tools like Splunk or ELK):
./orafail --headless
Demo Mode
Explore the dashboard with synthetic data — no config.yaml or Oracle connection required:
./orafail --demo
Demo mode uses three sample databases (prod-oltp, dw-warehouse, auth-db) and generates randomized failure events. If config.yaml exists, other settings (refresh interval, workers, etc.) are still loaded from it.
Sorting Failures
Sort the unified failure table from the CLI or config:
./orafail --sort-by user
./orafail --sort-by count
Valid values: time (default), user, database, count.
Project Structure
| Path | Purpose |
|---|---|
src/orafail/main.py |
Entry point, OracleLoginFailureMonitor, dashboard rendering |
src/orafail/config.py |
Re-exports AppConfig and DatabaseConfig |
src/orafail/models/ |
Pydantic models (AppConfig, DatabaseConfig, FailureDetail, DatabaseResult, AllResults, EventKey) |
config.example.yaml |
Configuration template |
config.yaml |
Active config with credentials — not committed |
scripts/publish.py |
Release automation (version bump, GitHub release, PyPI publish) |
orafail |
Bash launcher script |
Example Outputs
Headless Mode Logs
When running in --headless mode, the monitor logs connection statuses and logon failure events to stdout/log files:
2026-07-08 15:52:25.704 | INFO | orafail.main:run:573 - Oracle Login Failure Monitor starting...
2026-07-08 15:52:25.911 | INFO | orafail.main:run:589 - Database: homelab | Status: ONLINE | Latency: 204ms | Failures (1m/10m/1h): 0/0/1
2026-07-08 15:52:25.911 | WARNING | orafail.main:run:599 - NEW FAILURE on homelab | User: STEVE | IP: oracle26ai.aaronslab.net | Last Failed: 2026-07-08 15:38:15.064532
2026-07-08 15:52:25.911 | WARNING | orafail.main:run:599 - NEW FAILURE on homelab | User: SYSTEM | IP: Mac.aaronslab.net | Last Failed: 2026-07-08 13:58:53.295442
2026-07-08 15:52:25.911 | WARNING | orafail.main:run:599 - NEW FAILURE on homelab | User: AMYERS | IP: Mac.aaronslab.net | Last Failed: 2026-07-08 13:54:11.958124
2026-07-08 15:52:25.911 | WARNING | orafail.main:run:599 - NEW FAILURE on homelab | User: SYSTEM | IP: oracle-password-rotation-ui | Last Failed: 2026-06-30 14:36:37.579003
2026-07-08 15:52:25.911 | WARNING | orafail.main:run:599 - NEW FAILURE on homelab | User: C##DBA_PW_ROTATION | IP: dcc61bbb7878 | Last Failed: 2026-06-19 10:35:23.796769
Development & Testing
Running Tests
Execute the unit tests using pytest inside the workspace environment:
uv run --with pytest pytest
Formatting & Linting
We enforce codebase standards using Ruff. Run the formatter and linter before submitting PRs:
# Format codebase
uv run --with ruff ruff format src/ tests/
# Check lints
uv run --with ruff ruff check src/ tests/
Publishing Releases
The scripts/publish.py script automates version bumps, git commits, GitHub releases, and PyPI publishing:
# Interactive release (prompts for bump type)
uv run python scripts/publish.py
# Patch bump with confirmation bypass
uv run python scripts/publish.py --bump patch --yes
# Dry-run to preview steps
uv run python scripts/publish.py --bump patch --dry-run
# Build and publish to PyPI only (skip version bump and GitHub release)
uv run python scripts/publish.py --only-publish
Requires uv, gh (authenticated), and PyPI credentials (UV_PUBLISH_TOKEN or keyring).
Security Best Practices
- Least-Privilege Database Account: Create a dedicated read-only database user for monitoring. The user only needs access to
SYS.UNIFIED_AUDIT_TRAILor equivalent audit views. - Credential Management: Avoid embedding passwords in scripts. Keep
config.yamlsecurely on the system and restrict read permissions.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file orafail-0.2.0.tar.gz.
File metadata
- Download URL: orafail-0.2.0.tar.gz
- Upload date:
- Size: 656.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f57173a5ff45d94cd51b22f889dd79d8ff631ceec3944f4ac53da80eb2bd99a0
|
|
| MD5 |
72a76c94ce9c880ea1bf8b2d48f4d1e4
|
|
| BLAKE2b-256 |
7d35dd600b26cd4c06e7d96167218a40f36a5c82424f8fa9d6d8df75f6faf43d
|
File details
Details for the file orafail-0.2.0-py3-none-any.whl.
File metadata
- Download URL: orafail-0.2.0-py3-none-any.whl
- Upload date:
- Size: 42.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87cc4e72ef777747cf82a40749d13a978928ae93f46c1af6c773b7407d983a3c
|
|
| MD5 |
9f882753e0039ff6333a9a588e4a0c38
|
|
| BLAKE2b-256 |
c78d2b095e30d865cf005d6e0655cdf5d870a6fd487d0a3f8d75c47f5f63034c
|