Skip to content

fix(cnr): mask the session id in the secured POST body - #327

Merged
KaiSchwarz-cnic merged 1 commit into
masterfrom
RSRMID-2994/mask-session-id-in-debug-body
Aug 26, 2026
Merged

fix(cnr): mask the session id in the secured POST body#327
KaiSchwarz-cnic merged 1 commit into
masterfrom
RSRMID-2994/mask-session-id-in-debug-body

Conversation

@KaiSchwarz-cnic

@KaiSchwarz-cnic KaiSchwarz-cnic commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Found while setting up P8 of RSRMID-2994, and it doubles as that ticket's release trigger — a fix touching src/ is what makes semantic-release cut a version, which is what P8 needs in order to verify the release push now goes out over the deploy key.

The change stands on its own merits, though. It is not a contrived commit.

The defect

CNR\SocketConfig::getPOSTDataParams() masked s_pw but wrote s_sessionid in clear:

if (strlen($this->password) !== 0) {
    $params[$this->parameters["password"]] = $maskSecrets ? CommandRedactor::MASK : $this->password;
}
if (strlen($this->session) !== 0) {
    $params[$this->parameters["session"]] = $this->session;   // <-- not masked
}

$maskSecrets = true is the body built for debug logging. So a debug record could carry a working credential.

Why it matters more than it looks

A session id is not a lesser credential than the password but an alternative to it. setSession() says so itself, and enforces it:

// Always clears the stored password — a session and a password are alternative
// credentials on the wire and the newer one is authoritative.
public function setSession(string $session = ""): static
{
    $this->session = $session;
    $this->password = "";

So the gap opened precisely where it mattered. On the persistent-session path there is no password left to mask, and what the masked body contained was s_login + s_sessionid — everything needed to authenticate. Masking the password while logging the session defeated the masking on the one path where masking was the only thing left.

Scope

IBS/MONIKER are unaffected — they have no session parameter.

Tests

testGetPostDataSecuredMasksSession() asserts with a login present, which is the shape the fix is about, and pins the absence of the raw value rather than only the expected string.

No existing test changes:

  • the other masked-path assertions (ClientTest::testGetPostDataSecured, testGetPostDataSecuredMasksAuth, CNR\LoggerTest) set credentials rather than a session, so none of them covered this
  • the three tests that do assert an unmasked s_sessionid call getPOSTData() on the default unmasked path — that is the real request body and still has to carry the real value

I could not run the suite locally: the workspace devcontainer installs no language runtimes by design, so there is no PHP here. Static checks I did make: CommandRedactor was already imported, the new line is shorter than the s_pw line beside it, and both sides of the assertion go through http_build_query.

Note

If you would rather this had its own issue rather than riding on RSRMID-2994's branch prefix, say so and I will move it.

🤖 Generated with Claude Code

getPOSTDataParams() masked s_pw but wrote s_sessionid in clear, so the body
built for debug logging carried a working credential.

A session id is not a lesser credential than the password but an alternative to
it: setSession() clears the password, because the newer of the two is
authoritative on the wire. The gap therefore opened exactly where it mattered —
on the persistent-session path there is no password left to mask, and what the
debug record contained was login plus session, everything needed to
authenticate.

IBS/MONIKER are unaffected; they have no session parameter.

The new test asserts with a login present, which is the shape the fix is about,
and pins the absence of the raw value rather than only the expected string. The
existing masked-path tests set credentials rather than a session, so none of
them covered this and none of them change. The three tests that do assert an
unmasked s_sessionid call getPOSTData() on the default unmasked path, which is
the request body and still has to carry the real value.
@KaiSchwarz-cnic
KaiSchwarz-cnic requested a review from a team as a code owner August 26, 2026 14:34
@KaiSchwarz-cnic
KaiSchwarz-cnic merged commit c926f23 into master Aug 26, 2026
17 checks passed
@KaiSchwarz-cnic
KaiSchwarz-cnic deleted the RSRMID-2994/mask-session-id-in-debug-body branch August 26, 2026 14:35
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.90%. Comparing base (51a0475) to head (cb0ef62).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##             master     #327   +/-   ##
=========================================
  Coverage     99.90%   99.90%           
- Complexity      469      470    +1     
=========================================
  Files            32       32           
  Lines          1065     1065           
=========================================
  Hits           1064     1064           
  Misses            1        1           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@KaiSchwarz-cnic

Copy link
Copy Markdown
Collaborator Author

🎉 This PR is included in version 33.0.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant