|
| 1 | +#include "IssueReport.h" |
| 2 | +#include "AsyncLogWriter.h" // redactPii — GHSA-ccrg-j8cp-qhc4 |
| 3 | + |
| 4 | +namespace AetherSDR { |
| 5 | + |
| 6 | +QString buildIssueReport(const SupportBundle::SystemInfo& sys, |
| 7 | + const SupportBundle::RadioInfo& radio, |
| 8 | + const QString& logTail) |
| 9 | +{ |
| 10 | + QString body; |
| 11 | + |
| 12 | + // State the concrete privacy guarantees without implying that arbitrary |
| 13 | + // user-authored log text can be proven free of every kind of PII. |
| 14 | + body += "<!-- Pre-filled by AetherSDR (Help \xE2\x86\x92 File an Issue). " |
| 15 | + "Known sensitive fields are redacted: authentication tokens, " |
| 16 | + "network/radio identifiers, GPS coordinates, and SmartLink " |
| 17 | + "account names. Please review the report, then " |
| 18 | + "replace the italic placeholders with your own words. -->\n\n"; |
| 19 | + |
| 20 | + body += "### What happened?\n"; |
| 21 | + body += "_Describe what went wrong (e.g. \"the waterfall freezes after " |
| 22 | + "about 10 minutes\")._\n\n"; |
| 23 | + |
| 24 | + body += "### What did you expect?\n"; |
| 25 | + body += "_Describe the expected behavior._\n\n"; |
| 26 | + |
| 27 | + body += "### Steps to reproduce\n"; |
| 28 | + body += "1. _First step_\n"; |
| 29 | + body += "2. _\xE2\x80\xA6_\n\n"; |
| 30 | + |
| 31 | + body += "### Radio model & firmware\n"; |
| 32 | + if (radio.connected) { |
| 33 | + // Serial/IP are PII — redact to the same form used in logs so support |
| 34 | + // can correlate without seeing cleartext. Callsign is FCC public |
| 35 | + // record; model/firmware/protocol are not sensitive. |
| 36 | + body += QString("- Model: %1\n").arg(radio.model); |
| 37 | + body += QString("- Firmware: %1\n").arg(radio.firmware); |
| 38 | + if (!radio.protocolVersion.isEmpty()) { |
| 39 | + body += QString("- Protocol: %1\n").arg(radio.protocolVersion); |
| 40 | + } |
| 41 | + if (!radio.callsign.isEmpty()) { |
| 42 | + body += QString("- Callsign: %1\n").arg(radio.callsign); |
| 43 | + } |
| 44 | + if (!radio.serial.isEmpty()) { |
| 45 | + body += QString("- Serial: %1\n").arg(redactPii(radio.serial)); |
| 46 | + } |
| 47 | + body += "- Connection: connected\n\n"; |
| 48 | + } else { |
| 49 | + body += "- Connection: not connected\n\n"; |
| 50 | + } |
| 51 | + |
| 52 | + body += "### OS & version\n"; |
| 53 | + body += QString("- AetherSDR: %1\n").arg(sys.aetherVersion); |
| 54 | + body += QString("- Qt: %1\n").arg(sys.qtVersion); |
| 55 | + body += QString("- OS: %1 (kernel %2)\n").arg(sys.osName, sys.kernelVersion); |
| 56 | + body += QString("- Arch: %1\n").arg(sys.cpuArch); |
| 57 | + body += QString("- Build: %1\n\n").arg(sys.buildDate); |
| 58 | + |
| 59 | + body += "### Recent log\n"; |
| 60 | + if (logTail.isEmpty()) { |
| 61 | + body += "_(Recent log omitted from this link to keep it short \xE2\x80\x94 " |
| 62 | + "see the attached support bundle, or paste it from your " |
| 63 | + "clipboard.)_\n"; |
| 64 | + } else { |
| 65 | + body += QString("Last %1 lines, secret-redacted:\n\n") |
| 66 | + .arg(kIssueLogTailLines); |
| 67 | + // Redact at the render boundary: even though the on-disk tail is |
| 68 | + // already scrubbed by AsyncLogWriter::formatLine, re-run redactPii so |
| 69 | + // the guarantee holds regardless of the tail's provenance. |
| 70 | + body += "```text\n"; |
| 71 | + body += redactPii(logTail).trimmed(); |
| 72 | + body += "\n```\n"; |
| 73 | + } |
| 74 | + |
| 75 | + return body; |
| 76 | +} |
| 77 | + |
| 78 | +} // namespace AetherSDR |
0 commit comments