Skip to content

Redact raw URCs when dispatcher error handling is triggered #282

Description

@Justinabox

Summary

URCDispatcher.dispatch() redacts normal diagnostic output through _log_safe_urc(), but its broad exception handler logs the original modem line verbatim. A fault while dispatching a direct +CMT SMS header therefore exposes the sender field in the error log.

This is a distinct exception-path gap from draft PR #249, which redacts malformed +CDSI normal-path logging but does not change URCDispatcher.dispatch()'s exception handler.

Affected code

  • callstack/protocol/urc.py:68-72
    • Normal debug log: logger.debug("URC: %s", self._log_safe_urc(line))
    • Exception log: logger.exception("Error dispatching URC '%s': %s", line, exc)

Reproduction

From a clean checkout, with the normal test dependencies available:

uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite python -c $'import asyncio\nfrom callstack.protocol.urc import URCDispatcher\nclass BrokenBus:\n    async def emit(self, event):\n        raise RuntimeError("forced dispatch fault")\nasync def main():\n    dispatcher = URCDispatcher(BrokenBus())\n    await dispatcher.dispatch("+CMT: \\\"privacy-sentinel-private-sender\\\",\\\",\\\",\\\"", "test")\nasyncio.run(main())' 2>&1

Actual output begins with:

Error dispatching URC '+CMT: "privacy-sentinel-private-sender",",","': forced dispatch fault

The sentinel from the SMS sender field is present in the log despite the normal +CMT debug path being redacted.

Expected behavior

No caller identifier, SMS header field, USSD content, registration identifiers, or other raw modem URC payload should be emitted by the exception path. The exception class/message may remain useful only if it is independently known to be safe.

Suggested fix direction

Pass the line through _log_safe_urc() in the exception logger (or avoid printing the line altogether), and add defensive redaction for any relevant error-path log sites. Keep the full traceback for diagnostics without interpolating the raw URC.

Acceptance criteria

  • A forced event-emission failure while dispatching a +CMT: URC logs a redacted summary (for example +CMT:<redacted>) and never logs a sentinel sender value or body.
  • Equivalent forced-dispatch failures for +CUSD:, +CDSI:, and verbose registration URCs do not expose their raw payloads.
  • Successful dispatch behavior and existing typed events remain unchanged.
  • The regression test uses a deliberately non-PII sentinel and caplog assertions.
  • The solution does not weaken the existing privacy behavior covered by draft PR fix: redact malformed delivery report URCs #249.

Verification gates

git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions