-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsecurity_auditor.cpp
More file actions
98 lines (83 loc) · 2.92 KB
/
Copy pathsecurity_auditor.cpp
File metadata and controls
98 lines (83 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "security_auditor.hpp"
#include <algorithm>
#include <cctype>
#include "config_manager.hpp"
namespace flapi {
namespace {
bool isBcryptPrefix(const std::string& password) {
if (password.size() < 4) {
return false;
}
if (password[0] != '$' || password[1] != '2' || password[3] != '$') {
return false;
}
const char variant = password[2];
return variant == 'a' || variant == 'b' || variant == 'y';
}
bool isMd5HexDigest(const std::string& password) {
if (password.size() != 32) {
return false;
}
return std::all_of(password.begin(), password.end(), [](char c) {
return std::isxdigit(static_cast<unsigned char>(c)) != 0;
});
}
void scanUsers(const std::vector<AuthUser>& users,
const std::string& location,
std::vector<SecurityWarning>& out) {
for (const auto& user : users) {
const std::string code = SecurityAuditor::classifyPassword(user.password);
if (code == "AUTH_PLAINTEXT_PASSWORD") {
out.push_back({
code,
"User '" + user.username + "' has a plaintext password. "
"Use bcrypt instead (see flapii auth hash, when available).",
location
});
} else if (code == "AUTH_MD5_PASSWORD") {
out.push_back({
code,
"User '" + user.username + "' has an MD5-hashed password. "
"MD5 is cryptographically broken; migrate to bcrypt.",
location
});
}
}
}
} // namespace
std::string SecurityAuditor::classifyPassword(const std::string& password) {
if (password.empty()) {
return {};
}
if (isBcryptPrefix(password)) {
return {};
}
if (isMd5HexDigest(password)) {
return "AUTH_MD5_PASSWORD";
}
return "AUTH_PLAINTEXT_PASSWORD";
}
std::vector<SecurityWarning> SecurityAuditor::audit(const ConfigManager& config) const {
std::vector<SecurityWarning> warnings;
for (const auto& endpoint : config.getEndpoints()) {
scanUsers(endpoint.auth.users, "endpoint " + endpoint.getIdentifier(), warnings);
}
const auto& mcp = config.getMCPConfig();
scanUsers(mcp.auth.users, "mcp.auth", warnings);
if (mcp.enabled && !mcp.auth.enabled) {
const bool any_mcp_tool = std::any_of(
config.getEndpoints().begin(), config.getEndpoints().end(),
[](const EndpointConfig& e) { return e.isMCPTool(); });
if (any_mcp_tool) {
warnings.push_back({
"MCP_UNAUTHENTICATED_TOOLS",
"MCP tools are exposed without authentication (mcp.auth.enabled is false). "
"Anyone reaching the server can invoke any MCP tool. "
"Enable mcp.auth.enabled and configure users or JWT before exposing this server.",
"mcp"
});
}
}
return warnings;
}
} // namespace flapi