210 lines
5.6 KiB
Nim
210 lines
5.6 KiB
Nim
# ©AngelaMos | 2026
|
|
# test_all.nim
|
|
|
|
import std/[unittest, strutils]
|
|
import types
|
|
import collectors/base
|
|
import collectors/ssh
|
|
import collectors/history
|
|
import harvester
|
|
|
|
suite "redactValue":
|
|
test "short value fully redacted":
|
|
check redactValue("abc", 4) == "***"
|
|
|
|
test "value longer than showChars":
|
|
check redactValue("mysecret", 4) == "myse****"
|
|
|
|
test "exact showChars length":
|
|
check redactValue("abcd", 4) == "****"
|
|
|
|
test "empty string":
|
|
check redactValue("", 4) == ""
|
|
|
|
suite "isPrivateKey":
|
|
test "OpenSSH key":
|
|
check isPrivateKey("-----BEGIN OPENSSH PRIVATE KEY-----\ndata")
|
|
|
|
test "RSA key":
|
|
check isPrivateKey("-----BEGIN RSA PRIVATE KEY-----\ndata")
|
|
|
|
test "ECDSA key":
|
|
check isPrivateKey("-----BEGIN EC PRIVATE KEY-----\ndata")
|
|
|
|
test "DSA key":
|
|
check isPrivateKey("-----BEGIN DSA PRIVATE KEY-----\ndata")
|
|
|
|
test "generic PKCS8 key":
|
|
check isPrivateKey("-----BEGIN PRIVATE KEY-----\ndata")
|
|
|
|
test "public key rejected":
|
|
check isPrivateKey("-----BEGIN PUBLIC KEY-----\ndata") == false
|
|
|
|
test "random text rejected":
|
|
check isPrivateKey("this is not a key") == false
|
|
|
|
test "empty string rejected":
|
|
check isPrivateKey("") == false
|
|
|
|
suite "isEncrypted":
|
|
test "ENCRYPTED marker":
|
|
check isEncrypted(
|
|
"-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\ndata"
|
|
)
|
|
|
|
test "bcrypt marker":
|
|
check isEncrypted(
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\nbcrypt\ndata"
|
|
)
|
|
|
|
test "aes256-ctr marker":
|
|
check isEncrypted("data with aes256-ctr in it")
|
|
|
|
test "unencrypted key":
|
|
check isEncrypted(
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----\nAAAAB3NzaC1\ndata"
|
|
) == false
|
|
|
|
suite "matchesSecretPattern":
|
|
test "export with KEY=":
|
|
check matchesSecretPattern("export API_KEY=some_value")
|
|
|
|
test "export with SECRET=":
|
|
check matchesSecretPattern(
|
|
"export AWS_SECRET_ACCESS_KEY=abc123"
|
|
)
|
|
|
|
test "bare TOKEN= at start":
|
|
check matchesSecretPattern("TOKEN=abcdef12345")
|
|
|
|
test "bare PASSWORD=":
|
|
check matchesSecretPattern("PASSWORD=hunter2")
|
|
|
|
test "non-secret assignment":
|
|
check matchesSecretPattern("export PATH=/usr/bin") == false
|
|
|
|
test "ordinary command":
|
|
check matchesSecretPattern("ls -la /tmp") == false
|
|
|
|
test "empty string":
|
|
check matchesSecretPattern("") == false
|
|
|
|
suite "matchesCommandPattern":
|
|
test "curl with auth header":
|
|
check matchesCommandPattern(
|
|
"curl -H \"Authorization: Bearer token\" https://api.example.com"
|
|
)
|
|
|
|
test "curl with lowercase -h auth":
|
|
check matchesCommandPattern(
|
|
"curl -h \"authorization: bearer token\" https://api.example.com"
|
|
)
|
|
|
|
test "curl with -u flag":
|
|
check matchesCommandPattern(
|
|
"curl -u user:pass https://api.example.com"
|
|
)
|
|
|
|
test "wget with authorization header":
|
|
check matchesCommandPattern(
|
|
"wget --header=\"Authorization: Basic abc\" https://example.com"
|
|
)
|
|
|
|
test "wget with password":
|
|
check matchesCommandPattern(
|
|
"wget --password=secret https://files.example.com/data.zip"
|
|
)
|
|
|
|
test "mysql with -p flag":
|
|
check matchesCommandPattern("mysql -u root -psecret mydb")
|
|
|
|
test "psql with password":
|
|
check matchesCommandPattern(
|
|
"psql password=secret host=db.example.com"
|
|
)
|
|
|
|
test "sshpass command":
|
|
check matchesCommandPattern("sshpass -p 'mypass' ssh user@host")
|
|
|
|
test "safe curl rejected":
|
|
check matchesCommandPattern("curl https://example.com") == false
|
|
|
|
test "safe git command rejected":
|
|
check matchesCommandPattern("git push origin main") == false
|
|
|
|
test "empty string rejected":
|
|
check matchesCommandPattern("") == false
|
|
|
|
suite "matchesExclude":
|
|
test "exact filename match":
|
|
check matchesExclude("/home/user/.env", @[".env"])
|
|
|
|
test "directory segment match":
|
|
check matchesExclude("/home/user/.git/config", @[".git"])
|
|
|
|
test "no false positive on partial name":
|
|
check matchesExclude(
|
|
"/home/user/.venv/lib/site.py", @[".env"]
|
|
) == false
|
|
|
|
test "no match on embedded substring":
|
|
check matchesExclude(
|
|
"/home/user/environment/data", @[".env"]
|
|
) == false
|
|
|
|
test "empty patterns":
|
|
check matchesExclude("/home/user/.env", @[]) == false
|
|
|
|
suite "permissionSeverity":
|
|
test "returns svInfo for unreadable path":
|
|
check permissionSeverity("/nonexistent/path/abc123") == svInfo
|
|
|
|
suite "parseModules":
|
|
test "single module":
|
|
check parseModules("ssh") == @[catSsh]
|
|
|
|
test "multiple modules":
|
|
let mods = parseModules("ssh,git,cloud")
|
|
check mods.len == 3
|
|
check mods.contains(catSsh)
|
|
check mods.contains(catGit)
|
|
check mods.contains(catCloud)
|
|
|
|
test "with whitespace":
|
|
let mods = parseModules(" browser , keyring ")
|
|
check mods.len == 2
|
|
check mods.contains(catBrowser)
|
|
check mods.contains(catKeyring)
|
|
|
|
test "all modules":
|
|
let mods = parseModules(
|
|
"browser,ssh,cloud,history,keyring,git,apptoken"
|
|
)
|
|
check mods.len == 7
|
|
|
|
test "empty string":
|
|
check parseModules("").len == 0
|
|
|
|
test "unknown module ignored":
|
|
check parseModules("fake,nonexistent").len == 0
|
|
|
|
suite "redactLine":
|
|
test "export with quoted value":
|
|
let got = redactLine("export KEY=\"secret\"")
|
|
check got.contains("KEY=")
|
|
check got.contains("\"") == false
|
|
|
|
test "export with unquoted value":
|
|
let got = redactLine("export API_KEY=mysecretvalue")
|
|
check got.contains("API_KEY=")
|
|
check got.contains("myse")
|
|
check got.contains("cretvalue") == false
|
|
|
|
test "no equals sign":
|
|
check redactLine("no assignment here") == "no assignment here"
|
|
|
|
test "single-quoted value":
|
|
let got = redactLine("export TOKEN='abcdefgh'")
|
|
check got.contains("TOKEN=")
|
|
check got.contains("'") == false
|