java-topology/defects/digikam/test/DigiKamOAuthSecretLogTest.java
russell@unturf.com 16a8ba0900 digikam: 2 new defects (0003 MOAD-0001, 0004 MOAD-0004); lmms: all 5 MOADs CLEAN
digikam-0003 CWE-407: MetaEngine/DMetadata addToXmpTagStringBag and
removeFromXmpTagStringBag call QStringList::contains() inside a loop over
existing entries — O(O*N) per image during batch metadata write.
Fix: build QSet<QString> before the loop for O(1) lookup.
17x speedup at K=200 keywords (unit test PASS).

digikam-0004 CWE-312: O2::onVerificationReceived logs the full OAuth2
token exchange POST body (including client_secret_) via qDebug() at
GrantFlowAuthorizationCode completion — exposes cloud service credentials
in debug logs/stderr for Google Photos, Flickr, OneDrive integrations.
Fix: replace full-body dump with redacted log line.

lmms: all 5 MOADs CLEAN — std::find uses are non-hot, contains() calls
are on QHash/QMap/QSet, no credential logging, no leaked thread context,
no unsynchronized cache access.
2026-03-31 21:15:08 -04:00

89 lines
4.4 KiB
Java

import java.util.*;
import java.util.logging.*;
import java.io.*;
/**
* DigiKamOAuthSecretLogTest — digikam-0004
*
* Reproduces the CWE-312 defect in O2::onVerificationReceived:
* when GrantFlowAuthorizationCode completes, the full token exchange
* POST body — including client_secret — is logged verbatim via qDebug().
*
* Models the defective (full body logged) vs fixed (body redacted) behavior,
* verifying that the fixed version does not emit the secret.
*/
public class DigiKamOAuthSecretLogTest {
// Simulates building the OAuth2 token exchange body (like buildRequestBody)
static String buildRequestBody(Map<String, String> params) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : params.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}
return sb.toString();
}
// Defective: logs full body including client_secret (mirrors o2.cpp line 284)
static String defectiveLog(String clientSecret, String code, String clientId, String redirectUri) {
Map<String, String> parameters = new LinkedHashMap<>();
parameters.put("code", code);
parameters.put("client_id", clientId);
parameters.put("client_secret", clientSecret);
parameters.put("redirect_uri", redirectUri);
parameters.put("grant_type", "authorization_code");
String data = buildRequestBody(parameters);
// Simulates: qDebug() << QString("O2::onVerificationReceived: Exchange access code data:\n%1").arg(data)
return "O2::onVerificationReceived: Exchange access code data:\n" + data;
}
// Fixed: logs redacted message (mirrors patch)
static String fixedLog(String clientSecret, String code, String clientId, String redirectUri) {
// Simulates: qDebug() << "O2::onVerificationReceived: Sending token exchange request (body redacted)"
return "O2::onVerificationReceived: Sending token exchange request (body redacted)";
}
public static void main(String[] args) {
System.out.println("DigiKamOAuthSecretLogTest — digikam-0004 CWE-312");
System.out.println("O2::onVerificationReceived logs client_secret in full POST body");
System.out.println();
String clientSecret = "super_secret_oauth2_client_credential_abc123xyz";
String code = "auth_code_from_callback";
String clientId = "digikam-app-client-id";
String redirectUri = "http://localhost:8080/callback";
// Defective: secret appears in log output
String defectiveOutput = defectiveLog(clientSecret, code, clientId, redirectUri);
boolean defectiveLeaks = defectiveOutput.contains(clientSecret);
System.out.println("Defective log output:");
System.out.println(" " + defectiveOutput.replace("\n", "\n "));
System.out.println(" Contains secret: " + defectiveLeaks);
assert defectiveLeaks : "Defective variant should contain secret in log (test setup error)";
System.out.println("PASS: defective variant confirmed to leak secret");
// Fixed: secret must NOT appear in log output
String fixedOutput = fixedLog(clientSecret, code, clientId, redirectUri);
boolean fixedLeaks = fixedOutput.contains(clientSecret);
System.out.println("\nFixed log output:");
System.out.println(" " + fixedOutput);
System.out.println(" Contains secret: " + fixedLeaks);
assert !fixedLeaks : "Fixed variant must not contain secret in log, but found: " + fixedOutput;
System.out.println("PASS: fixed variant does not leak secret");
// Verify fixed log still contains useful diagnostic info
assert fixedOutput.contains("O2::onVerificationReceived") :
"Fixed log should still identify the function";
assert fixedOutput.contains("redacted") :
"Fixed log should indicate body was redacted";
System.out.println("PASS: fixed log retains useful diagnostic context");
// Edge: verify no partial secret leak (first 3 chars truncation check)
String secretPrefix3 = clientSecret.substring(0, 3);
assert !fixedOutput.contains(secretPrefix3) :
"Fixed log should not contain even first 3 chars of secret";
System.out.println("PASS: fixed log contains no partial secret fragment");
System.out.println("\nALL PASS");
}
}