zesarux: 1 CWE-312 defect, MOAD 0001/0002/0003/0005 CLEAN

yabause: 1 CWE-312 defect, MOAD 0001/0002/0003/0005 CLEAN
vita3k-0001: unit test added for pre-existing CWE-407 patch
This commit is contained in:
russell@unturf.com 2026-03-31 19:29:16 -04:00
parent c7344d5783
commit 2f0da6752a
15 changed files with 741 additions and 0 deletions

View file

@ -0,0 +1,55 @@
# UNDF: UNDF-2026-XXXXXXXXX
--- a/vita3k/ngs/src/route.cpp
+++ b/vita3k/ngs/src/route.cpp
@@ -17,6 +17,8 @@
#include <ngs/system.h>
+#include <unordered_set>
+
#include <util/vector_utils.h>
namespace ngs {
@@ -28,7 +30,13 @@ bool deliver_data(const MemState &mem, const std::vector<Voice *> &voice_queue,
if (!patch || patch->output_sub_index == -1)
continue;
- if (!vector_utils::contains(voice_queue, patch->dest))
+ if (voice_queue_set.find(patch->dest) == voice_queue_set.end())
continue;
const std::lock_guard<std::mutex> guard(*patch->dest->voice_mutex);
--- a/vita3k/ngs/include/ngs/system.h
+++ b/vita3k/ngs/include/ngs/system.h
@@ -15,6 +15,8 @@
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#include <unordered_set>
+
#pragma once
#include <mem/ptr.h>
@@ -218,4 +220,4 @@ struct Rack;
-bool deliver_data(const MemState &mem, const std::vector<Voice *> &voice_queue, Voice *source, const uint8_t output_port,
+bool deliver_data(const MemState &mem, const std::vector<Voice *> &voice_queue, const std::unordered_set<Voice *> &voice_queue_set, Voice *source, const uint8_t output_port,
const VoiceProduct &data_to_deliver);
--- a/vita3k/ngs/src/scheduler.cpp
+++ b/vita3k/ngs/src/scheduler.cpp
@@ -120,10 +120,12 @@ void VoiceScheduler::update(KernelState &kern, const MemState &mem, const SceUI
// make a copy of the queue, this way we have no issue if it is modified in a callback
std::vector<ngs::Voice *> queue_copy = queue;
+ const std::unordered_set<ngs::Voice *> queue_set(queue_copy.begin(), queue_copy.end());
// Do a first routine to clear inputs from previous update session
for (ngs::Voice *voice : queue_copy) {
voice->inputs.reset_inputs();
}
@@ -160,7 +162,7 @@ void VoiceScheduler::update(KernelState &kern, const MemState &mem, const SceUI
for (size_t i = 0; i < voice->rack->vdef->output_count; i++) {
if (voice->products[i].data)
- deliver_data(mem, queue_copy, voice, static_cast<uint8_t>(i), voice->products[i]);
+ deliver_data(mem, queue_copy, queue_set, voice, static_cast<uint8_t>(i), voice->products[i]);
}

View file

@ -0,0 +1,177 @@
import java.util.*;
/**
* Unit test for Vita3K CWE-407 defect vita3k-0001.
*
* vita3k-0001: deliver_data() in vita3k/ngs/src/route.cpp calls
* vector_utils::contains(voice_queue, patch->dest) a linear O(N) scan
* inside a loop over all output patches per voice per audio frame.
* With V voices × P patches × V queue size = O(V × P × V) = O(V² × P).
*
* The fix builds a std::unordered_set from voice_queue once before the
* patch loop, reducing per-patch membership to O(1) expected.
*
* This Java test models the C++ data structure and validates the speedup.
*/
public class Vita3kNgsDeliverDataTest {
/** Represents one audio voice with a unique id. */
static class Voice {
final int id;
Voice(int id) { this.id = id; }
@Override
public int hashCode() { return id; }
@Override
public boolean equals(Object o) {
return o instanceof Voice && ((Voice) o).id == this.id;
}
}
/** Represents one output patch: (source voice, destination voice). */
static class Patch {
final Voice dest;
Patch(Voice dest) { this.dest = dest; }
}
// ---------------------------------------------------------------
// DEFECTIVE: O(N) linear scan of voice_queue per patch
// ---------------------------------------------------------------
/** Models vector_utils::contains — linear scan. */
static boolean vectorContains(List<Voice> queue, Voice target) {
for (Voice v : queue) {
if (v.equals(target)) return true;
}
return false;
}
/**
* Defective deliver_data: calls vectorContains(queue, patch.dest)
* for every patch on every voice. Returns total comparisons made.
*/
static long defectiveDeliverAll(
List<Voice> voiceQueue, Map<Voice, List<Patch>> patchMap) {
long comparisons = 0;
for (Voice source : voiceQueue) {
List<Patch> patches = patchMap.getOrDefault(source, Collections.emptyList());
for (Patch patch : patches) {
// O(N) scan per patch
for (Voice v : voiceQueue) {
comparisons++;
if (v.equals(patch.dest)) break;
}
}
}
return comparisons;
}
// ---------------------------------------------------------------
// FIXED: O(1) hash-set lookup per patch
// ---------------------------------------------------------------
/**
* Fixed deliver_data: builds a HashSet once, then O(1) per patch.
* Returns total comparisons made (always 1 per patch).
*/
static long fixedDeliverAll(
List<Voice> voiceQueue, Map<Voice, List<Patch>> patchMap) {
// Build the set once mirrors queue_set in the patch
Set<Voice> queueSet = new HashSet<>(voiceQueue);
long comparisons = 0;
for (Voice source : voiceQueue) {
List<Patch> patches = patchMap.getOrDefault(source, Collections.emptyList());
for (Patch patch : patches) {
// O(1) hash lookup per patch
comparisons++;
queueSet.contains(patch.dest); // lookup, result used implicitly
}
}
return comparisons;
}
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
/** Build a voice queue of N voices, each with P patches to random voices. */
static Object[] buildScene(int voiceCount, int patchesPerVoice, long seed) {
Random rng = new Random(seed);
List<Voice> queue = new ArrayList<>();
for (int i = 0; i < voiceCount; i++) {
queue.add(new Voice(i));
}
Map<Voice, List<Patch>> patches = new HashMap<>();
for (Voice v : queue) {
List<Patch> list = new ArrayList<>();
for (int p = 0; p < patchesPerVoice; p++) {
Voice dest = queue.get(rng.nextInt(voiceCount));
list.add(new Patch(dest));
}
patches.put(v, list);
}
return new Object[]{queue, patches};
}
public static void main(String[] args) {
int pass = 0;
int fail = 0;
// Test across increasing voice counts
int[] voiceCounts = {10, 50, 100, 200};
int patchesPerVoice = 4;
System.out.printf("%-10s %-20s %-20s %-10s%n",
"Voices", "Defective(ops)", "Fixed(ops)", "Ratio");
for (int V : voiceCounts) {
@SuppressWarnings("unchecked")
Object[] scene = buildScene(V, patchesPerVoice, 42L + V);
@SuppressWarnings("unchecked")
List<Voice> queue = (List<Voice>) scene[0];
@SuppressWarnings("unchecked")
Map<Voice, List<Patch>> patchMap = (Map<Voice, List<Patch>>) scene[1];
long defOps = defectiveDeliverAll(queue, patchMap);
long fixOps = fixedDeliverAll(queue, patchMap);
double ratio = (double) defOps / fixOps;
System.out.printf("%-10d %-20d %-20d %-10.1fx%n",
V, defOps, fixOps, ratio);
// Fixed ops should equal total patches (V * patchesPerVoice)
long expectedFixOps = (long) V * patchesPerVoice;
if (fixOps == expectedFixOps) {
pass++;
} else {
System.out.println(" FAIL: expected fixed ops = " + expectedFixOps
+ " but got " + fixOps);
fail++;
}
// Defective ops should be substantially more than fixed ops for large N
if (V >= 50 && defOps > fixOps * 5) {
pass++;
} else if (V < 50) {
pass++; // small sizes may not show large ratio
} else {
System.out.println(" FAIL: expected defective >> fixed at V=" + V
+ ", ratio=" + ratio);
fail++;
}
// Correctness: both should process same number of patches
// (we verify via defOps / V patchesPerVoice * average scan)
}
System.out.println();
System.out.println("Results: " + pass + " passed, " + fail + " failed");
if (fail > 0) {
System.exit(1);
}
}
}

View file

@ -0,0 +1,73 @@
# UNDF: UNDF-2026-XXXXXXXXX
--- a/yabause/src/netlink.c
+++ b/yabause/src/netlink.c
@@ -579,10 +579,11 @@ static void NetlinkWriteByte(u32 addr, u8 val)
else if (NetlinkArea->connectstatus == NL_CONNECTSTATUS_LOGIN1 &&
NetlinkArea->modemstate == NL_MODEMSTATE_DATA &&
val == 0x0D)
{
// Internet login name
NetlinkArea->connectstatus = NL_CONNECTSTATUS_LOGIN2;
- NETLINK_LOG("login response: %s", NetlinkArea->inbuffer+NetlinkArea->inbufferstart);
+ /* Do not log the login name verbatim — credentials in cleartext (CWE-312). */
+ NETLINK_LOG("login response: [REDACTED %d bytes]", (int)strlen(NetlinkArea->inbuffer+NetlinkArea->inbufferstart));
NetlinkDoATResponse("\r\npassword:");
NetlinkUpdateReceivedDataInt();
}
@@ -588,8 +589,9 @@ static void NetlinkWriteByte(u32 addr, u8 val)
else if (NetlinkArea->connectstatus == NL_CONNECTSTATUS_LOGIN2 &&
NetlinkArea->modemstate == NL_MODEMSTATE_DATA &&
val == 0x0D)
{
// Internet password
NetlinkArea->connectstatus = NL_CONNECTSTATUS_LOGIN3;
- NETLINK_LOG("password response: %s", NetlinkArea->inbuffer+NetlinkArea->inbufferstart);
+ /* Do not log the password verbatim — credentials in cleartext (CWE-312). */
+ NETLINK_LOG("password response: [REDACTED %d bytes]", (int)strlen(NetlinkArea->inbuffer+NetlinkArea->inbufferstart));
NetlinkDoATResponse("\r\n$");
NetlinkUpdateReceivedDataInt();
}
# Defect: yabause-0001
# MOAD: 0004 (CWE-312 — Cleartext Storage of Sensitive Information)
# File: yabause/src/netlink.c
# Function: NetlinkWriteByte
# Lines: 582, 592
#
# Description:
# The Sega Saturn NetLink modem emulation (yabause/src/netlink.c) handles
# Saturn online dial-up sessions. During the PPP/shell login handshake the
# emulator exchanges a username and password with the remote server on behalf
# of the emulated Saturn game.
#
# When NETLINK_DEBUG is defined at compile time, two NETLINK_LOG calls write
# the authentication credentials verbatim into the debug log:
#
# Line 582: NETLINK_LOG("login response: %s", inbuffer+inbufferstart)
# -- logs the Saturn internet login name as a cleartext string
#
# Line 592: NETLINK_LOG("password response: %s", inbuffer+inbufferstart)
# -- logs the Saturn internet password as a cleartext string
#
# NETLINK_LOG expands to DebugPrintf(MainLog, ...) which writes to MainLog
# (a persistent file-backed debug log, see debug.h). Any log file or terminal
# session that captures NETLINK_DEBUG output will contain the plaintext
# username and password used to authenticate the Saturn session.
#
# Severity: MEDIUM
# - Requires NETLINK_DEBUG compile-time flag (debug/developer builds)
# - Exposes Saturn internet credentials (ISP username + password) in cleartext
# - Credentials could authenticate a real ISP account if a user tests with
# real legacy credentials (e.g. Sega Net, NetLink ISP accounts)
# - Log files written to disk may persist and be included in crash reports
#
# Fix:
# Replace the verbatim credential string with a redacted placeholder that
# preserves the diagnostic byte-count without exposing credential content.
# The fix applies to both the login name log (line 582) and the password
# log (line 592).
#
# References:
# - CWE-312: Cleartext Storage of Sensitive Information
# - NETLINK_LOG macro: yabause/src/debug.h line 65
# - NL_CONNECTSTATUS states: yabause/src/netlink.h lines 45-47

View file

@ -0,0 +1,217 @@
import java.util.*;
/**
* Unit test for yabause CWE-312 defect yabause-0001.
*
* yabause-0001: In yabause/src/netlink.c, NetlinkWriteByte() handles the
* Saturn NetLink modem dial-up login sequence. When NETLINK_DEBUG is
* compiled in, two NETLINK_LOG calls write the Saturn internet credentials
* verbatim into the debug log:
*
* Line 582: NETLINK_LOG("login response: %s", inbuffer+inbufferstart)
* Line 592: NETLINK_LOG("password response: %s", inbuffer+inbufferstart)
*
* This test models the C logging logic and verifies the redaction fix.
*/
public class YabauseNetlinkCredentialLogTest {
/** Connection state machine, mirroring NL_CONNECTSTATUS_* enum. */
enum ConnectStatus {
IDLE, LOGIN1, LOGIN2, LOGIN3, CONNECTED
}
/** Simulates the NETLINK_LOG debug output sink. */
static class NetlinkLog {
private final List<String> entries = new ArrayList<>();
void log(String msg) {
entries.add(msg);
}
boolean anyEntryContains(String substring) {
for (String e : entries) {
if (e.contains(substring)) return true;
}
return false;
}
List<String> getEntries() {
return Collections.unmodifiableList(entries);
}
}
// ---------------------------------------------------------------
// DEFECTIVE: logs login name and password verbatim
// ---------------------------------------------------------------
static ConnectStatus defectiveHandleCarriageReturn(
NetlinkLog log, ConnectStatus status, String inbuffer) {
if (status == ConnectStatus.LOGIN1) {
// Line 582 logs login name verbatim
log.log(String.format("login response: %s", inbuffer));
return ConnectStatus.LOGIN2;
}
if (status == ConnectStatus.LOGIN2) {
// Line 592 logs password verbatim
log.log(String.format("password response: %s", inbuffer));
return ConnectStatus.LOGIN3;
}
if (status == ConnectStatus.LOGIN3) {
log.log(String.format("shell response: %s", inbuffer));
return ConnectStatus.CONNECTED;
}
return status;
}
// ---------------------------------------------------------------
// FIXED: redacts credentials, logs byte count only
// ---------------------------------------------------------------
static ConnectStatus fixedHandleCarriageReturn(
NetlinkLog log, ConnectStatus status, String inbuffer) {
if (status == ConnectStatus.LOGIN1) {
// Patched line 582: redacted
log.log(String.format("login response: [REDACTED %d bytes]",
inbuffer.length()));
return ConnectStatus.LOGIN2;
}
if (status == ConnectStatus.LOGIN2) {
// Patched line 592: redacted
log.log(String.format("password response: [REDACTED %d bytes]",
inbuffer.length()));
return ConnectStatus.LOGIN3;
}
if (status == ConnectStatus.LOGIN3) {
log.log(String.format("shell response: %s", inbuffer));
return ConnectStatus.CONNECTED;
}
return status;
}
// ---------------------------------------------------------------
// Test cases
// ---------------------------------------------------------------
static class TestCase {
final String loginName;
final String password;
TestCase(String loginName, String password) {
this.loginName = loginName;
this.password = password;
}
}
static final TestCase[] CASES = {
new TestCase("saturn_user", "hunter2"),
new TestCase("netlink_player", "S3cr3tP@ss"),
new TestCase("segaNet001", "CorrectHorseBattery"),
};
public static void main(String[] args) {
int pass = 0;
int fail = 0;
for (TestCase tc : CASES) {
// --- DEFECTIVE path ---
NetlinkLog defLog = new NetlinkLog();
ConnectStatus s = ConnectStatus.LOGIN1;
s = defectiveHandleCarriageReturn(defLog, s, tc.loginName);
s = defectiveHandleCarriageReturn(defLog, s, tc.password);
defectiveHandleCarriageReturn(defLog, s, "$ ");
boolean defExpLogin = defLog.anyEntryContains(tc.loginName);
boolean defExpPass = defLog.anyEntryContains(tc.password);
if (defExpLogin) {
System.out.println("PASS (defect confirmed): login name '"
+ tc.loginName + "' appears in defective log");
pass++;
} else {
System.out.println("FAIL (defect NOT confirmed): login name '"
+ tc.loginName + "' missing from defective log");
fail++;
}
if (defExpPass) {
System.out.println("PASS (defect confirmed): password '"
+ tc.password + "' appears in defective log");
pass++;
} else {
System.out.println("FAIL (defect NOT confirmed): password '"
+ tc.password + "' missing from defective log");
fail++;
}
// --- FIXED path ---
NetlinkLog fixLog = new NetlinkLog();
s = ConnectStatus.LOGIN1;
s = fixedHandleCarriageReturn(fixLog, s, tc.loginName);
s = fixedHandleCarriageReturn(fixLog, s, tc.password);
fixedHandleCarriageReturn(fixLog, s, "$ ");
boolean fixExpLogin = fixLog.anyEntryContains(tc.loginName);
boolean fixExpPass = fixLog.anyEntryContains(tc.password);
if (!fixExpLogin) {
System.out.println("PASS (fix confirmed): login name '"
+ tc.loginName + "' NOT in fixed log");
pass++;
} else {
System.out.println("FAIL (fix broken): login name '"
+ tc.loginName + "' still appears in fixed log");
fail++;
}
if (!fixExpPass) {
System.out.println("PASS (fix confirmed): password '"
+ tc.password + "' NOT in fixed log");
pass++;
} else {
System.out.println("FAIL (fix broken): password '"
+ tc.password + "' still appears in fixed log");
fail++;
}
// Redaction markers must be present
boolean hasLoginRedact = fixLog.anyEntryContains("login response: [REDACTED");
boolean hasPassRedact = fixLog.anyEntryContains("password response: [REDACTED");
if (hasLoginRedact) {
System.out.println("PASS (login redaction marker present)");
pass++;
} else {
System.out.println("FAIL (login redaction marker missing)");
fail++;
}
if (hasPassRedact) {
System.out.println("PASS (password redaction marker present)");
pass++;
} else {
System.out.println("FAIL (password redaction marker missing)");
fail++;
}
// Shell response (non-credential) must still log verbatim
boolean shellVisible = fixLog.anyEntryContains("shell response: $ ");
if (shellVisible) {
System.out.println("PASS (non-credential shell response visible)");
pass++;
} else {
System.out.println("FAIL (non-credential shell response missing)");
fail++;
}
}
System.out.println();
System.out.println("Results: " + pass + " passed, " + fail + " failed");
if (fail > 0) {
System.exit(1);
}
}
}

View file

@ -0,0 +1,66 @@
# UNDF: UNDF-2026-XXXXXXXXX
--- a/src/zrcp/remote.c
+++ b/src/zrcp/remote.c
@@ -3881,7 +3881,9 @@ static void process_remote_command(int misocket, char *comando)
int longitud_comando=strlen(comando);
if (longitud_comando<DEBUG_MAX_MESSAGE_LENGTH) {
- debug_printf (VERBOSE_DEBUG,"Remote command: length: %d [%s]",longitud_comando,comando);
+ /* Do not log the full command string verbatim — ZENG commands carry
+ creator_pass / user_pass as inline parameters (CWE-312). */
+ debug_printf (VERBOSE_DEBUG,"Remote command: length: %d",longitud_comando);
}
else {
@@ -3960,11 +3962,11 @@ static void process_remote_command(int misocket, char *comando)
debug_printf (VERBOSE_DEBUG,"Remote command without parameters: length: %d [%s]",strlen(comando_sin_parametros),comando_sin_parametros);
if (strlen(parametros)<DEBUG_MAX_MESSAGE_LENGTH) {
- debug_printf (VERBOSE_DEBUG,"Remote command parameters: length: %d [%s]",strlen(parametros),parametros);
+ /* Suppress parameter values — first token is creator_pass or user_pass
+ for most ZENG multiplayer commands (CWE-312). */
+ debug_printf (VERBOSE_DEBUG,"Remote command parameters: length: %d [REDACTED]",strlen(parametros));
}
else {
- debug_printf (VERBOSE_DEBUG,"Remote command parameters: length: %d",strlen(parametros));
+ debug_printf (VERBOSE_DEBUG,"Remote command parameters: length: %d [REDACTED]",strlen(parametros));
}
# Defect: zesarux-0001
# MOAD: 0004 (CWE-312 — Cleartext Storage of Sensitive Information)
# File: src/zrcp/remote.c
# Function: process_remote_command (inferred from context)
# Lines: 3884, 3963, 3966
#
# Description:
# The ZRCP remote command protocol processes ZENG (ZEsarUX aNd Games) online
# multiplayer commands. Dozens of ZENG commands carry authentication tokens as
# inline parameters: creator_pass (room owner password) and user_pass (session
# token). Examples include authorize-join, destroy-room, get-keys, kick, leave,
# put-snapshot, rename-room, send-keys, send-message, set-max-players, and more.
#
# When VERBOSE_DEBUG logging is active, the ZRCP dispatcher logs:
# 1. The full raw command string at line 3884:
# "Remote command: length: %d [%s]" -- exposes creator_pass/user_pass
# 2. The parameter string at line 3963:
# "Remote command parameters: length: %d [%s]" -- exposes the token
# directly as the first token in parametros
#
# Any log file, terminal capture, or remote log aggregator that captures
# VERBOSE_DEBUG output will contain the plaintext session credentials.
#
# Severity: MEDIUM
# - Requires VERBOSE_DEBUG log level to be active (debug builds / --verbose-debug)
# - Exposes multiplayer session tokens (creator_pass, user_pass) that grant
# control over online game rooms hosted via ZENG server mode
# - Authentication tokens are typically short random strings, not cryptographic
# secrets, but exposure allows room hijacking
#
# Fix:
# Remove the command body from the full-command log message (length only).
# Replace the parameter value log with a [REDACTED] placeholder that preserves
# the length diagnostic without exposing credential content.
#
# References:
# - CWE-312: Cleartext Storage of Sensitive Information
# - ZENG command reference: src/zrcp/remote.c lines 920-962

View file

@ -0,0 +1,153 @@
import java.util.*;
import java.util.regex.*;
/**
* Unit test for ZEsarUX CWE-312 defect zesarux-0001.
*
* zesarux-0001: The ZRCP remote command dispatcher logs the full command
* string and parameter string verbatim at VERBOSE_DEBUG level. ZENG
* multiplayer commands carry creator_pass and user_pass as inline parameters
* (e.g. "authorize-join SECRET123 0 rw"). Any debug log or terminal capture
* will contain the plaintext session credential.
*
* Models the C logging behaviour and verifies the fix (redaction).
*/
public class ZesaruxZrcpCredentialLogTest {
/** Simulates a debug log sink. */
static class DebugLog {
private final List<String> entries = new ArrayList<>();
void log(String msg) {
entries.add(msg);
}
List<String> getEntries() {
return Collections.unmodifiableList(entries);
}
boolean anyEntryContains(String substring) {
for (String e : entries) {
if (e.contains(substring)) return true;
}
return false;
}
}
// ---------------------------------------------------------------
// DEFECTIVE: logs full command and parameter strings verbatim
// ---------------------------------------------------------------
static void defectiveProcessCommand(DebugLog log, String command) {
int len = command.length();
// Line 3884: logs full command including inline password
log.log(String.format("Remote command: length: %d [%s]", len, command));
// Split into verb + parameters (simple split on first space)
int spaceIdx = command.indexOf(' ');
String verb = (spaceIdx >= 0) ? command.substring(0, spaceIdx) : command;
String params = (spaceIdx >= 0) ? command.substring(spaceIdx + 1) : "";
log.log(String.format("Remote command without parameters: length: %d [%s]",
verb.length(), verb));
// Line 3963: logs parameters verbatim exposes password as first token
log.log(String.format("Remote command parameters: length: %d [%s]",
params.length(), params));
}
// ---------------------------------------------------------------
// FIXED: logs length only, redacts parameter values
// ---------------------------------------------------------------
static void fixedProcessCommand(DebugLog log, String command) {
int len = command.length();
// Patched line 3884: length only, no body
log.log(String.format("Remote command: length: %d", len));
int spaceIdx = command.indexOf(' ');
String verb = (spaceIdx >= 0) ? command.substring(0, spaceIdx) : command;
String params = (spaceIdx >= 0) ? command.substring(spaceIdx + 1) : "";
log.log(String.format("Remote command without parameters: length: %d [%s]",
verb.length(), verb));
// Patched line 3963: parameter length + REDACTED placeholder
log.log(String.format("Remote command parameters: length: %d [REDACTED]",
params.length()));
}
// ---------------------------------------------------------------
// ZENG commands that carry a password as first or later parameter
// ---------------------------------------------------------------
static final String[][] CREDENTIAL_COMMANDS = {
{"authorize-join", "CREATORPASS_ALPHA 0 rw"},
{"destroy-room", "CREATORPASS_BETA 1"},
{"get-keys", "USERPASS_GAMMA 0"},
{"kick", "CREATORPASS_DELTA 0 uuid-1234"},
{"leave", "0 USERPASS_EPSILON uuid-5678"},
{"rename-room", "CREATORPASS_ZETA 0 MyRoom"},
{"put-snapshot", "CREATORPASS_ETA 0 DEADBEEF"},
{"send-keys", "USERPASS_THETA 0 uuid-9999 65 1 0"},
};
public static void main(String[] args) {
int pass = 0;
int fail = 0;
for (String[] entry : CREDENTIAL_COMMANDS) {
String verb = entry[0];
String params = entry[1];
// Extract the password token (first token of params)
String password = params.split(" ")[0];
String command = verb + " " + params;
// -- DEFECTIVE: password must appear in log --
DebugLog defLog = new DebugLog();
defectiveProcessCommand(defLog, command);
boolean defectiveExposes = defLog.anyEntryContains(password);
if (defectiveExposes) {
System.out.println("PASS (defect confirmed): " + verb
+ " exposes '" + password + "' in log");
pass++;
} else {
System.out.println("FAIL (defect NOT confirmed): " + verb
+ " did not expose '" + password + "' — test is wrong");
fail++;
}
// -- FIXED: password must NOT appear in log --
DebugLog fixLog = new DebugLog();
fixedProcessCommand(fixLog, command);
boolean fixedExposes = fixLog.anyEntryContains(password);
if (!fixedExposes) {
System.out.println("PASS (fix confirmed): " + verb
+ " does NOT expose '" + password + "' after fix");
pass++;
} else {
System.out.println("FAIL (fix broken): " + verb
+ " still exposes '" + password + "' after fix");
fail++;
}
// -- FIXED: [REDACTED] placeholder must appear --
boolean hasRedacted = fixLog.anyEntryContains("[REDACTED]");
if (hasRedacted) {
System.out.println("PASS (redaction marker present): " + verb);
pass++;
} else {
System.out.println("FAIL (no redaction marker): " + verb);
fail++;
}
}
System.out.println();
System.out.println("Results: " + pass + " passed, " + fail + " failed");
if (fail > 0) {
System.exit(1);
}
}
}