Sensitive content stays on the instance

What a red-log ticket publishes, and the switch that publishes nothing. The platform-wide rule — what a log line may carry at all — is core's Sensitive Content in Logs (get Doc/Architecture/SensitiveContentInLogs). This page is the ticketing half: what the incident filer copies from the logs into GitHub.

What was wrong

An enterprise client's instance holds health records in node content. A call site that formats a value into its message — Update failed for record {name} — puts that value into the formatted line, and the console formatter Loki ships has no message template to fall back to: the TEXT is all there is. The filer then copied that text into three public places:

Where What it carried
the issue body the samples verbatim (Recent log lines), the per-shape ledger's normalized excerpt
every "still happening" comment, and the retirement note the last three samples, the shape ledger
the site-fold note a Detail row — the normalized exception message

Plugins#4444 masked mesh PATHS in all of them, and that is the limit of masking: a path has a shape, a person's name or a diagnosis is just a word. LogLineParser.Normalize keeps such a word too — it masks guids, numbers, quoted literals and paths, nothing that reads as prose — so the normalized text is not a template either.

What is published now

Every text the filer posts — LogIncidentFiler.Body, the recurrence comment, RetirementComment, SiteFoldNote — carries:

The incident node in the Admin partition keeps the samples verbatim — that is on the instance that recorded them, under its own access control.

The triage draft is not published either — by default

The triage agent reads the incident node, raw samples included: that is how it finds the concrete case, and it cannot do its job on the template-level fields alone. So whatever it writes can echo a logged value, and telling it not to (its instructions do) is a request, not a guarantee. The input cannot be cut without blinding it, so the cut is at the OUTPUT:

Key Default Effect
LogWatch:PublishTriageDraft false false ⇒ none of the agent's TEXT is published: the issue title is BUILT from the exception's short type and the fault site (InvalidOperationException in MeshDataSource.Apply — the top frame's Type.Method, else the category), the body opens with a line saying the write-up stays on the incident node, the routing row says an override's reason is on the node, and the fold note carries neither the agent's title nor its write-up. The draft's labels and severity still apply, and the write-up stays on the incident node in the Admin partition. true ⇒ the previous behaviour, path-masked: for an instance whose logs carry only its own platform's text.

Pinned by PublishedIncidentTextCarriesNoLogTextTest.ATriageDraftThatEchoesASample_IsNotPublished_ByDefault: a marker in a sampled line, echoed into the draft's title, body and routing reason, reaches neither the issue nor the fold note.

The switch: LogWatch:FileIssues

Key Default Effect
LogWatch:FileIssues true false ⇒ incidents are still ingested and recorded on this instance, and nothing reaches GitHub: no issue, no recurrence comment, no reopen, no retirement note, whatever GitHub App credentials the instance holds. No triage is asked for either — a draft for a ticket that is never opened is a model round spent on nothing, and a Failed incident would otherwise be re-triaged on every report. A Triage, File or Comment request already PERSISTED on a node (asked before the switch, or by an admin) is consumed at the claim and granted nothing, with the reason on the incident; the comment-debt clock and the stranded-triage reconcile ask for nothing. An admin's Suppress is still honoured.

The other ways the egress is off, for completeness: LogWatch:IngestToken unset (the ingest endpoint is not mapped at all), no GitHub App (GitHub:App), or a watcher with no LogWatcher:PortalUrl. Each is an absence; FileIssues=false is the one that says so on purpose.

The in-cluster watcher sends raw samples to its portal

🚨 The watcher posts its samples to LogWatcher:PortalUrl. Raw lines (up to LogWatcher:MaxSamplesPerReport × LogWatcher:MaxSampleLength, default 5 × 2000 characters) travel to whichever portal that names, and are stored there on the incident node. An instance whose logs can carry sensitive content points its watcher at ITS OWN portal — never at a shared control instance — and the watcher's LogWatcher:Namespaces lists only that instance's namespace, so no other deployment's lines are read into it either. None of LogWatch:* above governs this hop: it happens before the portal sees anything.

Pinned by

PublishedIncidentTextCarriesNoLogTextTest (MeshWeaver.Observability.Test): the issue body, the recurrence comment, the site-fold note and the retirement note carry none of a logged value while carrying the category, exception type, top frame, count and pod; FileIssues=false refuses every GitHub write with credentials present, asks for no triage or comment, consumes a persisted Triage/File/Comment request at the claim, keeps the comment-debt clock silent, and still grants a Suppress; a marker in a sampled line echoed into the triage draft reaches neither the issue nor the fold note.