6#if !defined(SIGNET_ENABLE_COMMERCIAL) || !SIGNET_ENABLE_COMMERCIAL
7#error "signet/ai/threat_model.hpp requires SIGNET_ENABLE_COMMERCIAL=ON (AGPL-3.0 commercial tier). See LICENSE_COMMERCIAL."
106 auto ok = [](int32_t v) {
return v >= 1 && v <= 10; };
145 if (
static_cast<int32_t
>(m.status) <
static_cast<int32_t
>(worst))
191 (void)commercial::require_feature(
"ThreatModelAnalyzer");
196 bool covered[6] = {};
197 double dread_sum = 0.0;
198 int32_t valid_count = 0;
200 for (
const auto& t : model.
threats) {
202 if (!t.dread.valid())
continue;
205 int cat =
static_cast<int32_t
>(t.category);
206 if (cat >= 0 && cat < 6) covered[cat] =
true;
208 auto sev = t.dread.severity();
221 dread_sum += t.dread.composite();
229 for (
int i = 0; i < 6; ++i) {
248 m.
author =
"Signet Security Team";
252 "T-AUTH-001",
"Key impersonation via INTERNAL mode",
253 "An attacker with access to plaintext keys in INTERNAL mode can "
254 "impersonate any column encryption identity.",
257 "Access to unencrypted key material in file metadata",
260 "EXTERNAL key mode with KMS integration",
261 "crypto/key_metadata.hpp:IKmsClient",
264 "Production gate rejects INTERNAL mode (C-15)",
265 "crypto/pme.hpp:production_key_mode_gate()",
267 {
"NIST SP 800-57",
"PARQUET-1178"}
272 "T-TAMP-001",
"Hash chain manipulation in audit logs",
273 "An attacker modifies audit chain entries without detection.",
276 "Direct modification of Parquet audit log files",
277 "ai/audit_chain.hpp",
279 "SHA-256 cryptographic hash chain with prev_hash linkage",
280 "ai/audit_chain.hpp:AuditChainHasher",
282 {
"SEC 17a-4",
"NIST SP 800-92"}
287 "T-REP-001",
"Denial of AI decision actions",
288 "An operator denies having made or approved an AI trading decision.",
291 "Lack of non-repudiable logging for human overrides",
292 "ai/decision_log.hpp",
294 "Immutable decision log with operator_id and hash chain",
295 "ai/decision_log.hpp:DecisionLogWriter",
298 "Human override log with provenance (EU AI Act Art.14)",
299 "ai/human_oversight.hpp:HumanOverrideLogWriter",
301 {
"EU AI Act Art.14",
"MiFID II RTS 24"}
306 "T-DISC-001",
"Side-channel leakage from AES timing",
307 "An attacker observes timing variations in AES operations to "
308 "recover key material.",
311 "Timing analysis of AES encrypt/decrypt operations",
312 "crypto/aes_core.hpp",
314 "Constant-time AES via bitsliced S-box + AES-NI detection",
315 "crypto/aes_core.hpp:Aes256",
318 "Key material zeroing in destructors",
319 "crypto/aes_core.hpp:~Aes256()",
321 {
"NIST SP 800-38D",
"CWE-208"}
326 "T-DOS-001",
"Decompression bomb via crafted Parquet pages",
327 "A malicious Parquet file with extreme compression ratios causes "
328 "memory exhaustion during decompression.",
331 "Crafted Parquet file with oversized uncompressed pages",
334 "PARQUET_MAX_PAGE_SIZE (256 MB) limit on decompressed pages",
335 "reader.hpp:PARQUET_MAX_PAGE_SIZE",
338 "Thrift field count (65536) and string size (64 MB) limits",
339 "thrift/compact.hpp:MAX_FIELD_COUNT",
341 {
"CWE-409",
"OWASP Decompression Bomb"}
346 "T-PRIV-001",
"Path traversal in FeatureWriter output_dir",
347 "An attacker supplies a path with '..' segments to write outside "
348 "the intended directory.",
351 "Controlled output_dir parameter with path traversal sequences",
352 "ai/feature_writer.hpp",
354 "Path traversal guard rejects '..' segments",
355 "ai/feature_writer.hpp:create()",
357 {
"CWE-22",
"OWASP Path Traversal"}
397 static std::string escape_json(
const std::string& s) {
399 out.reserve(s.size() + 16);
402 case '"': out +=
"\\\"";
break;
403 case '\\': out +=
"\\\\";
break;
404 case '\n': out +=
"\\n";
break;
405 case '\r': out +=
"\\r";
break;
406 case '\t': out +=
"\\t";
break;
408 if (
static_cast<unsigned char>(c) < 0x20) {
410 std::snprintf(buf,
sizeof(buf),
"\\u%04x",
411 static_cast<unsigned char>(c));
422 static std::string generate_json(
const ThreatModel& model,
423 const ThreatModelAnalysis& analysis) {
424 std::ostringstream o;
426 o <<
" \"model_id\": \"" << escape_json(model.model_id) <<
"\",\n";
427 o <<
" \"component\": \"" << escape_json(model.component) <<
"\",\n";
428 o <<
" \"version\": \"" << escape_json(model.version) <<
"\",\n";
429 o <<
" \"methodology\": \"STRIDE/DREAD\",\n";
430 o <<
" \"summary\": {\n";
431 o <<
" \"total_threats\": " << analysis.total_threats <<
",\n";
432 o <<
" \"critical\": " << analysis.critical_count <<
",\n";
433 o <<
" \"high\": " << analysis.high_count <<
",\n";
434 o <<
" \"medium\": " << analysis.medium_count <<
",\n";
435 o <<
" \"low\": " << analysis.low_count <<
",\n";
436 o <<
" \"mitigated\": " << analysis.mitigated_count <<
",\n";
437 o <<
" \"unmitigated\": " << analysis.unmitigated_count <<
",\n";
438 o <<
" \"stride_complete\": " << (analysis.stride_complete ?
"true" :
"false") <<
",\n";
439 o <<
" \"mean_dread_score\": " << analysis.mean_dread_score <<
"\n";
441 o <<
" \"threats\": [\n";
443 for (
size_t i = 0; i < model.threats.size(); ++i) {
444 const auto& t = model.threats[i];
446 o <<
" \"id\": \"" << escape_json(t.threat_id) <<
"\",\n";
447 o <<
" \"title\": \"" << escape_json(t.title) <<
"\",\n";
448 o <<
" \"stride\": \"" << stride_name(t.category) <<
"\",\n";
449 o <<
" \"severity\": \"" << severity_name(t.dread.severity()) <<
"\",\n";
450 o <<
" \"dread\": {\n";
451 o <<
" \"damage\": " << t.dread.damage <<
",\n";
452 o <<
" \"reproducibility\": " << t.dread.reproducibility <<
",\n";
453 o <<
" \"exploitability\": " << t.dread.exploitability <<
",\n";
454 o <<
" \"affected_users\": " << t.dread.affected_users <<
",\n";
455 o <<
" \"discoverability\": " << t.dread.discoverability <<
",\n";
456 o <<
" \"composite\": " << t.dread.composite() <<
"\n";
458 o <<
" \"status\": \"" << mitigation_status_name(t.overall_status()) <<
"\",\n";
459 o <<
" \"mitigations\": [";
460 for (
size_t j = 0; j < t.mitigations.size(); ++j) {
461 const auto& m = t.mitigations[j];
462 o <<
"\n {\"id\": \"" << escape_json(m.control_id)
463 <<
"\", \"status\": \"" << mitigation_status_name(m.status) <<
"\"}";
464 if (j + 1 < t.mitigations.size()) o <<
",";
466 o << (t.mitigations.empty() ?
"" :
"\n ") <<
"]\n";
468 if (i + 1 < model.threats.size()) o <<
",";
Validates threat model coverage and produces audit-ready JSON.
static ThreatModelAnalysis analyze(const ThreatModel &model)
Analyze a threat model for completeness and risk posture.
static ThreatModel signet_default_model()
Build the Signet Forge default threat model with known threats.
ThreatSeverity
Threat severity classification per NIST SP 800-30.
@ LOW
DREAD composite < 4.0.
@ CRITICAL
DREAD composite >= 9.0.
@ HIGH
DREAD composite 7.0 - 8.9.
@ MEDIUM
DREAD composite 4.0 - 6.9.
@ LOW
Minor documentation update.
@ CRITICAL
Immediate action required (compliance deadline)
@ HIGH
Significant architectural changes.
@ MEDIUM
Code/configuration changes required.
StrideCategory
Microsoft STRIDE threat categories.
@ SPOOFING
Authentication bypass, identity impersonation.
@ ELEVATION_OF_PRIVILEGE
Gaining unauthorized access levels.
@ REPUDIATION
Denying actions without proof.
@ TAMPERING
Unauthorized data modification.
@ DENIAL_OF_SERVICE
Resource exhaustion, availability attacks.
@ INFORMATION_DISCLOSURE
Unauthorized data exposure.
MitigationStatus
Mitigation status for a threat.
@ MITIGATED
Fully mitigated by implemented controls.
@ TRANSFERRED
Risk transferred (insurance, third-party)
@ NOT_MITIGATED
No mitigation in place.
@ ACCEPTED
Risk accepted per organizational policy.
@ PARTIAL
Some controls, residual risk remains.
DREAD risk quantification — 5 factors scored 1..10.
bool valid() const
Validate all factors are in range [1, 10].
double composite() const
Composite DREAD score (arithmetic mean, 1.0 .. 10.0).
int32_t affected_users
Fraction of users affected (1-10)
ThreatSeverity severity() const
Derive severity from composite score.
int32_t discoverability
Ease of discovering the vulnerability (1-10)
int32_t exploitability
Effort required to exploit (1-10)
int32_t damage
Potential damage if exploited (1-10)
int32_t reproducibility
Ease of reproducing the attack (1-10)
A specific mitigation control for a threat.
std::string description
What the control does.
std::string control_id
Unique identifier (e.g., "CTRL-AES-001")
std::string implementation
Where in codebase (file:line or module)
A single identified threat in the threat model.
std::string affected_component
Module or subsystem at risk.
std::vector< std::string > references
CVEs, NIST refs, etc.
std::string title
Short description.
MitigationStatus overall_status() const
Overall mitigation status — worst (lowest) across all mitigations.
std::string attack_vector
How the attack is carried out.
std::string threat_id
Unique identifier (e.g., "T-CRYPT-001")
std::string description
Detailed threat narrative.
DreadScore dread
Risk quantification.
std::vector< Mitigation > mitigations
Analysis result from validating a threat model.
std::string report_json
Full JSON report.
std::vector< StrideCategory > missing_categories
bool stride_complete
All 6 STRIDE categories covered.
int32_t unmitigated_count
A threat model for a specific component or the entire system.
std::string author
Who created/reviewed the model.
std::vector< ThreatEntry > threats
std::string model_id
Unique identifier for this threat model.
std::string version
Version of the threat model.
std::string reviewed_at
ISO 8601 last review timestamp.
std::string component
Component being modeled (e.g., "crypto", "pme")
std::string created_at
ISO 8601 creation timestamp.