Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
/***************************************************************************
|
|
|
|
|
* Copyright (C) 2025 by Mike Conley - mike.conley@stickmud.com *
|
|
|
|
|
* *
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
|
* (at your option) any later version. *
|
|
|
|
|
* *
|
|
|
|
|
* This program is distributed in the hope that it will be useful, *
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
|
|
|
* GNU General Public License for more details. *
|
|
|
|
|
* *
|
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
|
|
|
|
* along with this program; if not, write to the *
|
|
|
|
|
* Free Software Foundation, Inc., *
|
|
|
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "TMxpProcessor.h"
|
|
|
|
|
#include "TMxpStubClient.h"
|
|
|
|
|
#include "TMxpTagParser.h"
|
2026-02-09 05:29:46 -05:00
|
|
|
#include <QTest>
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-21 13:45:49 -04:00
|
|
|
* Test the MXP mode security and tag validation implementation
|
2026-02-09 05:29:46 -05:00
|
|
|
*
|
2026-03-21 13:45:49 -04:00
|
|
|
* This addresses issues #8150 and #8899: MXP "open mode" security and
|
|
|
|
|
* preventing false positives when < appears in normal game text.
|
2026-02-09 05:29:46 -05:00
|
|
|
*
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
* According to the MXP spec (https://www.zuggsoft.com/zmud/mxp.htm):
|
2026-03-21 13:45:49 -04:00
|
|
|
* - OPEN mode (default after negotiation): Only OPEN-category tags allowed
|
|
|
|
|
* - SECURE mode: All MXP tags allowed
|
|
|
|
|
* - LOCKED mode: No tags at all (verbatim text)
|
|
|
|
|
*
|
|
|
|
|
* Tags that don't match known MXP spec tags or user-defined elements
|
|
|
|
|
* are treated as literal text, preventing false positives from characters
|
|
|
|
|
* like < in normal game text.
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
*/
|
|
|
|
|
class TMxpModeSecurityTest : public QObject {
|
2026-02-09 05:29:46 -05:00
|
|
|
Q_OBJECT
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
|
|
|
|
|
private:
|
2026-03-21 13:45:49 -04:00
|
|
|
/**
|
|
|
|
|
* Helper: feed a string through the MXP processor character by character
|
|
|
|
|
* and collect all entity values that are output (rejected/literal text).
|
|
|
|
|
* Handles both HANDLER_INSERT_ENTITY_SYS and HANDLER_INSERT_AND_REPROCESS
|
|
|
|
|
* which are used when tags are rejected as literal text.
|
|
|
|
|
*/
|
|
|
|
|
static QString processAndCollectOutput(TMxpProcessor &processor,
|
|
|
|
|
const std::string &input) {
|
|
|
|
|
QString output;
|
|
|
|
|
for (char ch : input) {
|
|
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
if (result == HANDLER_INSERT_ENTITY_SYS ||
|
|
|
|
|
result == HANDLER_INSERT_AND_REPROCESS) {
|
|
|
|
|
output += processor.getEntityValue();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
private slots:
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Mode defaults and transitions
|
|
|
|
|
// ---------------------------------------------------------------
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
void testDefaultModeIsOpen() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// MXP spec: "OPEN MODE starts as the Default mode"
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
}
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
void testModeTransitions() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_SECURE);
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
processor.setMode(MXP_MODE_CODE_OPEN);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
processor.setMode(MXP_MODE_CODE_LOCKED);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_LOCKED);
|
|
|
|
|
}
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Tag recognition (static sets from the MXP spec)
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testOpenModeTagsRecognized() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
// All formatting tags should be recognized
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("B")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("BOLD")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("I")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("COLOR")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("FONT")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("BR")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("HR")));
|
|
|
|
|
|
|
|
|
|
// Case insensitive
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("b")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("Color")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testSecureModeTagsRecognized() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("SEND")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("A")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("VERSION")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("SUPPORT")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("SOUND")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("IMAGE")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("FRAME")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("DEST")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("!ELEMENT")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("!ENTITY")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("VAR")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testUnknownTagsNotRecognized() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QVERIFY(!processor.isRecognizedMxpTag(qsl("test")));
|
|
|
|
|
QVERIFY(!processor.isRecognizedMxpTag(qsl("villains")));
|
|
|
|
|
QVERIFY(!processor.isRecognizedMxpTag(qsl("notmxp")));
|
|
|
|
|
QVERIFY(!processor.isRecognizedMxpTag(qsl("echo")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// OPEN mode: only OPEN tags allowed
|
|
|
|
|
// ---------------------------------------------------------------
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
|
2026-02-09 05:29:46 -05:00
|
|
|
void testSendTagBlockedInOpenMode() {
|
2026-03-21 13:45:49 -04:00
|
|
|
TMxpStubClient client;
|
2026-02-09 05:29:46 -05:00
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// SEND is a SECURE tag - should be rejected in OPEN mode and
|
|
|
|
|
// output as literal text
|
|
|
|
|
QString output =
|
|
|
|
|
processAndCollectOutput(processor, "<SEND href=\"cmd\">click</SEND>");
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// The SEND tag should have been output as literal text
|
|
|
|
|
QVERIFY(output.contains(qsl("<SEND")));
|
2026-02-09 05:29:46 -05:00
|
|
|
|
|
|
|
|
// No link should have been created
|
|
|
|
|
QCOMPARE(client.mHrefs.size(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
void testFormattingTagsAllowedInOpenMode() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
|
|
|
|
// Process formatting tags - these should all work in OPEN mode
|
|
|
|
|
QString output = processAndCollectOutput(processor, "<B>bold</B>");
|
|
|
|
|
|
|
|
|
|
// Bold tags should have been handled (not rejected as literal text)
|
|
|
|
|
// When tags are rejected, the helper captures them as entity values
|
|
|
|
|
QVERIFY2(!output.contains(qsl("<B>")),
|
|
|
|
|
"Opening <B> tag should not appear as literal text");
|
|
|
|
|
QVERIFY2(!output.contains(qsl("</B>")),
|
|
|
|
|
"Closing </B> tag should not appear as literal text");
|
|
|
|
|
|
|
|
|
|
// Bold should have been activated and then deactivated
|
|
|
|
|
// After </B>, boldCounter should be back to 0
|
|
|
|
|
QCOMPARE(client.boldCounter, static_cast<unsigned int>(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testColorTagAllowedInOpenMode() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
|
|
|
|
// COLOR is an OPEN tag - process only the opening tag so that
|
|
|
|
|
// pushColor is called but popColor hasn't happened yet
|
|
|
|
|
processAndCollectOutput(processor, "<COLOR red>text");
|
|
|
|
|
|
|
|
|
|
// Should have set the color (not rejected)
|
|
|
|
|
QVERIFY(!client.fgColor.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// SECURE mode: all MXP tags allowed
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
2026-02-09 05:29:46 -05:00
|
|
|
void testSendTagAllowedInSecureMode() {
|
2026-03-21 13:45:49 -04:00
|
|
|
TMxpStubClient client;
|
2026-02-09 05:29:46 -05:00
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_SECURE);
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
processAndCollectOutput(processor,
|
|
|
|
|
"<SEND href=\"test command\">click me</SEND>");
|
2026-02-09 05:29:46 -05:00
|
|
|
|
|
|
|
|
// A link should have been created
|
|
|
|
|
QVERIFY(client.mHrefs.size() > 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
void testElementDefinitionAllowedInSecureMode() {
|
|
|
|
|
TMxpStubClient client;
|
2026-02-09 05:29:46 -05:00
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_SECURE);
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// !ELEMENT is a SECURE tag
|
|
|
|
|
processAndCollectOutput(
|
|
|
|
|
processor, "<!ELEMENT RName '<FONT COLOR=Red><B>' FLAG=\"RoomName\">");
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// Should have registered the element
|
|
|
|
|
QVERIFY(processor.getMxpTagProcessor().getElementRegistry().containsElement(
|
|
|
|
|
qsl("RName")));
|
2026-02-09 05:29:46 -05:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// LOCKED mode: no tags at all
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
2026-02-09 05:29:46 -05:00
|
|
|
void testNoTagsAllowedInLockedMode() {
|
2026-03-21 13:45:49 -04:00
|
|
|
TMxpStubClient client;
|
2026-02-09 05:29:46 -05:00
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_LOCKED);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_LOCKED);
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// In LOCKED mode, text passes through without parsing
|
2026-02-09 05:29:46 -05:00
|
|
|
std::string input = "<B>bold</B>";
|
|
|
|
|
|
|
|
|
|
for (char ch : input) {
|
2026-03-21 13:45:49 -04:00
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
// All characters should fall through (no tag parsing)
|
|
|
|
|
QCOMPARE(result, HANDLER_FALL_THROUGH);
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
}
|
2026-03-21 13:45:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// False positive prevention: non-MXP text with < characters
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testLessThanInTextOutputAsLiteral() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
|
|
|
|
// "< villains>" - the '<' starts tag parsing, ' villains'
|
|
|
|
|
// accumulates, '>' closes it. "villains" is not a known OPEN tag.
|
|
|
|
|
QString output = processAndCollectOutput(processor, "< villains>");
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// Should be output as literal text since "villains" is not an MXP tag
|
|
|
|
|
QVERIFY(output.contains(qsl("villains")));
|
2026-02-09 05:29:46 -05:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
void testUnknownTagNameRejectedImmediately() {
|
|
|
|
|
TMxpStubClient client;
|
2026-02-09 05:29:46 -05:00
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// <xyz is not a known MXP tag - should be rejected early via prefix check
|
|
|
|
|
// 'x' doesn't start any OPEN mode tag
|
|
|
|
|
QString output = processAndCollectOutput(processor, "<xyz something>");
|
|
|
|
|
|
|
|
|
|
// Should show the original text as literal
|
|
|
|
|
QVERIFY(output.contains(qsl("<xyz")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testSecureTagInTextRejectedInOpenMode() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
|
|
|
|
// Even though IMAGE is a real MXP tag, it's SECURE - not allowed in OPEN
|
|
|
|
|
// mode
|
|
|
|
|
QString output = processAndCollectOutput(processor, "<IMAGE map.jpg>");
|
|
|
|
|
|
|
|
|
|
// Should be output as literal text
|
|
|
|
|
QVERIFY(output.contains(qsl("<IMAGE")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// ANSI escape sequences after < character (Issue #8899)
|
|
|
|
|
// When < is followed by ESC (0x1B), it should be rejected immediately
|
|
|
|
|
// and the ESC should be reprocessed as an ANSI sequence, not literal text.
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testAnsiEscapeAfterLessThanReprocessed() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
|
|
|
|
// Input: < followed by ESC (which starts an ANSI escape sequence)
|
|
|
|
|
// The ESC character (0x1B) is not a valid MXP tag name character
|
|
|
|
|
std::string input = {'<', '\x1B'};
|
|
|
|
|
QString output;
|
|
|
|
|
TMxpProcessingResult lastResult = HANDLER_FALL_THROUGH;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < input.size(); ++i) {
|
|
|
|
|
char ch = input[i];
|
|
|
|
|
lastResult = processor.processMxpInput(ch, true);
|
|
|
|
|
if (lastResult == HANDLER_INSERT_ENTITY_SYS ||
|
|
|
|
|
lastResult == HANDLER_INSERT_AND_REPROCESS) {
|
|
|
|
|
output += processor.getEntityValue();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The result should be HANDLER_INSERT_AND_REPROCESS so ESC gets reprocessed
|
|
|
|
|
QCOMPARE(lastResult, HANDLER_INSERT_AND_REPROCESS);
|
|
|
|
|
|
|
|
|
|
// Output should only contain '<', not the ESC (which should be reprocessed)
|
|
|
|
|
QCOMPARE(output, qsl("<"));
|
|
|
|
|
}
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
void testAnsiEscapeSequenceInTextPreserved() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// Simulate the problematic case from issue #8899:
|
|
|
|
|
// Text like "<\e[1m19" where \e is ESC (0x1B)
|
|
|
|
|
// After rejection, only '<' should be in entity value, and
|
|
|
|
|
// the ESC should be returned for reprocessing
|
|
|
|
|
std::string input = {'<', '\x1B', '[', '1', 'm', '1', '9'};
|
|
|
|
|
QString output;
|
|
|
|
|
std::vector<TMxpProcessingResult> results;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < input.size(); ++i) {
|
|
|
|
|
char ch = input[i];
|
|
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
results.push_back(result);
|
|
|
|
|
if (result == HANDLER_INSERT_ENTITY_SYS ||
|
|
|
|
|
result == HANDLER_INSERT_AND_REPROCESS) {
|
|
|
|
|
output += processor.getEntityValue();
|
|
|
|
|
}
|
2026-02-09 05:29:46 -05:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// When ESC is encountered after '<', we should get
|
|
|
|
|
// HANDLER_INSERT_AND_REPROCESS with entity value "<" (just the less-than,
|
|
|
|
|
// not the ESC)
|
|
|
|
|
QCOMPARE(output, qsl("<"));
|
|
|
|
|
|
|
|
|
|
// The ESC triggers HANDLER_INSERT_AND_REPROCESS (results[1])
|
|
|
|
|
QCOMPARE(results[1], HANDLER_INSERT_AND_REPROCESS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// User-defined elements
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testUserDefinedOpenElementAllowedInOpenMode() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
// First, register a user-defined OPEN element (normally done in SECURE
|
|
|
|
|
// mode)
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
processAndCollectOutput(processor,
|
|
|
|
|
"<!ELEMENT Auction '<FONT COLOR=red>' OPEN>");
|
2026-02-09 05:29:46 -05:00
|
|
|
|
|
|
|
|
// Switch back to OPEN mode
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_OPEN);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// The OPEN user-defined element should be recognized
|
|
|
|
|
QVERIFY(processor.isTagAllowedInCurrentMode(qsl("Auction")));
|
|
|
|
|
}
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
void testUserDefinedSecureElementBlockedInOpenMode() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
// Register a SECURE user-defined element (no OPEN keyword)
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
processAndCollectOutput(processor,
|
|
|
|
|
"<!ELEMENT ImmChan '<FONT COLOR=Red,Blink>'>");
|
|
|
|
|
|
|
|
|
|
// Switch back to OPEN mode
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_OPEN);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
|
|
|
|
// The SECURE user-defined element should NOT be allowed in OPEN mode
|
|
|
|
|
QVERIFY(!processor.isTagAllowedInCurrentMode(qsl("ImmChan")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Integration: MXP spec example should work in SECURE mode
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testMxpSpecExampleInSecureMode() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_LOCK_SECURE);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_SECURE);
|
|
|
|
|
|
|
|
|
|
// From the MXP spec example
|
|
|
|
|
processAndCollectOutput(
|
|
|
|
|
processor, "<!ELEMENT RName '<FONT COLOR=Red><B>' FLAG=\"RoomName\">");
|
|
|
|
|
processAndCollectOutput(processor, "<!ELEMENT Ex '<SEND>'>");
|
|
|
|
|
|
|
|
|
|
QVERIFY(processor.getMxpTagProcessor().getElementRegistry().containsElement(
|
|
|
|
|
qsl("RName")));
|
|
|
|
|
QVERIFY(processor.getMxpTagProcessor().getElementRegistry().containsElement(
|
|
|
|
|
qsl("Ex")));
|
|
|
|
|
|
|
|
|
|
// The user-defined elements should be recognized
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("RName")));
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("Ex")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Newline inside a partial tag rejects as literal text
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testNewlineInsideTagRejectsAsLiteral() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
|
|
|
|
|
// Feed '<B' then a newline - the tag cannot span lines
|
|
|
|
|
char ch = '<';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = 'B';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = '\n';
|
|
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(result, HANDLER_INSERT_AND_REPROCESS);
|
|
|
|
|
QCOMPARE(processor.getEntityValue(), qsl("<B"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testCarriageReturnInsideTagRejectsAsLiteral() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
|
|
|
|
|
char ch = '<';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = 'S';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = '\r';
|
|
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(result, HANDLER_INSERT_AND_REPROCESS);
|
|
|
|
|
QCOMPARE(processor.getEntityValue(), qsl("<S"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Invalid tag name characters cause immediate rejection
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testInvalidTagNameCharPercent() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
|
|
|
|
|
// Feed '<%' - percent is not a valid tag name character
|
|
|
|
|
char ch = '<';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = '%';
|
|
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(result, HANDLER_INSERT_AND_REPROCESS);
|
|
|
|
|
QCOMPARE(processor.getEntityValue(), qsl("<"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testInvalidTagNameCharCaret() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
|
|
|
|
|
char ch = '<';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = '^';
|
|
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(result, HANDLER_INSERT_AND_REPROCESS);
|
|
|
|
|
QCOMPARE(processor.getEntityValue(), qsl("<"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testInvalidTagNameCharAt() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
|
|
|
|
|
char ch = '<';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = '@';
|
|
|
|
|
TMxpProcessingResult result = processor.processMxpInput(ch, true);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(result, HANDLER_INSERT_AND_REPROCESS);
|
|
|
|
|
QCOMPARE(processor.getEntityValue(), qsl("<"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// abortCurrentTag() unit test
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testAbortCurrentTagReturnsLiteralText() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
|
|
|
|
|
// Build a partial tag '<SEND'
|
|
|
|
|
char ch = '<';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = 'S';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = 'E';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = 'N';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
ch = 'D';
|
|
|
|
|
processor.processMxpInput(ch, true);
|
|
|
|
|
|
|
|
|
|
QVERIFY(processor.getMxpTagBuilder().isInsideTag());
|
|
|
|
|
|
|
|
|
|
QString literal = processor.abortCurrentTag();
|
|
|
|
|
QCOMPARE(literal, qsl("<SEND"));
|
|
|
|
|
|
|
|
|
|
// After abort, the builder should be reset
|
|
|
|
|
QVERIFY(!processor.getMxpTagBuilder().isInsideTag());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// TEMP_SECURE mode: allows one tag then reverts to default
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testTempSecureModeAllowsOneTag() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
QVERIFY(!processor.isTagAllowedInCurrentMode(qsl("SEND")));
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_TEMP_SECURE);
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_TEMP_SECURE);
|
|
|
|
|
QVERIFY(processor.isTagAllowedInCurrentMode(qsl("SEND")));
|
|
|
|
|
|
|
|
|
|
processAndCollectOutput(processor, "<SEND href=\"test\">click</SEND>");
|
|
|
|
|
QVERIFY(client.mHrefs.size() > 0);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
QVERIFY(!processor.isTagAllowedInCurrentMode(qsl("SEND")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Closing tags for secure-only elements rejected in OPEN mode
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testClosingTagRejectedInOpenMode() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
QCOMPARE(processor.mode(), MXP_MODE_OPEN);
|
|
|
|
|
|
|
|
|
|
QString output = processAndCollectOutput(processor, "</SEND>");
|
|
|
|
|
QVERIFY(output.contains(qsl("</SEND>")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
// Underscore is valid in tag names (MXP/XML element names)
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
void testUnderscoreValidInTagName() {
|
|
|
|
|
TMxpStubClient client;
|
|
|
|
|
TMxpProcessor processor(&client);
|
|
|
|
|
processor.enable();
|
|
|
|
|
|
|
|
|
|
processor.setMode(MXP_MODE_CODE_SECURE);
|
|
|
|
|
|
|
|
|
|
// Register a user-defined element with underscore
|
|
|
|
|
processAndCollectOutput(processor,
|
|
|
|
|
"<!ELEMENT Enemy_123 '<FONT COLOR=Red>'>");
|
|
|
|
|
|
|
|
|
|
QVERIFY(processor.isRecognizedMxpTag(qsl("Enemy_123")));
|
|
|
|
|
|
|
|
|
|
// Feeding <Enemy_123> should not reject during tag name building
|
|
|
|
|
// (underscore must be accepted as a valid tag name character)
|
|
|
|
|
QString output = processAndCollectOutput(processor, "<Enemy_123>");
|
2026-02-09 05:29:46 -05:00
|
|
|
|
2026-03-21 13:45:49 -04:00
|
|
|
// The tag should be processed, not rejected as literal text
|
|
|
|
|
QVERIFY(output.isEmpty());
|
2026-02-09 05:29:46 -05:00
|
|
|
}
|
Fix: Block dangerous MXP tags in open mode (#8376)
#### Brief overview of PR changes/additions
This PR fixes a security vulnerability where dangerous MXP tags like
`<SEND>` were being processed in open mode, allowing malicious users to
inject commands through chat channels. The fix ensures that only safe
formatting tags are allowed in open mode, while secure and locked modes
continue to work as intended.
#### Motivation for adding to Mudlet
The MXP specification clearly defines three security modes:
- **Open mode** (default): Only basic formatting tags should be allowed
- **Secure mode**: All tags are allowed when explicitly enabled by the
server
- **Locked mode**: No tags are processed
Mudlet was treating open mode the same as secure mode, creating a
security risk where players could send clickable links with commands to
other players through in-game chat that uses open mode.
#### Other info (issues closed, discussion etc)
Closes #8150
**Changes made:**
- Added `TMxpMudlet::startTagReceived()` to validate tags against
current MXP mode
- Open mode now only allows safe formatting tags (B, I, U, COLOR, FONT,
BR, etc.)
- Secure mode continues to allow all tags including SEND, A, VAR, SOUND,
MUSIC
- Locked mode blocks all tags as expected
- Added comprehensive test suite with 5 test cases covering all modes
- All 13 existing tests still pass
**Testing:**
✅ New test: `TMxpModeSecurityTest` with full coverage of mode
transitions
✅ All existing MXP tests continue to pass
✅ Manual testing with live MUD connection confirmed the fix works
2025-10-24 07:28:47 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QTEST_MAIN(TMxpModeSecurityTest)
|
|
|
|
|
#include "TMxpModeSecurityTest.moc"
|