fold/tests/unit/test_hardcoded_credential.cpp
trvon 86b2f14a43
feat(analysis): harden project-wide scans
- persist TU-owned graphs and guarded configuration evidence
- add closed-world production reachability classification
- calibrate detector precision and reviewed self-scan policy
- terminate poisoned workers instead of longjmp recovery
2026-07-30 18:18:04 -04:00

108 lines
4 KiB
C++

#include <clang/Tooling/Tooling.h>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <string>
#include "fold/detect/hardcoded_credential.hpp"
#include "fold/report/finding.hpp"
namespace {
std::unique_ptr<clang::ASTUnit> parse(const std::string& code) {
return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp");
}
} // namespace
TEST_CASE("hardcoded-credential: JWT literal is flagged", "[hardcoded-credential][jwt]") {
auto unit = parse(R"(
const char *token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].kind == fold::FindingKind::HardcodedCredential);
REQUIRE(findings[0].message.find("JWT") != std::string::npos);
}
TEST_CASE("hardcoded-credential: AWS access key id is flagged", "[hardcoded-credential][aws]") {
auto unit = parse(R"(
const char *access_key = "AKIA1234567890ABCDEF";
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].message.find("AWS") != std::string::npos);
}
TEST_CASE("hardcoded-credential: generic private key armour is flagged",
"[hardcoded-credential][pem]") {
auto unit = parse(R"(
const char *key = "-----BEGIN OPENSSH PRIVATE KEY-----\nabc\n-----END OPENSSH PRIVATE KEY-----";
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].message.find("private key") != std::string::npos);
}
TEST_CASE("hardcoded-credential: secret-looking literal assigned to api_key is flagged",
"[hardcoded-credential][heuristic]") {
auto unit = parse(R"(
const char *api_key = "AbCdEfGhIjKlMnOpQr9_123";
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].message.find("secret-looking") != std::string::npos);
}
TEST_CASE("hardcoded-credential: placeholder api key literal is not flagged",
"[hardcoded-credential][placeholder][negative]") {
auto unit = parse(R"(
const char *api_key = "your_api_key_here";
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.empty());
}
TEST_CASE("hardcoded-credential: matcher and regex vocabulary are not credentials",
"[hardcoded-credential][vocabulary]") {
auto unit = parse(R"(
struct Text { unsigned long rfind(const char*, unsigned long) const; };
bool recognizes(Text value) {
return value.rfind("sk_live_1234567890abcdef", 0) == 0;
}
namespace std { struct regex { explicit regex(const char*); }; }
std::regex private_key_pattern("-----BEGIN [A-Z ]*PRIVATE KEY-----");
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.empty());
}
TEST_CASE("hardcoded-credential: direct secret comparison remains reportable",
"[hardcoded-credential][comparison]") {
auto unit = parse(R"(
extern "C" int strcmp(const char*, const char*);
bool accepts(const char* provided) {
return strcmp(provided, "sk_live_1234567890abcdef") == 0;
}
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
}
TEST_CASE("hardcoded-credential: Stripe test key is flagged",
"[hardcoded-credential][stripe-test]") {
auto unit = parse(R"(
const char *secret = "sk_test_51NvTExampleSecretValue";
)");
REQUIRE(unit);
auto findings = fold::detectHardcodedCredentialInContext(unit->getASTContext());
REQUIRE(findings.size() == 1);
REQUIRE(findings[0].message.find("Stripe test key") != std::string::npos);
}