Repository navigation
AB#3757505 Improve vulnerability triage fixability gates and reporting, Fixes AB#3757505 - #469
Conversation
…#3654306 Adds a skill that triages MSRC/ITD security vulnerabilities filed against Android Authenticator & Broker and produces evidence-based, agree/rebut severity classifications plus a shareable WBR HTML report. - SKILL.md: parallel codebase-researcher workflow, severity rubric, mandatory defense-in-depth sweep with verification-boundary discipline (only confirm what we own; disclaim downstream/server checks we cannot verify). - references/: severity rubric, report template, ITD/FireWatch manual-intake guide, and an auto-glossary source. - scripts/: discover_findings, scaffold_itd, transcribe_finding, rollup, and build_research_pages (self-contained HTML evidence subpages + audit trail). - Public-repo safety: PUBLIC-skill banner forbids sensitive data (telemetry sampling/coverage, internal security-control logic, PII/tenant data); opaque routing IDs are allowed. Investigation outputs live OUT of the repo in the private VULN_TRIAGE_WORKSPACE (default ~/vuln-triage-workspace). - Register in copilot-instructions skills table; add update-skills override; add .github/local-context/ as a defensive .gitignore entry.
…urface guidance, Fixes AB#3654306 - codebase-researcher: delegate Authenticator-app research to the Authenticator skill; add Scenario-Scoped Defect Surfaces guidance (filter to 3-6 entries whose failure mode matches the observed symptom). - incident-investigator: add Zero-Row Guard for android_spans CID lookups (reference-trace + same-tenant/same-window cross-check before treating absence as evidence), broker telemetry-signature table, a richer standalone-doc output format, and a pointer to the new vuln-triage-reporter skill for MSRC/ITD IcMs. - kusto-analyst: document correlation_id_v2 as the primary correlation column (correlation_id is effectively empty) and add head-sampling caveats for absence-as-evidence claims, kept generic for public-repo safety.
…epo safety, Fixes AB#3654306 Make MSRC/ITD triage more thorough and certain so on-call can confidently delegate lower-severity findings and produce dispatch-ready fixes for the rest. - Two-pass verification: mandatory adversarial Challenger pass that tries to break Pass 1's verdict, yielding an explicit Confidence (High/Medium/Low). Adds the two-pass model, Step 3.5, timing/ETA + hang-detection guidance to SKILL.md. - Severity rubric: map analytical tiers to team IcM Sev (Sev2/2.5/3/4) with response urgency, a conservative Sev2.5+ gate (High confidence + proven reachable + no safeguard + not boundary-dependent), and an evolving calibration log. - Intern vs. engineer routing: simple severity cutoff (Low/Moderate -> Intern-eligible; Important/Critical -> Engineer-owned) plus a dispatch-ready remediation-spec.md template for kept findings. - Verification gaps: required report section surfacing what static analysis could NOT test (runtime/server/downstream conditions), why, and what the user must confirm. - Public-repo safety: new scripts/safety_check.py scanner (sampling/coverage, security-control logic, private file:line, internal hosts, PII, GUIDs, long IcM numbers); mandated before any commit via banner + Non-Negotiables. - report-template.md gains Confidence / IcM Severity / Assignment / External-validation fields; glossary terms added.
…up columns, Fixes AB#3654306 - build_research_pages.py: add a colorful stat-tile band (Our Severity, IcM Severity, Confidence, Verdict, Passes, External-Validation-Needed, Assignment); add styled callouts for Adversarial Verification, Remediation, and Verification Gaps sections; fix literal underscores not rendering as italics (placeholder-protect code spans so identifiers like app_link are never mangled); fix double-numbered ordered lists. - rollup.py: add confidence, IcM-Sev, and assignment columns; split output into an Intern Queue vs. Engineer-owned section; add IcM-Sev breakdown with a Sev2.5+ warning; fix a UTF-8 stdout crash on redirect.
… AB#3654306 Split each finding into two coordinated artifacts so the report is easy for humans to read while an AI agent can act on it to open a PR. - build_agent_spec.py (new): generates <slug>.agent.md from the finding README valid YAML front-matter (finding_id, our_tier, icm_sev, confidence, assignment, target_repos, files_to_change, external_validation_needed, status, blocked_on) plus a Dispatch Block (problem statement, acceptance criteria = the negative test, do-not-proceed-until gating from the verification gaps, and constraints). Consumed by the Copilot coding agent / pbi-creator without scraping prose. - build_research_pages.py: curate the human HTML a Bottom-line TL;DR callout, a Decisions-Needed box, the heavy Searches-Run audit auto-collapsed into <details>, and a header button linking the machine-readable .agent.md (--agent-dir). - report-template.md: add Bottom line field + Decisions Needed section. - agent-spec-template.md (new): document the dual-output model + front-matter schema. - SKILL.md Step 5: rewrite for the two-artifact (human report + agent spec) model.
…B#3654306 Capture reusable tier->Sev calibration points discovered while triaging the remaining 8 [ITD] findings (per the skill's "capture learnings" non-negotiable): - The "embedded WebView is non-default" mitigation is a TRAP for broker findings: the broker forces AuthorizationAgent.WEBVIEW by default, so auth-WebView sinks ARE on the default broker path (caught two wrong down-classifications). - A completable, zero-click CSRF still caps at Sev3 when the injected artifact is attacker-owned and the exfiltrated data is non-weaponizable. - A log-leak of durable secrets holds at Sev3 only after the release-suppression control is PROVEN absent (BuildConfig.DEBUG gating, proguard -assumenosideeffects, scrub default) - never assumed. - A mislabeled finding (e.g. "TLSBypass" that isn't) needs the category corrected in the report + ITD title, not just the severity.
…ster report, Fixes AB#3654306 - Component/Repo tile: each finding page now shows a color-coded tile for the canonical repo (Authenticator / Common / Broker / MSAL / ADAL), derived from the finding's Component field. - Intern-eligibility cutoff tightened: Intern-eligible ONLY when IcM Sev4 AND the component is the Authenticator app; everything else (any Sev3+, or Broker/Common/MSAL) is Engineer-owned. Updated rollup.py + build_research_pages.py to derive this consistently, plus SKILL.md / severity-rubric.md / report-template.md. - build_master_report.py (new): generates a self-contained wbr-security-report.html (summary cards, severity legend, master table linking each finding's research subpage + agent spec) so the run folder no longer reuses a prior run's overview. The research subpages' back-link resolves to it. - Ignore scripts/__pycache__ (the master report imports build_research_pages).
…er, Fixes AB#3654306 - Intern cutoff is now tier-based: Intern-eligible when our tier is Moderate or lower AND the component is the Authenticator app (MSRC or ITD); Important+ or any Broker/Common/MSAL component is Engineer-owned. (Was Sev4-only.) - Master table (build_master_report.py): drop the IcM Sev column (the our-tier IcM Sev mapping stays in the legend); add a Tag column (MSRC vs ITD); compact the Verdict (AGREE/DOWN/UP) and Owner (E/I) columns with bottom legends explaining both; drop the per-row agent-spec "Spec" link (the spec stays on each finding's evidence page) Evidence column is just Research now. - Evidence-page header button is now descriptive: "Fix this with an AI agent open the dispatch spec (feed it to a coding agent to implement the fix)". - Updated rollup.py + build_research_pages.py tile to derive the new cutoff, plus SKILL.md / severity-rubric.md / report-template.md.
…umn, shift workflow, UTF-8 rollup, Fixes AB#3654306 - Master report: 'Needs external validation' count tile + per-row diamond ext badge (orthogonal to owner/action), Action column (Keep & fix / Delegate), generated-timestamp + shift framing (--shift/--owner), Exports links to rollup+CSV, split eng-days/confidence tile - rollup.py: --out writes UTF-8 directly (fixes PowerShell '>' mojibake), Action column, optional external_validation column - Research pages: 'On this page' TOC with section anchors, assignment-reason in the Assignment tile sub - SKILL.md: on-call entry-mode options (triage-one / sweep-window / finalize / re-run-one), Wed->Wed append/manifest model, documented new flags - severity-rubric.md: reporting/tooling calibration learnings (ext-validation orthogonality, UTF-8 trap, shift append model) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ignment, concise clickable tiles, Fixes AB#3654306 - Master report + rollup: remove Action column (Owner E/I already encodes keep-&-fix vs delegate); the two rollup sections (Engineer-owned / Intern Queue) are themselves the split - Master 'Ours' cell: split the base tier chip from any trailing note (e.g. 'and recategorize') into a muted ↻ annotation so chip widths stay aligned - Research-page tiles: concise values (External Validation = Yes/No, no truncated prose; Assignment = keep & fix / delegate, no cutoff reasoning); tiles now jump-link (↓) to the matching detail section (classification / adversarial / gaps / remediation) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ter, Fixes AB#3654306 - Non-Negotiable #14: NEVER create ADO work items without explicit user approval (always propose + confirm parent/area/iteration/assignee first) - SKILL.md Step 6 (Create PBIs, opt-in): default parent = team's standing 'Keep the Lights On' (KTLO) feature on the Auth Client - Android board (look up at creation; Summer-2026 intern feature was one-time); inherit area/iteration from parent; one PBI per shared fix; description = report distilled + reports/spec placeholder; MCP-or-REST tooling notes + idempotency - SKILL.md Step 7 (Weekly status report): concise email-ready table for manager tracking (IcM, bug, severity, owner, status, work item, updated) — no research detail - NEW references/status-report-template.md: columns, ADO-state->status mapping, layout, cadence - NEW scripts/build_status_report.py: renders the Outlook-safe HTML table from classifications.csv + optional IcM->work-item map + optional live ADO state via bearer token; UTF-8 --out - Fixed stale 'Action column' reference in the rollup bullet (column was removed earlier) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…to token), Fixes AB#3654306 - build_status_report.py: --auto-token acquires the ADO token via az (no token file); auto-discovers work-item-map.json beside the CSV so --map is optional. Weekly refresh is now a single command. - safety_check.py: allow-list the well-known PUBLIC Azure DevOps resource GUID (tenant-independent, in Microsoft docs) so it doesn't false-positive as a tenant/finding GUID - status-report-template.md + SKILL.md Step 7: document the persisted-map + one-command cadence; map lives in the private workspace (pairs IcM ids with work items), never in the repo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…assignee), Fixes AB#3654306 Owner (E/I) was redundant with the linked work item's assignee. Report now: IcM, Bug, Sev, Status, Work Item, Updated. Removed the unused rollup import and updated the template + SKILL Step 7. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, Fixes AB#3654306 New 'Requirements — verify BEFORE any work' section + Non-Negotiable #0: do NOT begin until (1) full android-complete checkout WITH submodules on the MAIN checkout (not a worktree) — authenticator/PhoneFactor + broker/AADAuthenticator must be populated, else a missing submodule silently turns a real sink into a false 'no sink'; (2) IcM MCP + codebase-researcher available (ADO MCP / az optional with REST fallback; FireWatch/Security MCP is N/A — manual ITD intake); (3) writable private workspace. Includes a copy-paste PowerShell verification block. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…x) to vuln-triage-reporter, Fixes AB#3654306 New rule: a finding whose SOLE exploitation path requires a rooted/jailbroken device, physical/forensic access, a debuggable/test build, or adb/developer-mode is out of scope -> Won't-Fix (Sev4) — the OS boundary is already defeated. Critical SOLE-path nuance: must first prove there is NO non-root path (another app via IPC/Intent/deep-link, network/zero-click, or off-device egress like a diagnostics/log upload); if one exists the finding is in scope and the non-root path governs (cites the TOTP-seed log example that stayed Important on its diagnostics egress). Wired into the rubric tiers, the defense-in-depth sweep checklist, Step 3 investigation, the SKILL severity table, and the calibration log; reconciled the Moderate tier so root-only no longer reads as Moderate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s AB#3654306 The TOTP-seed worked example carried a real IcM number; safety_check flagged it (public-mirror rule). Replaced with 'an ITD' — the example doesn't need the literal id. Follow-up to 2155afc, which was pushed before the safety check was re-verified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extends the skill beyond triage+reporting to optionally implement kept findings end-to-end (fix, test, public-repo-safe PR): - New references/remediation-execution.md: public-repo PR hygiene (sanitize branch/commit/comments/test fixtures), regression-safety prime directive for >1B-user libraries (smallest diff, default-OFF ECS flight, reuse hardened siblings, prove rollback + legit-flow tests), the common gradle credentials gotcha, and references to codebase-researcher + repo custom instructions for grounded edits. - SKILL.md: broadened name/description/title to triage -> report -> remediate; added Step 4.6 (execute the fix & open the PR). - severity-rubric.md: calibration entry for default-OFF progressive rollout of a shared-library security fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Captures that the four target repos differ in PR destination and GitHub identity model, so the correct credential must be chosen before opening a PR: - common / msal: public GitHub, non-EMU -> use the local Git Credential Manager token; the MCP GitHub tool is EMU and 403s on these public repos, so fall back to the REST API. - broker: GitHub Enterprise, EMU -> use the EMU/MCP identity. - authenticator: Azure DevOps -> open the PR in ADO, not GitHub. Adds the matrix + fallback recipe to references/remediation-execution.md and a pointer in SKILL.md Step 4.6. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remediation often spans multiple sessions (one per MSRC). Documents a durable EXECUTION-TRACKER.md kept in the private workspace (not the repo) that records per-finding IcM<->WI<->branch<->commit<->PR linkage and an exec status, so a fresh session for a single MSRC can resume without re-deriving state from git. - references/remediation-execution.md: new "Cross-session execution tracker" section with status vocabulary + per-finding skeleton. - SKILL.md Step 4.6: pointer to maintain the tracker at every milestone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…post-report next-actions, Fixes AB#3654306 Gap 1 — shift-windowing: NEW scripts/shift.py computes the Wed->Wed window (shift containing today; a Wednesday starts a fresh shift), names the folder deterministically (msrc/<YYYY-MM-DD_to_YYYY-MM-DD>/), and maintains a per-shift manifest.json (check=dedup exit 0/3, add=append first_seen). Wired into the mode picker + Step 0/1/2 (scaffold under the shift folder; dedup before researching). Gap 2 — prior/duplicate lookup: NEW Step 1.5 — before investigating, call IcM get_similar_incidents + the android-dri-search MCP and record a **Prior incidents:** field (report-template). A prior RESOLVED match lets on-call short-circuit (link fix / close dup). build_research_pages renders an inline field + a CONDITIONAL 'N prior' tile (only when a match exists, to keep tiles concise). Post-report flow — NEW Step 8: after the report, the agent summarizes and ASKS which follow-ups to run (manifest record = auto; create PBIs = approve, Step 6; dispatch/execute a fix via the .agent.md spec = approve per finding, blocked_on gates honored; weekly status = on request). Nothing auto-creates work items or opens PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # .github/skills/vuln-triage-reporter/references/severity-rubric.md
… (intern), Fixes AB#3654306 build_status_report.py now auto-discovers EXECUTION-TRACKER.md next to the CSV and uses its per-finding exec status (branch/PR/merge state) IN PRECEDENCE OVER live ADO state — the tracker is the source of truth for what's actually been done. Maps the tracker vocabulary (NOT STARTED/IN PROGRESS/IMPLEMENTED/PUSHED/PR OPEN/MERGED/BLOCKED/OUT OF SCOPE) to the report's status set, adding a new 'Out of scope' status. Intern-eligible items show Out of scope (assigned to an intern who hasn't started yet), sort last, and the report renders a one-line note explaining it. Parser only accepts rows whose last cell is a recognized status (skips the tracker's bottom spec-path table). --tracker overrides auto-discovery. Updated template + SKILL Step 7. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… Fixes AB#3654306 Two new columns driven by the Combined Android Release Checklist: - Code complete = eng-days + testing buffer (default +50%), business days from --asof; remaining effort scaled by status (Not started=full, In progress=half, In review/Complete=done). ✓ when code is done; — when out of scope. - Prod (100%) = code-complete + a COMPONENT-BASED rollout window: ~14d for broker/common/MSAL/ADAL libraries (Phase 4 Maven Central publish) vs ~35d for the Authenticator app (Phase 5 gradual ramp 5/10/25/50/100 with 2-day bakes + flag-on after 100%). Libraries reach prod earlier than the app — verified the app item lands ~3 weeks later than an equal-effort common fix. New args: --asof, --test-buffer, --rollout-app-days, --rollout-lib-days. Footer legend documents the basis; template gains a 'Prod rollout basis' section citing the release checklist. Read the rollout timeline from the checklist via EngHub. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…reporter A kept finding (unvalidated app_link -> ACTION_VIEW, cited at 3-4 sinks) turned out to be already neutralized on current dev by a shared allow-list validator at the redirect->result-code classifier, upstream of every sink. It was only discovered mid-remediation (the fix's tests failed because the existing validator rejected the test input). The report should have flagged it as already-covered defense-in-depth (Won't-Fix/Low). Learnings captured: - severity-rubric.md: strengthen the defense-in-depth "Upstream validation" row to trace untrusted input all the way to its admission/classifier point, and flag already-mitigated findings as Won't-Fix instead of proposing a redundant sink fix; + calibration-log entry for this case. - remediation-execution.md: new "Pre-flight" gate — re-verify the finding still reproduces on the current base-branch HEAD before writing code (findings are investigated on a snapshot; controls land in between). - SKILL.md Step 4.6: pointer to the pre-flight re-verify. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ction We keep getting MSRC/ITD findings already covered by defense-in-depth. Codify that as Gate 0: check for existing coverage FIRST, before any Engineer/Intern split, and close covered findings out as Won't-Fix (Already-Covered) -- shipping nothing, since a redundant fix in a >1B-user library is regression risk for no gain. Conservatism both ways: the category requires a cited covering control on the current base branch, because not everything is covered. - SKILL.md: new Gate 0 in Step 4 + Non-Negotiable #7 + the Confidence/Assignment summary table; adds the Won't-Fix (Already-Covered) assignment. - scripts/rollup.py: derive_assignment buckets Won't-Fix separately; new "Already Covered / Won't-Fix" section (0 eng-days) ahead of Engineer/Intern. - report-template.md: Assignment line documents the Gate-0 option. - severity-rubric.md: calibration entry on the high filing volume + the category. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…surface drop reason build_status_report.py: - New "In testing" status (amber) for code-complete-but-unverified work (draft PRs), distinct from "In progress" (still being written) and "In review" (formal review). Draft PRs map here; ETA weight 15% remaining. - New PR column with linked draft-PR references, parsed from the execution tracker (GitHub PR #NNNN resolved by component->repo; ADO PR !NNNN linked directly). - Dropped / already-covered / won't-fix tracker rows map to Out of scope. build_research_pages.py / build_master_report.py: - compute_assignment recognizes Won't-Fix (Already-Covered) as its own bucket (coverage gate first), so dropped findings are no longer miscounted as Engineer-owned. - Master WBR gains an "Already covered / Won't-Fix" stat card. - The per-finding DROP REASON lives in the research report (per-finding markdown -> research subpage + master), NOT the weekly email. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ete column Per team preference, allow supplying milestone dates instead of computed ETAs: - --code-complete-date: one date for all findings, shown once in the header (the per-row "Code complete" column is removed). - --prod-date-app / --prod-date-lib: fixed Prod (100%) dates per component family (Authenticator vs broker/common/msal/adal). When set, the Prod column and footnote use these instead of the rollout-day estimate. Accepts ISO or M/D/YY date formats. Falls back to the computed estimate when the flags are omitted (backward compatible). Out-of-scope rows show "—". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…act verification Addresses feedback from two engineers who ran the skill cold. Reported problems -> fixes: - A full session produced a verdict but ZERO files on disk. Report generation is now unconditional (Non-Negotiable #17) and explicitly covers Won't-Fix / Already-Covered / out-of-scope verdicts, which are the ones most often lost to chat and the ones we must justify back to the security team. New scripts/verify_outputs.py closes every run: it asserts the required artifacts exist, prints their absolute paths, and exits non-zero if the run is incomplete. - Asked to help with a fix, the skill went straight to writing one. Added a mandatory Fix Options gate (Non-Negotiable #16 + Step 4.5 + remediation-spec): 2-3 materially different approaches with tradeoffs and a "does NOT close" column, a defended recommendation, and a stop-and-wait before any edit. - A related prior MSRC was missed because it came in through a different Android component type (Activity vs Service) with the same root cause. Step 1.5 now requires three query shapes -- vuln class alone, sink API alone, and class x each component type -- plus a grep of prior shift folders, and records near-misses as "Related prior art" fed into Pass 1. - Runs took ~35 min against a documented ~20. ETAs now reflect measured ranges, with Fast/Standard/Deep depth modes the engineer chooses explicitly, plus scope allow-lists, time budgets with partial-result reporting, and a Pass 2 scoped to challenging Pass 1's claims rather than re-investigating. - Pass 1 concluded "no fix" and Pass 2 found the real root cause. Pass 1 is now explicitly never a verdict; on disagreement the challenger wins, a scoped reconciliation pass runs, and Confidence drops to Low with both conclusions preserved in the report. First-run experience (new Step -1 + references/intake-interview.md): one message, four questions -- what to look at (specific MSRC/IcM ids, this shift, a date range or week, an ITD report, the engineer's own existing findings, or just finalize), how deep, what outcome, and any context -- then a plan echo that always names the absolute output folder before anything launches. Broader trigger phrases so the skill surfaces without an explicit slash-invocation. Validated: safety_check.py PASS; verify_outputs.py exercised against missing, partial, and fully-populated run folders plus --expect and shift resolution; all relative markdown links resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ix verification Addresses a reported wrong verdict: a finding living entirely in one cross-app channel had a co-resident but unrelated channel pulled into the analysis, and the higher-severity argument was retired on that evidence. Every citation resolved -- only the relevance was wrong, which is precisely what a file:line citation cannot establish. Two distinct defects, both invisible to a citation check: 1. Scope leak -- an off-path component treated as relevant to the sink's trust decision. Fixed by a Scope Contract (new Step 2.5, Non-Negotiable #18) written BEFORE any agent launches: sink location, channel, entry point, trust decision, consumers, asset, and an explicit OUT OF SCOPE list. Evidence is admissible as a mitigation OR a refutation only with a named hop-by-hop call path; "same app" is not a path, and off-path evidence can move severity in neither direction. 2. Claim drift -- the claim was silently reworded between passes, so the refutation answered a question nobody asked. Fixed by a Claim Ledger (Non-Negotiable #19): numbered claims, quoted verbatim, tagged with their channel, carried into the challenger prompt by copy rather than restatement. Every refutation now passes a strawman check (verbatim, same channel, same asset/consumers, and no nouns absent from both the claim and IN SCOPE) or is VOID. Untested is not refuted; severity moves only on ledger transitions. This also corrects the previous "challenger wins by default" rule, which was unsafe as written -- a challenger winning on a strawman converts an open question into false confidence with citations attached. Also adds the capability to verify a fix works with the flight on or off (Non-Negotiable #20, new Step 4.7, references/flight-verification.md): a four-cell matrix requiring flight OFF + exploit = still vulnerable, OFF + legit = works, ON + exploit = blocked, ON + legit = works. Cell A is the one usually skipped and the one that matters -- if the exploit fails with the flight OFF, the finding was already covered or the test never reproduced it, and the blocked result proves nothing. Paired automated tests are the gate; an on-device toggle pass via BrokerHost and the Broker Flights screen is the sign-off, since only the device proves the flight key is read at the sink in a real build. Promotes the SDL/MSRC bug bar factor table to a required report artifact after a reviewer singled it out as the most useful part of the analysis -- it makes the severity call auditable rather than asserted, and requires naming the factor that blocks the tier in BOTH directions so a rebuttal doesn't read as motivated. New scripts/lint_finding.py checks report structure (scope contract, claim ledger with channel tags, verdict, audit trail, and the flag matrix when a fix is claimed). It cannot judge reasoning, but the omissions it catches are the exact shape of the failure above. Validated: safety_check.py PASS; lint_finding.py exercised against a bare report, a report reproducing the untagged-claim/missing-matrix signature, and a complete report (--strict clean); all relative links resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A finding report's `**Label:**` fields and its Filed/Ours classification rows are a parser contract that populates the HTML stat tiles and the master-report row -- not prose. A report written free-hand instead of from the template read perfectly but published with the Severity, Confidence and Verdict tiles blank, and the master table labelled an MSRC as an ITD. Documenting the template again would not have caught it, so the failure is now enforced by the scripts the workflow already runs. - new_finding.py: scaffold a template-conformant report into the CURRENT shift folder and record it in the manifest. Refuses an IcM already triaged this shift (exit 3) so the week's second finding appends instead of clobbering. - rebuild_shift.py: one command for the whole shift -- lints first and refuses to build on a structural failure, then regenerates research pages, agent specs, master report and roll-up across every finding, and closes with verify_outputs. The master report is built from all findings, so a mid-shift append requires a full regeneration, not an edit. - verify_outputs.py: fail when a finding's tile fields do not parse, when a rendered tile is blank, or when a scaffolded report still contains TODO. The last one closed a hole found in testing: an unfilled skeleton passed lint and published as a real, actioned row. - build_master_report.py: accept the bracketed [MSRC]/[ITD] title form the template prescribes -- the regex never matched its own template, so every conforming report was mislabelled ITD. Add a RE-ROOTED verdict for when the filed tier stands but the filed root cause was refuted and a different real weakness was kept; rendering that as AGREE hid the point of the finding. - build_research_pages.py: same verdict vocabulary so both pages agree, and neutral wording on the prior-incidents tile -- several priors on one theme are a campaign, not a duplicate, and "may be a dup" invites closing a distinct finding. - report-template.md: add the Scope Contract and Claim Ledger sections that lint_finding.py requires but the template omitted, so following the template verbatim no longer fails lint. Document RE-ROOTED and the validate-before-you -generate step. - SKILL.md: Non-Negotiables #21/#22 (scaffold every report; a second finding mid-shift appends to the same folder), the append loop, and the intake path for restricted [MSRC] IcMs -- IcM MCP, the portal and the DRI MCP all deny access, so the case zip from the IcM MSRC tab is the reliable route. safety_check.py and lint_finding.py pass; full pipeline verified end to end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A stale git remote silently produced a wrong Gate-0 verdict. The broker module had migrated hosts; the retired remote kept resolving and kept serving a frozen snapshot, so `git fetch` exited 0 and `git pull` reported "Already up to date" while every history query stopped at the migration date. A fix that had landed and shipped was reported as "no fix exists", two findings were over-rated, and an escalation went out twice on a wrong premise. Separately, two findings stalled at "unverifiable server-side boundary" purely because the identity-service source was not checked out. With it, both resolved in under 20 minutes and the answer changed the severity. - Add scripts/preflight.py: one command, blocking exit code. Asserts submodule population, main-checkout-not-worktree, expected remote per module, currency, identity-service checkout, and workspace writability. Validated against the real drift case. - Requirements: promote the identity-service source to REQUIRED; split "current" from "correct remote" as separate checks, because a successful fetch is not evidence of currency. - Gate 0: four outcomes rather than two (covered / not covered / fix exists unshipped / landed-then-reverted); judge coverage on the shipping branch, not dev; watch for revert-then-merge, where ancestry misleads and only file content settles it; resolve submodule pins via ls-tree rather than `git show <ref>:<submodule>/<path>`, which returns a gitlink. - Verification boundary: "unverifiable" is now a conclusion to earn, not a default, since server-side questions are frequently answerable in source. - Search gotchas: never search only *.java (much of broker/common is Kotlin); a glob miss is not an absence proof; prove absence tree-wide, both languages; verify platform behavior in real AOSP source at the app's actual minSdk. - Intake: run preflight before the plan echo; echo each repo HEAD so freshness is proven rather than assumed; flag engineer-blocking items (missing source checkout, interactive host auth) up front instead of mid-run. Public-repo safety check passes; learnings are recorded as process only, with no flight names, control logic, incident identifiers, or private-submodule file:line references. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Triage runs could answer "is there a control?" but never "could a control exist at all?", and collapsed every closed-out finding into one bucket. A benchmark run against an already-resolved case exposed the gaps. Verdicts - Gate 0 grows from four outcomes to six, adding Not-Fixable (By-Design) for weaknesses no client-side change can close, and Fixed-Since-Filed for reports that were accurate when filed. The latter now requires a shipped-release exposure answer, since "fixed on dev" does not tell us whether customers were exposed. - Findings split per sub-claim. One report routinely bundles claims that resolve differently, and a blended verdict either overstates exposure or silently closes a live issue. - Existing Work records the branches and commits that already carry an unshipped fix. "No control on the shipping branch" and "written, merged, waiting for a train" are the same outcome but different asks. Domain knowledge - protocol-constraints.md covers the OAuth public-client model, FOCI, and what redirect-URI signature binding can prove. It warns against over-using by-design: the stronger claim is usually that our design stops depending on the unauthenticatable value. - msrc-bundle-intake.md covers case bundles and the evidence-quality pass. The attached PoC is the highest-signal artifact and reports cite identifiers that do not exist in our tree. Reliability - challenger-prompt.md ships a pre-vetted Pass 2 template. An improvised adversarial prompt was blocked by content filtering, returning nothing after ten minutes and silently costing the whole pass. - Gate 0 must resolve which ref actually ships. Two of our own reports judged coverage against an integration branch that carried a control only via a later dev merge; the release branch and its submodule pin lacked it, and the branches had diverged. - preflight fails on abandoned branches instead of warning, checks the nested broker/common gitlink the build actually compiles, checks the Authenticator app, and tolerates monorepo churn so the gate is satisfiable. - classifications.csv is emitted automatically, so the roll-up generates and the closing gate stops reporting warnings on a correct run. Drops the engineer/intern owner split, which no longer applies. Public-repo safety check passes; no versions, flight names, work-item IDs or case numbers are referenced. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
❌ Work item link check failed. Description does not contain AB#{ID}. Click here to Learn more. |
|
✅ Work item link check complete. Description contains link AB#3654306 to an Azure Boards work item. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain in intake, safety, repository-gating, and reporting workflows.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR expands vulnerability-triage workflows with fixability verdicts, evidence gates, reporting automation, and public-repository safety checks. It does not change runtime authentication behavior.
Changes:
- Adds tooling for intake, discovery, transcription, linting, reporting, rollups, preflight, and output verification.
- Adds guidance and templates for research, remediation, protocols, and reporting.
- Updates related skills, repository instructions, and ignore rules.
File summaries
| File | Reviewed change |
|---|---|
.kotlin/errors/errors-1782956770091.log |
Adds a generated Kotlin compiler diagnostic artifact. |
.gitignore |
Updates repository ignore rules. |
.github/skills/vuln-triage-reporter/scripts/verify_outputs.py |
Verifies generated shift outputs. |
.github/skills/vuln-triage-reporter/scripts/transcribe_finding.py |
Transcribes saved findings into reports. |
.github/skills/vuln-triage-reporter/scripts/shift.py |
Manages shift windows and manifests. |
.github/skills/vuln-triage-reporter/scripts/scaffold_itd.py |
Scaffolds ITD investigations. |
.github/skills/vuln-triage-reporter/scripts/safety_check.py |
Checks public-repository safety. |
.github/skills/vuln-triage-reporter/scripts/rollup.py |
Produces aggregate triage summaries. |
.github/skills/vuln-triage-reporter/scripts/rebuild_shift.py |
Rebuilds shift artifacts. |
.github/skills/vuln-triage-reporter/scripts/preflight.py |
Validates repository and environment state. |
.github/skills/vuln-triage-reporter/scripts/new_finding.py |
Creates finding reports. |
.github/skills/vuln-triage-reporter/scripts/lint_finding.py |
Validates report structure. |
.github/skills/vuln-triage-reporter/scripts/discover_findings.py |
Discovers and inventories findings. |
.github/skills/vuln-triage-reporter/scripts/build_agent_spec.py |
Generates agent specifications. |
.github/skills/vuln-triage-reporter/references/status-report-template.md |
Defines status-report formatting. |
.github/skills/vuln-triage-reporter/references/research-discipline.md |
Defines research and claim-ledger guidance. |
.github/skills/vuln-triage-reporter/references/report-template.md |
Defines the finding report schema. |
.github/skills/vuln-triage-reporter/references/remediation-spec.md |
Defines remediation requirements. |
.github/skills/vuln-triage-reporter/references/remediation-execution.md |
Documents remediation execution. |
.github/skills/vuln-triage-reporter/references/protocol-constraints.md |
Documents protocol constraints. |
.github/skills/vuln-triage-reporter/references/msrc-bundle-intake.md |
Documents MSRC bundle intake. |
.github/skills/vuln-triage-reporter/references/itd-intake.md |
Documents ITD intake. |
.github/skills/vuln-triage-reporter/references/intake-interview.md |
Defines intake questions. |
.github/skills/vuln-triage-reporter/references/glossary.md |
Defines report terminology. |
.github/skills/vuln-triage-reporter/references/flight-verification.md |
Defines flight-verification requirements. |
.github/skills/vuln-triage-reporter/references/challenger-prompt.md |
Defines challenger prompts. |
.github/skills/vuln-triage-reporter/references/agent-spec-template.md |
Defines agent-spec formatting. |
.github/skills/vuln-triage-reporter/.gitignore |
Ignores local Python artifacts. |
.github/skills/update-skills/SKILL.md |
Updates skill-maintenance guidance. |
.github/skills/kusto-analyst/SKILL.md |
Updates telemetry-analysis guidance. |
.github/skills/incident-investigator/SKILL.md |
Updates incident-investigation guidance. |
.github/skills/codebase-researcher/SKILL.md |
Updates codebase-research guidance. |
.github/copilot-instructions.md |
Updates repository-wide instructions. |
Review details
Suppressed comments (22)
.github/skills/incident-investigator/SKILL.md:109
- The new zero-row guard correctly tells investigators to use
correlation_id_v2, but the bundled reference queries this workflow points to still filter and project the legacycorrelation_id. Following those snippets produces the exact false-negative zero-row result this guard warns about; update the linked queries and field table in the same change.
When a `android_spans` query for an incident's correlation IDs returns zero rows, do **not** conclude "the broker was never invoked" from that alone. Zero rows is ambiguous — it could mean the query is wrong (e.g., querying `correlation_id` instead of `correlation_id_v2`), or coverage is genuinely absent. Resolve the ambiguity with **two additional queries** before relying on the result:
1. **Reference healthy trace** — pull one known-good recent example of the same operation to confirm what the row *should* look like:
```kql
android_spans
| where EventInfo_Time >= ago(30m)
| where calling_package_name == '<same-package>'
| where span_name == '<same-span>'
| where span_status == 'OK'
| where isnotempty(correlation_id_v2)
.github/skills/vuln-triage-reporter/references/remediation-execution.md:90
- This guidance explicitly permits an ADO work-item link in a public commit/PR body, but the public-repository policy for this project forbids exposing AB# identifiers in PR descriptions or code comments. The URL itself carries that identifier and can couple public history to a sensitive finding; keep the real WI/IcM linkage in the private execution tracker and make the public-safe rule consistent throughout this section.
| **Branch name** | `cesaracosta/itd-635851-intent-allowlist-bypass` | `cesaracosta/webview-intent-validation` (neutral feature name) |
| **Commit title/body** | "Fix MSRC intent-scheme bypass / CWE-939", IcM/MSRC numbers, "vulnerability/exploit" | Generic one-liner ("Add flighted validation in WebView intent handling") + **only** a corp-gated ADO work-item link |
| **Code comments** | "the intent:// scheme lets the page embed a component that redirects to an *attacker-chosen* activity" (teaches the attack) | State what the code *enforces*: "activity resolution is driven solely by the validated package" |
| **Test fixtures / names** | `TEST_INTENT_SPOOFED_PACKAGE`, `com.attacker.evil/.EvilActivity`, "decoy substrings satisfy the gate" | `TEST_INTENT_WITH_NON_ALLOWLISTED_PACKAGE`, `com.example.unrelatedapp/.SampleActivity`, neutral comments |
.github/skills/vuln-triage-reporter/references/remediation-execution.md:169
- The broker destination here is stale: the preflight gate and current routing guidance use
msft.ghe.com/security/ad-accounts-for-android, notidentity-authnz-teams/ad-accounts-for-android. Following this table can open remediation against a retired or incorrect organization.
| **broker** / broker4j | **GitHub Enterprise** (`identity-authnz-teams/ad-accounts-for-android`, GHE) | **EMU** (Enterprise Managed User) | Use the **EMU** identity for this org. |
.github/skills/vuln-triage-reporter/references/status-report-template.md:10
- This newly added status-report guidance promises that
--auto-token/--tokenwill fall back to live ADO state, but the referencedbuild_status_report.pycurrently sends******rather than the supplied bearer token in its Authorization header. Following this workflow therefore produces unauthenticated ADO reads and stale/default statuses until the script uses the token.
> Generate it with [`scripts/build_status_report.py`](../scripts/build_status_report.py), which reads
> `classifications.csv`, auto-discovers a persisted `work-item-map.json` (IcM → AB#) beside it, reads the
> **execution tracker** (`EXECUTION-TRACKER.md`) for real remediation status, and (with `--auto-token`)
> falls back to live ADO work-item state. Output is a self-contained HTML table that pastes cleanly into
.github/skills/vuln-triage-reporter/scripts/build_agent_spec.py:213
clean()deliberately removes parenthesized text, but the disposition values use the parentheses to distinguish Already-Covered, Fixed-Since-Filed, and Not-Fixable. The generated agent spec therefore collapses all of those toWon't-Fix/Not-Fixable, losing the Gate 0 outcome promised by its schema.
assignment = clean(field(md, "Disposition")) or clean(field(md, "Assignment"))
.github/skills/vuln-triage-reporter/scripts/build_status_report.py:305
- The token passed into
fetch_adois never used: every request sends the literal******as its Authorization header. Consequently--tokenand--auto-tokenalways receive an unauthorized response and the report silently falls back toNot started/missing Updated dates instead of live ADO state.
req.add_header("Authorization", f"Bearer {token}")
.github/skills/vuln-triage-reporter/scripts/lint_finding.py:122
- The claim-ledger gate accepts any row with three cells even though its own error text and
references/research-discipline.mdrequire claim, channel, evidence, Pass 2 result, and status. A row containing only ID/claim/channel therefore passes without evidence or a verdict state, undermining the new adversarial-evidence gate.
if len(r) < 3:
issues.append((REQ, f"claim {cid}: row is missing columns "
f"(need ID | claim | channel | evidence | pass2 | status)"))
continue
.github/skills/vuln-triage-reporter/scripts/preflight.py:193
- The fetch and remote-ref commands' return codes are ignored. If the fetch fails or
origin/<branch>does not exist,behind_nbecomes-1andage_daysremains unset, which reaches the final PASS branch and declares a stale/unverified checkout current. A freshness gate must fail when it cannot refresh or compare the ref.
run(["git", "-C", str(mod), "fetch", "origin", "--prune"])
rc, branch, _ = run(["git", "-C", str(mod), "rev-parse", "--abbrev-ref", "HEAD"])
if rc != 0:
continue
rc, behind, _ = run(
["git", "-C", str(mod), "rev-list", "--count", f"HEAD..origin/{branch}"]
)
behind_n = int(behind) if behind.isdigit() else -1
rc, iso, _ = run(
["git", "-C", str(mod), "log", "-1", "--format=%cI", f"origin/{branch}"]
)
.github/skills/vuln-triage-reporter/scripts/preflight.py:313
- This check has the same false-pass path for ESTS: a failed fetch, missing branch, or failed
rev-listproducesbehind_n == -1, which falls through toESTS present and current. That can let the workflow answer server-side questions from an unrefreshed checkout.
run(["git", "-C", str(ests), "fetch", "origin", "--prune"])
rc, branch, _ = run(["git", "-C", str(ests), "rev-parse", "--abbrev-ref", "HEAD"])
rc, behind, _ = run(
["git", "-C", str(ests), "rev-list", "--count", f"HEAD..origin/{branch}"]
)
behind_n = int(behind) if behind.isdigit() else -1
.github/skills/vuln-triage-reporter/scripts/preflight.py:352
- The app freshness check also ignores the fetch/comparison status and treats
behind_n == -1as current. A network failure or absentorigin/<branch>can therefore pass the very check intended to protect the shipped-release exposure decision.
run(["git", "-C", str(app), "fetch", "origin", "--prune"])
rc, branch, _ = run(["git", "-C", str(app), "rev-parse", "--abbrev-ref", "HEAD"])
if rc != 0 or not branch:
return
rc, behind, _ = run(
["git", "-C", str(app), "rev-list", "--count", f"HEAD..origin/{branch}"]
)
behind_n = int(behind) if behind.isdigit() else -1
.github/skills/vuln-triage-reporter/scripts/preflight.py:403
--skip-fetchis documented as skipping network freshness checks, butcheck_ests()is called unconditionally and performs its owngit fetch. Offline callers still hit the network and can fail or hang despite opting out; pass the flag through the ESTS check so only local validation remains when requested.
if not args.skip_fetch:
check_freshness(root, rep)
check_app_freshness(root, rep)
check_ests(ests, rep)
.github/skills/vuln-triage-reporter/scripts/preflight.py:123
any(p.iterdir())treats a directory containing only a submodule.gitmarker as populated, so the source tree can still be absent while this hard gate reports PASS. Verify actual working-tree content (for example withgit ls-filesor an expected source path) instead of mere directory entries.
populated = p.is_dir() and any(p.iterdir()) if p.is_dir() else False
if populated:
rep.add("PASS", f"submodule populated: {rel}")
.github/skills/vuln-triage-reporter/scripts/preflight.py:261
- Missing or unresolvable
broker/commonsilently returns even though this path is declared as a required nested checkout andbroker/settings.gradlebuilds against it. The run can proceed without evidence for the code actually built; emit a FAIL when the nested path, parent gitlink, or checked-out HEAD cannot be resolved.
if not (pdir / ".git").exists() or not ndir.exists():
continue
rc, out, _ = run(["git", "-C", str(pdir), "ls-tree", "HEAD", nested])
if rc != 0 or not out:
continue
.github/skills/vuln-triage-reporter/scripts/preflight.py:237
- A remote whose newest commit is older than the frozen-mirror threshold is recorded as
WARN, butReport.failedignores warnings andmain()exits 0 when there are no FAIL items. The preflight can therefore authorize investigation against exactly the frozen remote snapshot this gate is meant to reject; this condition should be blocking or require explicit override.
elif age_days is not None and age_days > STALE_DAYS * 10:
# Up to date with a remote whose newest commit is ancient => probably a frozen mirror.
rep.add(
"WARN",
f"{module}: up to date, but the remote itself looks FROZEN",
.github/skills/vuln-triage-reporter/scripts/rebuild_shift.py:89
- The research pages are generated before the agent specs, but
build_research_pages.pyonly adds the dispatch link when the matching.agent.mdalready exists. On a fresh run the HTML therefore permanently omits the agent-spec link, despite the documented generation order and the--agent-dirargument. Generate the specs first, then build the research pages.
rc |= run("build_research_pages.py", glob_arg, "--out", research_dir,
"--index", "--agent-dir", "../agent-specs")
rc |= run("build_agent_spec.py", glob_arg, "--out", specs_dir)
.github/skills/vuln-triage-reporter/scripts/rollup.py:175
- Unlike the earlier total, this sum does not catch malformed
eng_daysvalues. A non-numeric value in any kept row raisesValueErrorand aborts the roll-up, so one bad estimate prevents all reporting.
.github/skills/vuln-triage-reporter/scripts/scaffold_itd.py:28 - The default root omits the shift slug (
<workspace>/msrc/<window>/). A documented invocation without--roottherefore writes ITD READMEs outside the shift folder, whilerebuild_shift.pyand the report gates only process the shift's artifact tree. Make the default shift-aware or require the caller to provide the shift-specific root.
.github/skills/vuln-triage-reporter/scripts/shift.py:51 - Supplying only
--startor only--endsilently ignores the supplied value and computes the current shift instead. That can put findings in the wrong window without an error; reject partial explicit windows (and validate their shape) rather than falling back to today.
.github/skills/vuln-triage-reporter/scripts/transcribe_finding.py:126 EXCLUDE_SECTIONSis never consulted and the extracted rows carry no heading ancestry, soparse_code_locationsscans every table, including source-to-sink or exploit sections the module promises to omit. A table-backed PoC or trace can therefore be copied into the output; section context must be preserved and filtered before emitting rows.
.github/skills/vuln-triage-reporter/scripts/transcribe_finding.py:156- The wrapper parser only receives
TextExtractor.parts, which records headings and table rows but not the documentedfd-kvelements. For normal saved portal wrappers, wrapper-only Source, Severity, and Finding ID values are therefore silently lost.
.github/skills/vuln-triage-reporter/scripts/verify_outputs.py:176 - If
build_research_pages.pycannot be imported, this closing gate only emits a warning and returns success for the parser check. That allows a run to pass the required-artifact gate without validating the metadata that drives the HTML tiles, which defeats the purpose of this check; an unavailable parser should be a required failure.
.github/skills/vuln-triage-reporter/scripts/verify_outputs.py:161 - The structure gate never validates the MSRC/ITD source tag. A report with a missing or malformed
[MSRC]/[ITD]title can satisfy everyREQUIRED_METAfield, whilebuild_master_report.extract()defaults that title toITD; the verifier can therefore publish the MSRC→ITD mislabel this gate claims to prevent.
- Files reviewed: 35/38 changed files
- Comments generated: 14
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Fix the review comments on the vulnerability triage skill by moving ITD intake guidance to the private workspace, avoiding inline bundle passwords, validating finding slugs, failing closed on corrupt manifests, tightening remote validation, aligning finding discovery across lint/rebuild/verify, extracting suggested fixes, and removing generated Kotlin diagnostics from the branch. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the review-fix commit content LF-normalized after publishing through the GitHub API fallback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
❌ Invalid work item number: AB#3757505 ##. Work item number must be a valid integer. Click here to learn more. |
Summary
This PR expands the
vuln-triage-reporterskill and related Android Auth guidance so security triage distinguishes whether a finding is actionable, already fixed, covered upstream, or not fixable by a client-side change. It also strengthens the evidence, environment, reporting, and safety gates used by the workflow.What changed
Not-Fixable (By-Design)andFixed-Since-Filed, including shipped-release exposure checks.Work items
Validation
python -m py_compileacross.github/skills/vuln-triage-reporter/scripts/*.pypython .github/skills/vuln-triage-reporter/scripts/safety_check.py .github/skills/vuln-triage-reporter --all-trackednew_finding.pyforce cleanup, slug rejection, and corrupt-manifest fail-closed behaviorReview notes
This PR changes workflow guidance and tooling; it does not modify product authentication or runtime behavior.