Portfolio
CV
Apr 20, 2026 Case Study

RedOps: Java audit module framework via reflection

Extensible Java framework for running security audit modules in a standardized way, built on a @Module annotation, dynamic reflection-based loading, and a monitoring GUI.

JavaReflectionOffensive SecuritySoftware Architecture

The Goal

RedOps started as an ISEP project around offensive security, but what actually interested me was the architecture behind it: how to build a plugin system in Java without relying on a framework like Spring — just the JDK’s native reflection.

Concretely, the idea is that you write a new class, annotate it with @Module, and it gets discovered at compile time (not runtime) and executed automatically on startup. The modules shipped with the project (ping, basic port scan) are intentionally simple — the goal wasn’t to deliver a full pentest tool but to validate the extensibility pattern.

Architecture

The @Module annotation and reflection scan

Everything is built on a custom annotation:

@Module(name = "Simple Ping", order = 0)
public class Ping extends AbstractModule {
    @Override
    protected ExecutionStatus process(InetAddress address) throws InterruptedException {
        boolean reachable = address.isReachable(5000);
        return reachable ? ExecutionStatus.SUCCESS : ExecutionStatus.ERROR;
    }
}

At compile time, AnnotationConfigLoader scans the com.redops package (via the Reflections library) to find all classes carrying @Module, extracts their metadata (name, execution order), and builds the configuration. No XML or YAML to maintain: adding a module means writing a class and an annotation, and the framework handles discovery on its own.

The orchestration pipeline

ModuleManager is the entry point for orchestration and delegates to three single-responsibility components:

  • ModuleInitializer reads the config and calls ModuleInstantiator to instantiate each module via reflection, providing the dependencies its constructor needs (see below)
  • ModuleExecutor iterates over the ModuleInfo objects in order and calls run() on each module
  • Shutdown is handled separately to keep cleanup decoupled from execution
public class ModuleManager {
    public Stream<ModuleInfo> startModules() {
        List<ModuleInfo> modules = initializer.initializeModules();
        executor.executeModules(modules);
        return modules.stream();
    }
}

Very basic dependency injection

ModuleInstantiator inspects constructor parameters via reflection and, depending on their type, provides the matching instance — for example, the target IP address for the module. No DI container, no @Inject annotations: just an if/switch on the expected type and the right instance passed in by hand. Intentionally minimal — the framework resolves what a module needs before instantiating it, rather than letting the module go fetch it itself.

Module lifecycle

Each module extends AbstractModule, which wraps the status transitions around the process() call:

public abstract class AbstractModule {
    public ExecutionStatus status = ExecutionStatus.NOT_STARTED;

    public void run() throws UnknownHostException, InterruptedException {
        setStatus(ExecutionStatus.IN_PROGRESS);
        ExecutionStatus result = process(getTargetAddress());
        setStatus(result);
    }

    protected abstract ExecutionStatus process(InetAddress address)
        throws InterruptedException, IOException;
}

Statuses (SUCCESS, WARNING, ERROR, CRITICAL_ERROR…) are defined in a dedicated enum and pushed to the GUI in real time. A failing module doesn’t stop the others — ModuleExecutor logs the error and continues the sequence.

GUI

The UI (Swing + FlatLaf for a more modern look, JFreeChart for result graphs) runs on the Event Dispatch Thread, separate from the module execution thread. Synchronization between the two goes through SwingUtilities.invokeLater(), to avoid the classic concurrent access issues in Swing.

What I Learned

Understanding where reflection’s guarantees end: without a framework like Spring behind the scenes, I had to inspect constructor parameters by hand and decide what to provide based on their type. A larger mapping could have supported more dependencies, but wasn’t really useful here. The threading side (running modules off the EDT, pushing UI updates via invokeLater) was also a solid introduction to classic concurrency problems in a desktop app.

Disclaimer

RedOps is an educational project: the bundled modules are intentionally basic (ping, port scan), designed to demonstrate the extensibility pattern rather than for real pentest use. The code is MIT licensed and available on GitHub.