flightcheck: autolink bare URLs in report remediation - #208
Conversation
…ble links on manual rows) Report remediation text supported two link forms but only linkified markdown [label](url). Bare https:// URLs were escaped to plain text, so they rendered but were not clickable - worst on MANUAL / NotConfigured rows where the remediation URL is the operator's only path to the fix. _md_links_to_html now also autolinks bare URLs (stepping over anchors it just created so markdown links are not double-wrapped), and trims trailing sentence punctuation. _multiline_html and the manual checklist renderer inherit the fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8fb19ec0-cad9-41c2-8064-2208b3844ede
…d paren-peel and entity split) The bare-URL regex excluded ')', so the unbalanced-paren peel branch was dead code and URLs containing ')' (e.g. .../Foo_(bar)) truncated at the first paren. Allow ')' in the match and resolve balance in the replacer, merging the punctuation and paren peels into one loop that also refuses to strip a ';' terminating an HTML entity (&). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5d2cf751-a830-4bbb-bea0-f9d94f97cbee
…self-referential host allowlist) The prior gate required every host in FlightCheck source to be registered in a curated allowlist. That was self-referential (the author who writes a typo also edits the allowlist), high-maintenance (each new legit host, even docstring-only ones, was a red build), and it flagged legitimate hosts such as schema.management.azure.com and www.microsoft.com that are not clickable-typo risks. Replace it with one durable, fail-closed rule: every fetchable URL must be https, unless its host is a known namespace/identifier URI (SOAP/SAML namespaces) that is legitimately http. New hosts are covered by default, no registry upkeep. Rename url_registry.py -> url_hygiene_rules.py to reflect the rule-based purpose; add a structural host-shape check. Broaden CI: replace the single-file url-hygiene job with a job that runs the whole offline FlightCheck pytest suite (1003 tests), which previously ran in CI at all. Link clickability is handled by the report renderer (PR microsoft#208); live-path/redirect checking is a separate networked concern kept out of this deterministic job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 54c58357-b127-4686-a331-df0344225da8
|
No code blockers. The only thing gating merge is the required approving review ( A few non-blocking notes / gaps worth acknowledging:
|
…(fix wrapped-URL edge case) An angle-bracket-wrapped bare URL "<https://x>" is HTML-escaped to "<https://x>". The bare-URL regex pulled the trailing ">" into the href because ';' looked like an HTML-entity tail to keep. An unescaped '<', '>' or '"' is never a valid URL char, so peel a trailing </>/" entity back into the surrounding text while keeping a real query-string &. Addresses PR microsoft#208 review note (apurvabanka). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 46443839-5906-41f5-9d28-b5345fa96673
Summary
FlightCheck report
remediationtext supports two link forms: markdown[label](url)and barehttps://…URLs. Only the markdown form was linkified. Bare URLs were HTML-escaped and rendered as plain text, so they showed up but were not clickable. Because different checks author URLs differently, operators saw links that worked on some rows and not others.The concrete live case is the WD-CONN connection-health check (
checks/workday.py), whose WARNING and FAILED remediations embed a baremake.powerautomate.com/environments/<env>/connectionsURL built via f-string. That URL is the operator's path to fix a broken Workday connection, and today it renders as non-clickable plain text.This makes both forms render as clickable anchors.
What changed
_md_links_to_html(inrunner.py) now runs a second pass,_autolink_bare_urls, after the markdown pass.[label](url)is never double-wrapped..,;:!?) and an unbalanced closing paren are trimmed off the link target, sosee https://aka.ms/x.keeps the period outside thehref. Balanced parens are preserved, so.../Foo_(bar)stays intact; only an unbalanced trailing)(as in(see https://x)) is pushed back into the text. A;that terminates an HTML entity (&) is not stripped, so query strings stay whole.<,>,") is peeled back into the text. An angle-bracket-wrapped URL<https://x>escapes to<https://x>; without this, the trailing>was pulled into thehref. An unescaped<,>or"is never a valid URL character, so the wrapping bracket/quote stays as text and only the URL is anchored. A real query-string&is still kept whole._multiline_htmland the manual checklist renderer call_md_links_to_html, so they inherit the fix with no separate change.Design rationale
The fix lives in the renderer, not in each check. That fixes every existing report immediately and means check authors cannot reintroduce a non-clickable link by pasting a raw URL. The alternative (a lint rule forcing markdown links) would leave current reports broken and add an ongoing authoring burden. The renderer is the single choke point every remediation string already passes through, so it is the correct layer.
Testing
tests/flightcheck/test_runner_link_rendering.py(15 tests): bare URL becomes an anchor, trailing punctuation and unbalanced parens excluded, balanced parens preserved, query-string&stays HTML-safe and a trailing&is not split, angle-bracket-wrapped and quote-wrapped URLs exclude the escaped delimiter, markdown links still work and are not double-wrapped, mixed markdown + bare, plain text untouched except escaping,_multiline_htmllinkifies and keeps line breaks, plus end-to-end check cards render clickable links.tests/flightcheck): 1011 passed.Scope note
This PR makes bare URLs clickable. It does not check that a link's host is real/intended (typo'd or hallucinated domains) — that host-level hygiene gate is #209.
Risks and assumptions
http/httpsonly. Other schemes are not autolinked, which is intentional for a security-reviewed report surface._mask_sensitivecan rewrite a GUID that appears inside a deep-link URL, which alters the link target. That is a separate concern from clickability and is left untouched here.