The Problem

Organizations struggle to capture, score, and track IT & cyber risk across the full life cycle. Existing tools either focus on ticketing, asset inventory, or vulnerability scans, but not on a continuous risk register that records probability‑impact scores, action plans, and audit trails.

What This Does

RiskOps is a PHP 8.3 web app that stores a risk register, computes a 5 × 5 probability‑impact matrix on the server, and links each risk to mitigation actions.

  • The core data model lives in database/schema.sql; includes/risk.php contains the logic for score calculation (GENERATED ALWAYS AS (likelihood * impact)).
  • Risk CRUD is handled by the actions folder (actions/create.php, actions/update.php, etc.), while administration of categories, departments, and users is in admin/.
  • User authentication is done in auth/ (e.g., auth/login.php), and session handling is bootstrapped in includes/bootstrap.php.
  • Reporting and export are in reports/ and the CSV exports are driven by reports/export_csv.php.

How It Is Wired

Execution starts at the web root index.php.

  1. index.php includes includes/bootstrap.php.
  2. bootstrap.php loads config/config.php, establishes a PDO connection (includes/db.php) and sets up global helpers (includes/functions.php).
  3. The requested URL is parsed by PHP’s native routing (each .php file is a separate endpoint). For example, a POST to actions/create.php triggers include 'includes/risk.php', then calls Risk::create($data) which inserts into the risks table.
  4. Risk::create writes the record, relies on the database trigger for the score column, and returns the new ID.
  5. The script then redirects or returns JSON, depending on the X-Requested-With header.
  6. Audit logs are written by includes/audit.php whenever admin/ endpoints modify data, and user actions are tracked through includes/debug.php.

The biggest blast‑radius functions are Risk::create/update/delete and Audit::log; they touch the database and the audit log table. The routing layer is a simple include‑by‑URL scheme, so a change in any endpoint file only affects that path. No cyclic dependencies are detected in the call graph; all helpers are stateless.

How To Use It

Setup

git clone https://github.com/moses-y/riskops
cd riskops
cp config/database.example.php config/database.php
docker compose up -d

Configuration Edit config/database.php with your MariaDB credentials. The Docker image automatically creates a database using database/schema.sql and seeds it via database/seed.sql.

Running Access the web UI at http://localhost:8080. No PHP built‑in server is needed because Docker exposes port 8080.

Real‑World Use

A security analyst creates a new risk:

curl -X POST http://localhost:8080/actions/create.php \
     -H "Content-Type: application/json" \
     -d '{"title":"Third‑party API exposure","likelihood":4,"impact":5,"category_id":2}'

The response contains the new risk ID, and the risk appears in the dashboard (dashboard/index.php).

Code Health & Issues

  • No CI/CD – the repo lacks a .github/workflows/ folder or other CI config.
  • Limited tests – only four test files are present, not covering the full data model.
  • No linting or static analysis – no phpcs.xml, composer.json, or similar.
  • Secrets – no obvious secrets committed; configuration files are placeholders.

These observations come solely from the file structure; deeper static analysis was not performed.

The Bottom Line

RiskOps delivers a focused risk‑management platform without the overhead of a full framework. It is ready to run in Docker with minimal configuration. The lack of automated testing and CI makes it suitable for internal use where rapid iteration is acceptable, but it would need additional quality gates before production deployment. The code is straightforward to extend; the routing is simple, and the database layer is isolated, making maintenance manageable for a mid‑size dev team.