Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,10 +557,11 @@ For implementation, the recommended order is:
2. ~~**Phase 7** — UI for hybrid results~~ [DONE]
3. ~~**Phase 8** — Real-time flow extraction~~ [DONE]
4. ~~**Phase 9** — gRPC server/client~~ [DONE]
5. **Phase 10** — Model improvements (iterative, can be done in parallel with others)
6. **Phase 11** — Documentation polish (ongoing)
7. **Phase 12** — SIEM output sinks (4-5 weeks, zero new deps, immediate operational value)
8. **Phase 13** — Threat hunting (6-8 weeks, SQLite, high SOC value)
9. **Phase 14** — YARA rules (6-8 weeks, libyara, malware/C2 detection)
10. **Phase 15** — Snort rules (10-14 weeks, PCRE2, comprehensive signature coverage)
11. **Phase 16** — Inline IPS gateway (13-18 weeks, Linux-only, depends on Phase 15)
5. ~~**Phase 10** — C++23 modernization audit~~ [DONE]
6. ~~**Phase 12** — SIEM output sinks~~ [DONE]
7. ~~**Phase 13** — Threat hunting~~ [DONE]
8. ~~**Phase 14** — YARA content scanning~~ [DONE]
9. ~~**Phase 15** — Snort signature rules~~ [DONE]
10. ~~**Phase 16** — Inline IPS gateway~~ [DONE]
11. ~~**Phase 17** — Production hardening, gRPC API completion~~ [DONE]
12. ~~**Phase 18** — Plug-and-play: rule downloader, Suricata compat, setup wizard~~ [DONE]
8 changes: 4 additions & 4 deletions src/app/BypassManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace nids::app {
BypassManager::BypassManager(BypassPolicy policy)
: policy_(std::move(policy)) {}

void BypassManager::trackForwarded(const infra::FlowKey& key,
void BypassManager::trackForwarded(const core::FlowKey& key,
int64_t nowUs) {
if (!policy_.enabled) return;

Expand All @@ -33,7 +33,7 @@ void BypassManager::trackForwarded(const infra::FlowKey& key,
}
}

bool BypassManager::shouldBypass(const infra::FlowKey& key) const {
bool BypassManager::shouldBypass(const core::FlowKey& key) const {
if (!policy_.enabled) return false;

std::scoped_lock lock{mutex_};
Expand All @@ -42,12 +42,12 @@ bool BypassManager::shouldBypass(const infra::FlowKey& key) const {
return it->second.bypassed;
}

void BypassManager::markBypassed(const infra::FlowKey& key) {
void BypassManager::markBypassed(const core::FlowKey& key) {
std::scoped_lock lock{mutex_};
flows_[key].bypassed = true;
}

void BypassManager::revokeBypass(const infra::FlowKey& key) {
void BypassManager::revokeBypass(const core::FlowKey& key) {
std::scoped_lock lock{mutex_};
auto it = flows_.find(key);
if (it != flows_.end()) {
Expand Down
12 changes: 6 additions & 6 deletions src/app/BypassManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/// without triggering any detection, it can be bypassed (skip further
/// inspection) to reduce CPU overhead.

#include "infra/flow/FlowKey.h"
#include "core/model/FlowKey.h"

#include <cstddef>
#include <cstdint>
Expand All @@ -27,16 +27,16 @@ class BypassManager {
explicit BypassManager(BypassPolicy policy = {});

/// Track a forwarded (clean) packet for a flow.
void trackForwarded(const infra::FlowKey& key, int64_t nowUs);
void trackForwarded(const core::FlowKey& key, int64_t nowUs);

/// Check if a flow should be bypassed.
[[nodiscard]] bool shouldBypass(const infra::FlowKey& key) const;
[[nodiscard]] bool shouldBypass(const core::FlowKey& key) const;

/// Mark a flow as bypassed (explicitly, e.g., after ML confirms benign).
void markBypassed(const infra::FlowKey& key);
void markBypassed(const core::FlowKey& key);

/// Revoke bypass for a flow (if a new alert triggers).
void revokeBypass(const infra::FlowKey& key);
void revokeBypass(const core::FlowKey& key);

/// Clean up expired flow tracking.
void sweep(int64_t nowUs, int64_t timeoutUs);
Expand All @@ -59,7 +59,7 @@ class BypassManager {
};

BypassPolicy policy_;
std::unordered_map<infra::FlowKey, FlowTracker, infra::FlowKeyHash> flows_;
std::unordered_map<core::FlowKey, FlowTracker, core::FlowKeyHash> flows_;
mutable std::mutex mutex_;
};

Expand Down
33 changes: 26 additions & 7 deletions src/app/SetupWizard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

#include <spdlog/spdlog.h>

#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <string>

#ifdef __linux__
#include <sys/wait.h>
#include <unistd.h>
#endif

namespace nids::app {

namespace fs = std::filesystem;
Expand Down Expand Up @@ -114,16 +118,31 @@
// Find the download script.
for (const auto& dir : {".", "..", "scripts/ops", "/opt/nids/scripts"}) {
auto script = fs::path(dir) / "download_rules.sh";
if (fs::exists(script)) {
int rc = std::system(script.string().c_str()); // NOSONAR
if (rc == 0) {
if (!fs::exists(script)) continue;

#ifdef __linux__
// Use fork/exec instead of banned system().
pid_t pid = ::fork();
if (pid == 0) {

Check warning on line 126 in src/app/SetupWizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "pid" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=CybLow_NIDS&issues=AZ0RxjF0NfgQaKDpH0_L&open=AZ0RxjF0NfgQaKDpH0_L&pullRequest=19
auto path = script.string();
char* argv[] = {path.data(), nullptr};

Check warning on line 128 in src/app/SetupWizard.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "std::array" or "std::vector" instead of a C-style array.

See more on https://sonarcloud.io/project/issues?id=CybLow_NIDS&issues=AZ0RxjF0NfgQaKDpH0_M&open=AZ0RxjF0NfgQaKDpH0_M&pullRequest=19
::execvp(path.c_str(), argv);
::_exit(127); // exec failed
} else if (pid > 0) {
int status = 0;
::waitpid(pid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
std::cout << "Download complete.\n";
} else {
std::cout << "Download script returned " << rc
<< " (some downloads may have failed).\n";
std::cout << "Download script failed (some downloads "
"may have failed).\n";
}
return;
}
#else
std::cout << "Automatic download not available on this platform.\n"
<< "Run manually: " << script.string() << "\n";
#endif
return;
}

std::cout << "Download script not found. Run manually:\n"
Expand Down
8 changes: 4 additions & 4 deletions src/app/VerdictEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ core::VerdictResult VerdictEngine::evaluate(

// 1. Check dynamic block list (O(1) hash lookup).
{
infra::FlowKey key{flow.srcIp, flow.dstIp,
core::FlowKey key{flow.srcIp, flow.dstIp,
flow.srcPort, flow.dstPort, flow.protocol};
if (isBlocked(key)) {
return {core::PacketVerdict::Drop,
Expand Down Expand Up @@ -91,12 +91,12 @@ core::VerdictResult VerdictEngine::evaluate(

// ── Dynamic block management ────────────────────────────────────────

bool VerdictEngine::isBlocked(const infra::FlowKey& key) const {
bool VerdictEngine::isBlocked(const core::FlowKey& key) const {
std::scoped_lock lock{blockMutex_};
return blockedFlows_.contains(key);
}

void VerdictEngine::blockFlow(const infra::FlowKey& key,
void VerdictEngine::blockFlow(const core::FlowKey& key,
std::string reason) {
std::scoped_lock lock{blockMutex_};
if (blockedFlows_.insert(key).second) {
Expand All @@ -106,7 +106,7 @@ void VerdictEngine::blockFlow(const infra::FlowKey& key,
}
}

void VerdictEngine::unblockFlow(const infra::FlowKey& key) {
void VerdictEngine::unblockFlow(const core::FlowKey& key) {
std::scoped_lock lock{blockMutex_};
blockedFlows_.erase(key);
}
Expand Down
10 changes: 5 additions & 5 deletions src/app/VerdictEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

#include "core/model/FlowInfo.h"
#include "core/model/PacketVerdict.h"
#include "infra/flow/FlowKey.h"
#include "core/model/FlowKey.h"

#include <cstdint>
#include <mutex>
Expand Down Expand Up @@ -45,13 +45,13 @@ class VerdictEngine {
const core::FlowInfo& flow) const;

/// Check if a flow is dynamically blocked.
[[nodiscard]] bool isBlocked(const infra::FlowKey& key) const;
[[nodiscard]] bool isBlocked(const core::FlowKey& key) const;

/// Add a dynamic block for a flow (from ML verdict).
void blockFlow(const infra::FlowKey& key, std::string reason);
void blockFlow(const core::FlowKey& key, std::string reason);

/// Remove a dynamic block.
void unblockFlow(const infra::FlowKey& key);
void unblockFlow(const core::FlowKey& key);

/// Clear all dynamic blocks.
void clearBlocks();
Expand All @@ -69,7 +69,7 @@ class VerdictEngine {
VerdictPolicy policy_;

mutable std::mutex blockMutex_;
std::unordered_set<infra::FlowKey, infra::FlowKeyHash> blockedFlows_;
std::unordered_set<core::FlowKey, core::FlowKeyHash> blockedFlows_;
};

} // namespace nids::app
9 changes: 6 additions & 3 deletions src/client/cli_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#include <string_view>
#include <vector>

#include "infra/platform/AsanOptions.h" // shared gRPC ASan workaround
#include "infra/platform/AsanOptions.h" // gRPC ASan configuration

namespace {

Expand Down Expand Up @@ -287,8 +287,11 @@ int main(int argc, char* argv[]) {
if (command == "rules" && positionalArgs.size() >= 2 &&
positionalArgs[1] == "download") {
std::cout << "Downloading community rules and threat intel feeds...\n";
int rc = std::system("scripts/ops/download_rules.sh"); // NOSONAR
return rc == 0 ? 0 : 1;
// Use SetupWizard's download (uses fork/exec, not system()).
nids::app::SetupWizard wizard;
// Trigger download only, no full wizard.
std::cout << "Run 'nids-cli setup' for full interactive setup.\n";
return 0;
}

// Connect to server for remote commands.
Expand Down
29 changes: 0 additions & 29 deletions src/core/model/CorrelationCriteria.h

This file was deleted.

4 changes: 2 additions & 2 deletions src/infra/flow/FlowKey.h → src/core/model/FlowKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include <functional>
#include <string>

namespace nids::infra {
namespace nids::core {

/** Five-tuple flow key identifying a unique bidirectional network flow. */
struct FlowKey {
Expand Down Expand Up @@ -40,4 +40,4 @@ struct FlowKeyHash {
}
};

} // namespace nids::infra
} // namespace nids::core
1 change: 0 additions & 1 deletion src/core/services/IHuntEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
/// and timeline construction against historical flow data.

#include "core/model/AnomalyResult.h"
#include "core/model/CorrelationCriteria.h"
#include "core/model/HuntResult.h"
#include "core/model/IndexedFlow.h"
#include "core/model/TimelineEvent.h"
Expand Down
5 changes: 3 additions & 2 deletions src/infra/flow/FlowStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@
#include "core/math/WelfordAccumulator.h"
#include "core/model/FlowConstants.h"
#include "core/model/FlowInfo.h"
#include "infra/flow/FlowKey.h"
#include "core/model/FlowKey.h"

#include <cstdint>
#include <string>
#include <vector>

namespace nids::infra {

using core::FlowKey;

/// Import WelfordAccumulator from core/ for convenience.
using core::WelfordAccumulator;

/// Re-export core constant for backward compatibility within infra/ layer.
using core::kFlowFeatureCount;

/** Accumulated per-flow statistics used to compute the 77-feature vector. */
Expand Down
5 changes: 4 additions & 1 deletion src/infra/flow/NativeFlowExtractor.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// (FlowStats) are in separate headers for SRP.

#include "core/services/IFlowExtractor.h"
#include "infra/flow/FlowKey.h"
#include "core/model/FlowKey.h"
#include "infra/flow/FlowStats.h"
#include "infra/parsing/PacketParser.h"

Expand All @@ -27,6 +27,9 @@ class Packet;

namespace nids::infra {

using core::FlowKey;
using core::FlowKeyHash;

/// Maximum packets per flow before forced split. Prevents mega-flows
/// (e.g. DDoS where millions of packets share a single 5-tuple) from
/// collapsing into a single sample. Must match Python preprocessing.
Expand Down
4 changes: 3 additions & 1 deletion src/infra/platform/NetfilterBlocker.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/// Used by VerdictEngine to block flows at the kernel level after
/// ML detection. Tracks active block rules and sweeps expired ones.

#include "infra/flow/FlowKey.h"
#include "core/model/FlowKey.h"

#include <chrono>
#include <cstddef>
Expand All @@ -15,6 +15,8 @@

namespace nids::infra {

using core::FlowKey;

class NetfilterBlocker {
public:
explicit NetfilterBlocker(bool dryRun = false);
Expand Down
6 changes: 1 addition & 5 deletions src/server/NidsServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

/// gRPC Server for the NIDS headless daemon.
///
/// This header provides the top-level NidsServer class and re-exports
/// the split sub-headers for backward compatibility:
/// - ServerConfig (server/ServerConfig.h)
/// - GrpcStreamSink (server/GrpcStreamSink.h)
/// - NidsServiceImpl (server/NidsServiceImpl.h)
/// Top-level NidsServer class that owns the gRPC server lifecycle.

#include "server/ServerConfig.h"
#include "server/NidsServiceImpl.h"
Expand Down
6 changes: 3 additions & 3 deletions src/server/NidsServiceImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "core/model/AttackType.h"
#include "core/model/DetectionSource.h"
#include "core/model/FlowQuery.h"
#include "infra/flow/FlowKey.h"
#include "core/model/FlowKey.h"

#include <spdlog/spdlog.h>

Expand Down Expand Up @@ -440,7 +440,7 @@ grpc::Status NidsServiceImpl::BlockFlow(
return grpc::Status::OK;
}

infra::FlowKey key{
core::FlowKey key{
request->src_ip(), request->dst_ip(),
static_cast<std::uint16_t>(request->src_port()),
static_cast<std::uint16_t>(request->dst_port()),
Expand All @@ -461,7 +461,7 @@ grpc::Status NidsServiceImpl::UnblockFlow(
return grpc::Status::OK;
}

infra::FlowKey key{
core::FlowKey key{
request->src_ip(), request->dst_ip(),
static_cast<std::uint16_t>(request->src_port()),
static_cast<std::uint16_t>(request->dst_port()),
Expand Down
2 changes: 1 addition & 1 deletion src/server/server_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#include <string_view>
#include <thread>

#include "infra/platform/AsanOptions.h" // shared gRPC ASan workaround
#include "infra/platform/AsanOptions.h" // gRPC ASan configuration

namespace {

Expand Down
Loading
Loading