Complete Step 490: threat model integration
This commit is contained in:
@@ -3289,4 +3289,13 @@ target_link_libraries(step489_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step490_test tests/step490_test.cpp)
|
||||
target_include_directories(step490_test PRIVATE src)
|
||||
target_link_libraries(step490_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
126
editor/src/ThreatModelIntegration.h
Normal file
126
editor/src/ThreatModelIntegration.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
// Step 490: Threat Model Integration
|
||||
// Models trust boundaries, data flows, and validation diagnostics.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct TrustBoundary {
|
||||
std::string name; // external-api, database, user-input
|
||||
std::string level; // high, medium, low
|
||||
};
|
||||
|
||||
struct ThreatDataFlow {
|
||||
std::string fromBoundary;
|
||||
std::string toBoundary;
|
||||
bool hasInputValidation = false;
|
||||
std::string validationKind; // sanitized/raw/escaped
|
||||
};
|
||||
|
||||
struct ThreatModelInput {
|
||||
std::string moduleName;
|
||||
std::vector<TrustBoundary> boundaries;
|
||||
std::vector<ThreatDataFlow> flows;
|
||||
};
|
||||
|
||||
struct ThreatDiagnostic {
|
||||
int code = 0; // E1300 range
|
||||
std::string message;
|
||||
std::string severity;
|
||||
std::string boundaryFrom;
|
||||
std::string boundaryTo;
|
||||
};
|
||||
|
||||
struct ThreatModelOutput {
|
||||
std::vector<ThreatDiagnostic> diagnostics;
|
||||
std::vector<std::string> requiredAnnotations;
|
||||
std::vector<std::string> overlayNodes; // for GUI trust overlay
|
||||
std::vector<std::string> notes;
|
||||
};
|
||||
|
||||
class ThreatModelIntegration {
|
||||
public:
|
||||
static ThreatModelOutput analyze(const ThreatModelInput& in) {
|
||||
ThreatModelOutput out;
|
||||
out.requiredAnnotations.push_back("@ThreatModel(module=" + in.moduleName + ")");
|
||||
|
||||
for (const auto& b : in.boundaries) {
|
||||
out.overlayNodes.push_back(b.name + ":" + b.level);
|
||||
out.requiredAnnotations.push_back("@TrustBoundary(" + b.name + "," + b.level + ")");
|
||||
}
|
||||
|
||||
for (const auto& f : in.flows) {
|
||||
analyzeFlow(f, out);
|
||||
}
|
||||
|
||||
if (in.boundaries.empty()) {
|
||||
out.diagnostics.push_back({1302, "No trust boundaries defined for module",
|
||||
"medium", "", ""});
|
||||
}
|
||||
if (in.flows.empty()) {
|
||||
out.notes.push_back("No data flows provided for threat analysis");
|
||||
}
|
||||
out.notes.push_back("Trust boundary overlay data prepared");
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string lower(const std::string& s) {
|
||||
std::string out = s;
|
||||
std::transform(out.begin(), out.end(), out.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool isExternalBoundary(const std::string& name) {
|
||||
const std::string n = lower(name);
|
||||
return n.find("external") != std::string::npos || n.find("user") != std::string::npos;
|
||||
}
|
||||
|
||||
static void analyzeFlow(const ThreatDataFlow& flow, ThreatModelOutput& out) {
|
||||
const bool crossing = flow.fromBoundary != flow.toBoundary;
|
||||
if (crossing) {
|
||||
out.requiredAnnotations.push_back(
|
||||
"@DataFlow(" + flow.fromBoundary + "->" + flow.toBoundary + ")");
|
||||
}
|
||||
|
||||
const bool highRiskCrossing = crossing &&
|
||||
(isExternalBoundary(flow.fromBoundary) || isExternalBoundary(flow.toBoundary));
|
||||
|
||||
if (highRiskCrossing && !flow.hasInputValidation) {
|
||||
out.diagnostics.push_back({
|
||||
1300,
|
||||
"Missing input validation at trust boundary crossing",
|
||||
"high",
|
||||
flow.fromBoundary,
|
||||
flow.toBoundary
|
||||
});
|
||||
out.requiredAnnotations.push_back("@InputValidation(required)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (flow.hasInputValidation) {
|
||||
const std::string kind = lower(flow.validationKind);
|
||||
if (kind.empty() || kind == "raw") {
|
||||
out.diagnostics.push_back({
|
||||
1301,
|
||||
"Boundary flow validation exists but remains raw",
|
||||
"medium",
|
||||
flow.fromBoundary,
|
||||
flow.toBoundary
|
||||
});
|
||||
} else if (kind != "sanitized" && kind != "escaped") {
|
||||
out.diagnostics.push_back({
|
||||
1303,
|
||||
"Unknown validation kind at trust boundary flow",
|
||||
"low",
|
||||
flow.fromBoundary,
|
||||
flow.toBoundary
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
167
editor/tests/step490_test.cpp
Normal file
167
editor/tests/step490_test.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
// Step 490: Threat Model Integration Tests (12 tests)
|
||||
|
||||
#include "ThreatModelIntegration.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
static ThreatModelInput baseInput() {
|
||||
ThreatModelInput in;
|
||||
in.moduleName = "api";
|
||||
in.boundaries = {{"external-api", "high"}, {"database", "medium"}, {"internal", "low"}};
|
||||
return in;
|
||||
}
|
||||
|
||||
void test_threat_model_annotation_emitted_at_module_level() {
|
||||
TEST(threat_model_annotation_emitted_at_module_level);
|
||||
auto out = ThreatModelIntegration::analyze(baseInput());
|
||||
bool found = false;
|
||||
for (const auto& a : out.requiredAnnotations) {
|
||||
if (a.find("@ThreatModel(module=api)") != std::string::npos) found = true;
|
||||
}
|
||||
CHECK(found, "missing module threat model annotation");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_trust_boundaries_are_included_in_overlay_data() {
|
||||
TEST(trust_boundaries_are_included_in_overlay_data);
|
||||
auto out = ThreatModelIntegration::analyze(baseInput());
|
||||
CHECK(out.overlayNodes.size() == 3, "overlay node count mismatch");
|
||||
CHECK(out.overlayNodes[0].find("external-api:high") != std::string::npos, "missing boundary node");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_cross_boundary_flow_without_validation_emits_e1300() {
|
||||
TEST(cross_boundary_flow_without_validation_emits_e1300);
|
||||
auto in = baseInput();
|
||||
in.flows = {{"external-api", "database", false, ""}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
CHECK(!out.diagnostics.empty(), "expected diagnostics");
|
||||
CHECK(out.diagnostics[0].code == 1300, "expected E1300 diagnostic");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_boundary_flow_with_raw_validation_emits_e1301() {
|
||||
TEST(boundary_flow_with_raw_validation_emits_e1301);
|
||||
auto in = baseInput();
|
||||
in.flows = {{"external-api", "database", true, "raw"}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
CHECK(!out.diagnostics.empty(), "expected diagnostics");
|
||||
CHECK(out.diagnostics[0].code == 1301, "expected E1301 diagnostic");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_sanitized_boundary_flow_avoids_validation_diagnostic() {
|
||||
TEST(sanitized_boundary_flow_avoids_validation_diagnostic);
|
||||
auto in = baseInput();
|
||||
in.flows = {{"external-api", "database", true, "sanitized"}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
CHECK(out.diagnostics.empty(), "did not expect diagnostics for sanitized flow");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_unknown_validation_kind_emits_e1303() {
|
||||
TEST(unknown_validation_kind_emits_e1303);
|
||||
auto in = baseInput();
|
||||
in.flows = {{"external-api", "database", true, "custom"}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
CHECK(!out.diagnostics.empty(), "expected diagnostics");
|
||||
CHECK(out.diagnostics[0].code == 1303, "expected E1303 diagnostic");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_no_boundaries_emits_e1302() {
|
||||
TEST(no_boundaries_emits_e1302);
|
||||
ThreatModelInput in;
|
||||
in.moduleName = "api";
|
||||
in.flows = {{"external-api", "database", false, ""}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
bool found = false;
|
||||
for (const auto& d : out.diagnostics) if (d.code == 1302) found = true;
|
||||
CHECK(found, "expected E1302 for missing boundaries");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_data_flow_annotation_is_emitted_for_crossing() {
|
||||
TEST(data_flow_annotation_is_emitted_for_crossing);
|
||||
auto in = baseInput();
|
||||
in.flows = {{"external-api", "database", true, "sanitized"}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
bool found = false;
|
||||
for (const auto& a : out.requiredAnnotations) {
|
||||
if (a.find("@DataFlow(external-api->database)") != std::string::npos) found = true;
|
||||
}
|
||||
CHECK(found, "expected @DataFlow annotation");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_missing_validation_adds_input_validation_required_annotation() {
|
||||
TEST(missing_validation_adds_input_validation_required_annotation);
|
||||
auto in = baseInput();
|
||||
in.flows = {{"user-input", "internal", false, ""}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
bool found = false;
|
||||
for (const auto& a : out.requiredAnnotations) {
|
||||
if (a == "@InputValidation(required)") found = true;
|
||||
}
|
||||
CHECK(found, "expected @InputValidation(required)");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_internal_same_boundary_flow_does_not_trigger_e1300() {
|
||||
TEST(internal_same_boundary_flow_does_not_trigger_e1300);
|
||||
auto in = baseInput();
|
||||
in.flows = {{"internal", "internal", false, ""}};
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
for (const auto& d : out.diagnostics) {
|
||||
CHECK(d.code != 1300, "E1300 should not be emitted for non-crossing flow");
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_overlay_note_is_present() {
|
||||
TEST(overlay_note_is_present);
|
||||
auto out = ThreatModelIntegration::analyze(baseInput());
|
||||
CHECK(!out.notes.empty(), "expected notes");
|
||||
CHECK(out.notes.back().find("overlay") != std::string::npos, "missing overlay note");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_empty_flows_add_note_but_not_false_positive_diagnostic() {
|
||||
TEST(empty_flows_add_note_but_not_false_positive_diagnostic);
|
||||
auto in = baseInput();
|
||||
auto out = ThreatModelIntegration::analyze(in);
|
||||
bool note = false;
|
||||
for (const auto& n : out.notes) {
|
||||
if (n.find("No data flows") != std::string::npos) note = true;
|
||||
}
|
||||
CHECK(note, "expected no-data-flow note");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 490: Threat Model Integration Tests\n";
|
||||
|
||||
test_threat_model_annotation_emitted_at_module_level(); // 1
|
||||
test_trust_boundaries_are_included_in_overlay_data(); // 2
|
||||
test_cross_boundary_flow_without_validation_emits_e1300(); // 3
|
||||
test_boundary_flow_with_raw_validation_emits_e1301(); // 4
|
||||
test_sanitized_boundary_flow_avoids_validation_diagnostic(); // 5
|
||||
test_unknown_validation_kind_emits_e1303(); // 6
|
||||
test_no_boundaries_emits_e1302(); // 7
|
||||
test_data_flow_annotation_is_emitted_for_crossing(); // 8
|
||||
test_missing_validation_adds_input_validation_required_annotation(); // 9
|
||||
test_internal_same_boundary_flow_does_not_trigger_e1300(); // 10
|
||||
test_overlay_note_is_present(); // 11
|
||||
test_empty_flows_add_note_but_not_false_positive_diagnostic(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||
<< " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
39
progress.md
39
progress.md
@@ -7223,3 +7223,42 @@ post-hoc hardening.
|
||||
- `editor/src/SecureByDefaultGenerator.h` within header-size limit (`202` <= `600`)
|
||||
- `editor/tests/step489_test.cpp` within test-file size guidance (`164` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 490: Threat Model Integration
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Adds threat-model integration primitives that encode trust boundaries and
|
||||
boundary-crossing data-flow validation requirements, emitting structured
|
||||
E1300-range diagnostics and overlay-ready graph data.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/ThreatModelIntegration.h` — threat model analyzer:
|
||||
- `TrustBoundary`, `ThreatDataFlow`, `ThreatModelInput`
|
||||
- `ThreatDiagnostic`, `ThreatModelOutput`
|
||||
- `analyze(...)` capabilities:
|
||||
- module-level `@ThreatModel(...)` annotation requirement
|
||||
- boundary-level `@TrustBoundary(...)` requirements
|
||||
- cross-boundary `@DataFlow(...)` requirement emission
|
||||
- missing-validation detection at high-risk crossings (`E1300`)
|
||||
- raw-validation detection (`E1301`)
|
||||
- missing-boundary model diagnostic (`E1302`)
|
||||
- unknown validation kind diagnostic (`E1303`)
|
||||
- overlay node output for trust-boundary visualization
|
||||
- `editor/tests/step490_test.cpp` — 12 tests covering:
|
||||
- annotation emission for module/boundary/flow modeling
|
||||
- E1300/E1301/E1302/E1303 diagnostic behavior
|
||||
- sanitized-flow non-error path
|
||||
- validation-required annotation on missing checks
|
||||
- non-crossing internal-flow false-positive guard
|
||||
- overlay and empty-flow note behavior
|
||||
- `editor/CMakeLists.txt` — `step490_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake --build editor/build-native --target step490_test step489_test` — PASS
|
||||
- `./editor/build-native/step490_test` — PASS (12/12)
|
||||
- `./editor/build-native/step489_test` — PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/ThreatModelIntegration.h` within header-size limit (`126` <= `600`)
|
||||
- `editor/tests/step490_test.cpp` within test-file size guidance (`167` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user