Skip to content

flightcheck: autolink bare URLs in report remediation - #208

Open
daeunJe0ng wants to merge 6 commits into
microsoft:mainfrom
daeunJe0ng:dawnjeong/flightcheck-report-autolink-urls
Open

flightcheck: autolink bare URLs in report remediation#208
daeunJe0ng wants to merge 6 commits into
microsoft:mainfrom
daeunJe0ng:dawnjeong/flightcheck-report-autolink-urls

Conversation

@daeunJe0ng

@daeunJe0ng daeunJe0ng commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

FlightCheck report remediation text supports two link forms: markdown [label](url) and bare https://… 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 bare make.powerautomate.com/environments/<env>/connections URL 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 (in runner.py) now runs a second pass, _autolink_bare_urls, after the markdown pass.
  • The bare-URL pass steps over anchors created by the markdown pass, so [label](url) is never double-wrapped.
  • Trailing sentence punctuation (.,;:!?) and an unbalanced closing paren are trimmed off the link target, so see https://aka.ms/x. keeps the period outside the href. 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 (&amp;) is not stripped, so query strings stay whole.
  • A trailing escaped delimiter entity (&lt;, &gt;, &quot;) is peeled back into the text. An angle-bracket-wrapped URL <https://x> escapes to &lt;https://x&gt;; without this, the trailing &gt; was pulled into the href. 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 &amp; is still kept whole.
  • _multiline_html and 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 &amp; 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_html linkifies and keeps line breaks, plus end-to-end check cards render clickable links.
  • Full FlightCheck suite (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

  • Change touches shared report rendering, used by every check. Mitigated by the full-suite run and by keeping the markdown behavior identical (verified by the no-double-wrap test).
  • Assumes remediation URLs are http/https only. Other schemes are not autolinked, which is intentional for a security-reviewed report surface.
  • Not in scope: _mask_sensitive can 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.

daeunJe0ng and others added 2 commits July 21, 2026 14:15
…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
daeunJe0ng and others added 2 commits July 28, 2026 13:54
…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 (&amp;).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5d2cf751-a830-4bbb-bea0-f9d94f97cbee
daeunJe0ng added a commit to daeunJe0ng/Employee-Self-Service-Agent-Developer-Kit that referenced this pull request Jul 28, 2026
…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
@apurvabanka

Copy link
Copy Markdown
Contributor

No code blockers. The only thing gating merge is the required approving review (REVIEW_REQUIRED / BLOCKED) — everything else is ready.

A few non-blocking notes / gaps worth acknowledging:

  1. GUID masking inside deep-link URLs (please confirm): _mask_sensitive can rewrite a GUID embedded in a link target, which alters the href. The PR calls this out as out of scope, but the WD-CONN remediation URL this change is meant to fix contains an environment GUID — so a masked link could point somewhere invalid. Can we confirm that's acceptable for the live case, or is it worth a follow-up?

  2. Host validation — bare URLs are now clickable but not validated (typo'd/hallucinated domains). Already tracked as flightcheck: offline https link gate + run FlightCheck suite in CI #209, just noting it here.

  3. Minor untested edge case: an angle-bracket-wrapped URL like <https://x> escapes to &lt;https://x&gt;, and the regex would pull &gt; into the href. Unlikely in remediation text and low risk — flagging for awareness only.

@daeunJe0ng

…(fix wrapped-URL edge case)

An angle-bracket-wrapped bare URL "<https://x>" is HTML-escaped to
"&lt;https://x&gt;". The bare-URL regex pulled the trailing "&gt;" 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
&lt;/&gt;/&quot; entity back into the surrounding text while keeping a
real query-string &amp;. Addresses PR microsoft#208 review note (apurvabanka).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 46443839-5906-41f5-9d28-b5345fa96673
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants