Skip to content

fix(triage-panel): add DIFC read-integrity exemption for external issues#1462

Merged
danielmeppiel merged 2 commits into
mainfrom
fix/1344-triage-panel-difc-external-issues
May 23, 2026
Merged

fix(triage-panel): add DIFC read-integrity exemption for external issues#1462
danielmeppiel merged 2 commits into
mainfrom
fix/1344-triage-panel-difc-external-issues

Conversation

@sergio-sisternes-epam
Copy link
Copy Markdown
Collaborator

Problem

The triage-panel workflow's write-class safe-outputs (add-comment, add-labels, remove-labels, assign-milestone, dispatch-workflow) cause gh-aw's DIFC policy to elevate the minimum integrity for MCP reads to HIGH. Issues filed by external contributors (non-org-members) are assigned LOW integrity by DIFC, so search_issues and get_issue silently drop them -- making the triage panel blind to the exact issues it exists to triage.

Additionally, OPT_IN_RETRIAGE and MANUAL_DISPATCH modes used gh issue view bash commands, but the gh CLI is not authenticated in the agent sandbox, so those paths also failed for external contributor issues.

Fix

  1. DIFC exemption: Added difc: read-integrity: low in the workflow frontmatter. This tells the gh-aw runtime to accept LOW-integrity content in MCP reads despite the write-class safe-outputs. Safety is maintained because (a) the panel only reads issue content for classification, (b) prompt-injection rails are enforced in the prompt, and (c) write actions remain gated by safe-outputs allow-lists.

  2. MCP-based reads: Switched OPT_IN_RETRIAGE and MANUAL_DISPATCH from unauthenticated gh issue view bash commands to MCP get_issue/list_issue_comments tool calls, which are authenticated via the gh-aw runtime and now work correctly under the DIFC exemption.

  3. Corrected DIFC commentary: Fixed the SCHEDULED_SWEEP section that incorrectly claimed search_issues sidesteps DIFC filtering (it does not -- MCP reads are uniformly subject to DIFC regardless of the underlying API).

Fixes #1344

The triage-panel workflow's write-class safe-outputs (add-comment,
add-labels, remove-labels, assign-milestone, dispatch-workflow) cause
gh-aw's DIFC policy to elevate the minimum integrity for MCP reads
to HIGH. Issues filed by external contributors are assigned LOW
integrity by DIFC, so search_issues and get_issue silently drop
them -- making the triage panel blind to external contributor issues.

Changes:
- Add difc: read-integrity: low in the workflow frontmatter so MCP
  reads accept LOW-integrity content (external contributor issues).
- Switch OPT_IN_RETRIAGE and MANUAL_DISPATCH from unauthenticated
  gh CLI bash commands to MCP get_issue/list_issue_comments calls,
  which are authenticated via the gh-aw runtime and now visible
  under the DIFC exemption.
- Correct the SCHEDULED_SWEEP commentary that incorrectly claimed
  search_issues sidesteps DIFC filtering (it does not).
- Add DIFC note explaining the exemption and its safety rationale.

Fixes #1344

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 23, 2026 12:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the triage-panel gh-aw workflow being unable to triage issues authored by external contributors due to DIFC read-integrity filtering, and removes reliance on an unauthenticated gh CLI in the agent sandbox.

Changes:

  • Adds a workflow-level DIFC exemption (difc: read-integrity: low) so MCP reads can see LOW-integrity issues even when write-class safe-outputs are enabled.
  • Updates SCHEDULED_SWEEP guidance to correctly describe DIFC behavior and the rationale for using search_issues.
  • Switches OPT_IN_RETRIAGE and MANUAL_DISPATCH Step 1 reads from gh issue view to MCP get_issue / list_issue_comments.

Comment thread .github/workflows/triage-panel.md
Comment thread .github/workflows/triage-panel.md Outdated
@danielmeppiel
Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_now

External-contributor triage was silently broken by DIFC read-integrity; this narrow, well-rationalized fix unblocks 9 dropped issues and restores the contributor-funnel promise.

cc @sergio-sisternes-epam @danielmeppiel -- a fresh advisory pass is ready for your review.

All eight panelists converge on ship. The architectural argument is clean: DIFC read-integrity:low is justified by three compensating controls (prompt-injection rails, write-side allow-lists, BATCH_ALLOW_LIST computed before body ingestion), and the migration from unauthenticated gh issue view to MCP get_issue/list_issue_comments eliminates a shell-path that was both fragile and inconsistent with the agent sandbox model. The security panelist found no exploitable chain; the only hardening gap (assign-milestone lacks an allow-list) is accepted-risk given milestones are human-ratified and the verdict comment makes assignments visible.

The most consequential signal comes from the doc-writer: .apm/skills/apm-triage-panel/SKILL.md lines 295 and 322 now contradict the workflow. Line 295 still says the workflow fetches via gh issue view --json; line 322 instructs the agent to shell out to gh for duplicate verification while the workflow now states gh is unauthenticated. This is exactly the class of silent drift the PR set out to eliminate. However, SKILL.md is outside the PR's declared scope, and the drift is pre-existing (the PR did not introduce it -- it exposed it). The right call is ship now and track the SKILL.md alignment as an immediate post-merge follow-up issue, not scope-creep into this PR.

The status/needs-design label on #1344 is satisfied: the threat model is explicit in the frontmatter comment, the panel's security review confirmed no exploitable chain, and the architectural review found no abstraction gap. No further design discussion is required.

Aligned with: Secure by default -- DIFC exemption is defense-in-depth: read-only use, prompt-injection rails, write-side allow-lists, and BATCH_ALLOW_LIST all hold; accepted risk on assign-milestone is documented. Governed by policy -- DIFC read-integrity policy interaction is explicit in frontmatter; the 3-clause safety rationale is auditable and versioned alongside the workflow. OSS community-driven -- an external contributor fixing the mechanism that silently dropped external-contributor issues is the strongest possible authenticity signal for community trust.

Growth signal. The contributor-funnel had a silent-drop bug: every external-contributor issue filed since the DIFC gate shipped was invisible to the triage panel. An external contributor diagnosed and fixed this themselves. Post-merge, monitor whether the 9 backlogged issues get triaged in the next scheduled sweep. The CHANGELOG line should credit @sergio-sisternes-epam by name -- the narrative (external contributor fixes the gate that silenced external contributors) is inherently shareable and reinforces APM's community-first positioning.

Panel summary

Persona B R N Takeaway
Python Architect 0 0 2 DIFC read-integrity exemption is architecturally sound; frontmatter-prompt consistency is maintained; MCP tool migration eliminates an unauthenticated shell path. Ship.
CLI Logging Expert 0 0 0 No CLI output paths touched; triage-panel prompt-body wording uses ASCII bullet/code-block conventions already.
DevX UX Expert 0 0 1 Fix restores the contributor-facing triage promise for external issues; no ergonomic regressions. Ship.
Supply Chain Security Expert 0 1 1 DIFC read-integrity exemption is justified by defense-in-depth rails (BATCH_ALLOW_LIST, label allow-list, body-size cap, spam filter). No blocking exploit chain found. One hardening recommendation on assign-milestone.
OSS Growth Hacker 0 1 1 Contributor-funnel fix unblocks triage for external issues; recommend amplifying in CHANGELOG and crediting the external author.
Doc Writer 0 2 2 Prose is accurate and internally consistent; two stale gh issue view references in the apm-triage-panel SKILL.md now drift from the workflow's MCP-based reads.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [Doc Writer] Update SKILL.md line 295: replace gh issue view --json with MCP get_issue/list_issue_comments to match the workflow. -- Direct contradiction between workflow and skill prose; reader cross-referencing gets a wrong mental model. Same class of silent drift this PR eliminates elsewhere.
  2. [Doc Writer] Update SKILL.md line 322: switch duplicate-check instruction from gh issue view N --json to MCP get_issue. -- Workflow now states gh is unauthenticated in the agent sandbox; the skill still tells the agent to shell out to gh. Either the duplicate-check is broken at runtime or the workflow statement is overstated.
  3. [Supply Chain Security Expert] Add an allowed list to the assign-milestone safe-output (or document accepted risk in an inline comment). -- Under read-integrity:low, attacker-controlled body could influence milestone via prompt injection. Impact is low (milestones are human-ratified) but parity with add-labels would close the gap.
  4. [OSS Growth Hacker] CHANGELOG entry crediting @sergio-sisternes-epam with the contributor-funnel narrative angle. -- Adoption signal: external contributor fixing external-contributor experience is a story worth amplifying in release notes.
  5. [Python Architect] Add explicit MCP call code block to MANUAL_DISPATCH section (mirroring OPT_IN_RETRIAGE) instead of prose cross-reference. -- Agents are literal readers; duplicated fenced block is more robust than 'same as above, substituting...' at runtime.

Architecture

classDiagram
    direction LR
    class GhAwRuntime {
      <<Runtime>>
      +evaluate_frontmatter()
      +enforce_difc_policy()
      +dispatch_mcp_call()
    }
    class DIFCPolicy {
      <<Policy>>
      +read_integrity_floor str
      +elevate_for_write_class()
      +apply_exemption(level)
    }
    class SafeOutputs {
      <<AllowList>>
      +add_comment
      +add_labels
      +remove_labels
      +assign_milestone
      +dispatch_workflow
    }
    class MCPReadGate {
      <<Gate>>
      +filter_by_integrity(issue, floor)
      +search_issues(query)
      +get_issue(number)
      +list_issue_comments(number)
    }
    class TriagePanelWorkflow {
      <<Workflow>>
      +SCHEDULED_SWEEP
      +OPT_IN_RETRIAGE
      +MANUAL_DISPATCH
    }
    class Issue {
      <<Entity>>
      +integrity str
      +author_affiliation str
    }
    GhAwRuntime *-- DIFCPolicy : enforces
    GhAwRuntime *-- SafeOutputs : gates writes
    GhAwRuntime *-- MCPReadGate : gates reads
    DIFCPolicy ..> SafeOutputs : write-class elevates read floor
    MCPReadGate ..> Issue : filters by integrity
    TriagePanelWorkflow ..> MCPReadGate : calls search_issues / get_issue
    TriagePanelWorkflow ..> SafeOutputs : declares allowed writes
    class DIFCPolicy:::touched
    class TriagePanelWorkflow:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A["Trigger: schedule / issues.labeled / workflow_dispatch"] --> B{Mode selection}
    B -->|schedule| C["SCHEDULED_SWEEP"]
    B -->|issues event| D["OPT_IN_RETRIAGE"]
    B -->|workflow_dispatch + issue_number| E["MANUAL_DISPATCH"]

    C --> F["MCP search_issues<br/>query: repo:microsoft/apm is:open -label:status/triaged sort:created-asc"]
    D --> G["MCP get_issue<br/>issue_number: github.event.issue.number"]
    D --> H["MCP list_issue_comments<br/>issue_number: github.event.issue.number"]
    E --> I["MCP get_issue<br/>issue_number: inputs.issue_number"]
    E --> J["MCP list_issue_comments<br/>issue_number: inputs.issue_number"]

    subgraph DIFC["gh-aw DIFC layer"]
        direction TB
        K{"difc.read-integrity == low?"}
        K -->|yes| L["Pass LOW-integrity issues through"]
        K -->|no| M["Drop LOW-integrity issues silently"]
    end

    F --> DIFC
    G --> DIFC
    H --> DIFC
    I --> DIFC
    J --> DIFC

    L --> N["Step 2: Run triage panel classification"]
    N --> O["Step 3: Apply decisions via safe-outputs"]
    O --> P["add-comment / add-labels / remove-labels / assign-milestone"]
    O --> Q["dispatch-workflow: project-sync"]
Loading

Recommendation

Merge as-is. Zero blocking findings, narrow scope, well-rationalized threat model, and it unblocks 9 silently-dropped external-contributor issues today. File a follow-up issue for the two SKILL.md drift lines (295, 322) -- they are the highest-signal post-merge action and should land before the next scheduled triage sweep so the agent does not hit the stale gh issue view instruction at runtime. Remove status/needs-design from #1344 on merge; the design question is answered by the panel's security and architecture confirmation.


Full per-persona findings

Python Architect

  • [nit] Design patterns note for architectural record at .github/workflows/triage-panel.md
    Straight-line procedural workflow prose; no OO patterns apply to a gh-aw natural-language workflow file. No abstraction would reduce complexity further.
  • [nit] MANUAL_DISPATCH section could show the substituted MCP call explicitly (like OPT_IN_RETRIAGE does) to reduce ambiguity for the agent at runtime at .github/workflows/triage-panel.md:405
    OPT_IN_RETRIAGE includes a fenced code block with the literal get_issue and list_issue_comments calls. MANUAL_DISPATCH says 'Same MCP calls ... (substituting ...)'. Agents are literal readers; a duplicated fenced block with ${{ inputs.issue_number }} would be more robust than a prose cross-reference.
    Suggested: Add an explicit fenced code block mirroring the OPT_IN_RETRIAGE calls with ${{ inputs.issue_number }} substituted.

CLI Logging Expert

No findings.

DevX UX Expert

  • [nit] MANUAL_DISPATCH section lost the inline get_issue/list_issue_comments code blocks at .github/workflows/triage-panel.md:402
    OPT_IN_RETRIAGE now has explicit MCP call examples that a future workflow maintainer can copy-paste. MANUAL_DISPATCH says 'Same MCP calls as OPT_IN_RETRIAGE' with no inline snippet.
    Suggested: Add a brief inline example or markdown anchor link.

Supply Chain Security Expert

  • [recommended] assign-milestone safe-output lacks an allow-list unlike add-labels -- attacker-controlled body can influence milestone string at .github/workflows/triage-panel.md
    add-labels uses a strict allowed enumeration. assign-milestone has no equivalent allowed constraint. Under read-integrity:low, an attacker who controls the issue body could craft prompt-injection to coerce a misleading milestone assignment. Impact LOW: milestones are human-ratified, verdict comment makes assignment visible, only existing milestones can be assigned.
    Suggested: Add an allowed list to assign-milestone safe-output, or document accepted risk.
  • [nit] DIFC rationale comment should name the BATCH_ALLOW_LIST as the primary compensating control at .github/workflows/triage-panel.md
    BATCH_ALLOW_LIST is computed BEFORE body ingestion and is the strongest structural defense against cross-issue write amplification.
    Suggested: Append clause (d) BATCH_ALLOW_LIST is computed before body ingestion, preventing cross-issue write amplification via prompt injection.

OSS Growth Hacker

  • [recommended] Credit the external contributor explicitly in CHANGELOG and release notes for this fix.
    An external contributor fixing the very mechanism that silenced external contributors is a powerful authenticity signal. The story angle is inherently shareable.
    Suggested: CHANGELOG: 'fix(triage): external contributor issues are now triaged instead of silently dropped by DIFC read-integrity gate (thanks @sergio-sisternes-epam, fixes [BUG] triage-panel skips external contributor issues due to DIFC integrity policy #1344)'.
  • [nit] Consider a CONTRIBUTING.md note that the triage panel processes all issues regardless of author org membership.
    Sets expectations for new external contributors.

Auth Expert -- inactive

Only .github/workflows/triage-panel.md (a gh-aw workflow prompt) is touched; no AuthResolver, token_manager, or host-classification surface is changed.

Doc Writer

  • [recommended] SKILL.md line 295 still tells the skill the orchestrating workflow fetches issue context via gh issue view --json; after this PR the workflow uses MCP get_issue + list_issue_comments. at .apm/skills/apm-triage-panel/SKILL.md:295
    Drift between the workflow (now MCP) and the skill prose that documents how the workflow hands context to the skill. Reader cross-referencing the two files will get a contradictory mental model -- exactly the kind of silent drift this PR set out to remove.
    Suggested: Replace gh issue view --json with get_issue/list_issue_comments (MCP), matching the new OPT_IN_RETRIAGE/MANUAL_DISPATCH wording.
  • [recommended] SKILL.md line 322 instructs the skill to verify a duplicate candidate with gh issue view N --json state,title, but the workflow's new prose explicitly says gh is not authenticated in the agent sandbox. at .apm/skills/apm-triage-panel/SKILL.md:322
    Direct contradiction introduced by the PR: workflow says gh is unauthenticated, skill still tells the agent to shell out to gh. Either the duplicate-check step is broken at runtime or the workflow statement is too strong.
    Suggested: Switch duplicate verification to MCP get_issue(owner, repo, issue_number: N).
  • [nit] The SCHEDULED_SWEEP 'DIFC note' duplicates the safety rationale already in the frontmatter comment. at .github/workflows/triage-panel.md:311
    Non-bloat / state-once principle.
    Suggested: Collapse the 8-line DIFC note to a 2-line pointer.
  • [nit] Voice tighten-up in SCHEDULED_SWEEP intro parenthetical. at .github/workflows/triage-panel.md:293
    'authenticated via the gh-aw runtime; the difc: read-integrity: low exemption ensures external contributor issues are not filtered' reads slower than its terse neighbors.
    Suggested: Drop the 'authenticated via the gh-aw runtime' clause.

Test Coverage Expert -- inactive

Documentation-only PR -- no src/**/*.py touched; workflow-prompt-only change to .github/workflows/triage-panel.md, no runtime code paths to defend.

This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.

@sergio-sisternes-epam sergio-sisternes-epam added the panel-review Trigger the apm-review-panel gh-aw workflow label May 23, 2026
…d content_id source

- Distinguish search_issues (silent drop) from get_issue (McpError:
  MCP error 0: [Filtered]) in the DIFC read-integrity frontmatter
  comment so readers understand the explicit vs silent failure modes.
- Replace stale reference to 'gh issue list/view --json id' as the
  source of the dispatch-workflow content_id with the correct MCP
  search_issues/get_issue response, consistent with the rest of the
  workflow which uses MCP reads throughout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

APM Review Panel: ship_with_followups

DIFC read-integrity exemption correctly unblocks external contributor triage; two convergent findings (hardcoded org/repo, residual injection posture) warrant follow-up before the next triage cycle

cc @sergio-sisternes-epam @danielmeppiel -- a fresh advisory pass is ready for your review.

All seven active panelists agree on the core: the difc: read-integrity: low exemption is architecturally sound, the bug is real (external contributor issues were silently dropped), and the fix direction is correct. The switch from unauthenticated gh issue view bash to authenticated MCP calls is a net improvement regardless of whether the repo is public -- rate-limit headroom and pipeline consistency are genuine gains.

The two highest-signal findings with multi-panelist convergence are: (1) hardcoded owner/repo strings in MCP pseudo-code blocks (python-architect and devx-ux-expert independently flag this) -- if this workflow is forked or mirrored, those strings silently route the agent to the upstream org's issue tracker; the fix is one-line: ${{ github.repository_owner }} and ${{ github.event.repository.name }} are already used in adjacent blocks; (2) the safety rationale is logically complete but does not state the posture trade-off in writing -- that prompt-level injection rails are LLM-evaluated and weaker than system-level DIFC enforcement (supply-chain-security and auth-expert converge), which matters for future editors who may not hold the full threat model in their heads.

The dispatch_workflow injection risk raised by supply-chain-security is real but bounded: the safe-outputs allow-list constrains the write surface, and the blast radius of project-sync board manipulation is not a code-execution or credential-exfiltration path. The add-comment content exfiltration surface flagged by auth-expert is similarly low: an adversary gains mislabeling, not privilege escalation. Both belong in follow-up issues, not as pre-ship conditions.

The CHANGELOG entry recommendation carries strategic weight disproportionate to its technical footprint. Silent triage drops are among the highest-churn contributor experience failure modes. Calling the fix out -- even in a brief CHANGELOG line -- signals that the project monitors and repairs its contributor funnel.

Dissent. doc-writer assessed the CHANGELOG omission as acceptable for an internal workflow-only fix; oss-growth-hacker assessed it as a recommended addition on contributor-retention grounds. The growth argument is stronger: the fix is internal in implementation but external in effect -- it restores correct triage behavior for all non-org-member issue authors. A one-line CHANGELOG entry costs nothing and signals community accountability.

Aligned with: Portable by manifest (weakly -- hardcoded owner/repo in MCP call blocks risks fork-portability); Secure by default (partially -- MCP migration improves auth posture; residual prompt-injection trade-off is accepted and should be stated explicitly); Governed by policy (met -- difc: exemption follows the gh-aw DIFC mechanism correctly); OSS community-driven (directly improved -- external contributor issues were silently invisible before this fix); Pragmatic as npm (met -- minimal, targeted change that ships the right behavior).

Growth signal. Triage responsiveness is a top-3 contributor retention signal. The window between this fix shipping and a CHANGELOG entry going live is the right moment to monitor external-contributor issue response-time metrics. A spike in re-opened or newly filed issues post-announcement is a positive signal -- it means contributors who wrote off the project are trying again, and that data point is worth amplifying in the next release post or community update.

Panel summary

Persona B R N Takeaway
Python Architect 0 1 1 Architecturally correct fix; MCP call blocks hard-code owner/repo -- use template variables.
CLI Logging Expert 0 1 2 No CLI output regressions; add candidate-count logging in SCHEDULED_SWEEP so silent DIFC regressions are detectable.
DevX UX Expert 0 2 2 Correctness fix with good prose; silent-drop fix is itself silent -- emit a DIFC-active notice, and replace hardcoded org/repo.
Supply Chain Security Expert 0 2 2 DIFC lowering is operationally justified; dispatch_workflow input validation and explicit posture trade-off statement both warrant follow-up.
OSS Growth Hacker 0 1 1 Fixing silent issue-drop is a high-leverage contributor funnel repair -- add a CHANGELOG entry.
Auth Expert 0 1 2 Exemption is architecturally appropriate; add-comment has no content allow-list; cross-reference injection rail enforcement to the skill file.
Doc Writer 0 1 2 DIFC explanations are well-written; restore mid-page pagination-reliability nuance in SCHEDULED_SWEEP.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [Python Architect + DevX UX Expert] Replace hardcoded owner: "microsoft", repo: "apm" in OPT_IN_RETRIAGE MCP call blocks with ${{ github.repository_owner }} and ${{ github.event.repository.name }} template variables -- two panelists independently flagged this; any fork or mirror silently triages the wrong repository; the fix is one-line and the pattern is already used in adjacent blocks.
  2. [Supply Chain Security + Auth Expert] Add an explicit sentence to the difc: block acknowledging that prompt-level injection rails are LLM-evaluated and weaker than system-level DIFC enforcement, and that the exemption accepts this residual risk in exchange for external-contributor triage coverage -- future editors who remove prompt rails without realizing they are the sole injection barrier will have no written signal that the exemption's security model depends on those rails.
  3. [OSS Growth Hacker] Add a CHANGELOG entry: fix(triage): external contributor issues were silently dropped by the triage panel due to DIFC integrity filtering; they are now triaged correctly -- the fix is internal in implementation but external in effect; a one-line entry signals the project monitors and repairs its contributor funnel.
  4. [Doc Writer] Restore pagination-reliability nuance in SCHEDULED_SWEEP: client-side, which is wasteful and unreliable -- DIFC can silently drop non-org-member results mid-page, making "is the next page worth fetching?" reasoning unsound -- the removed sentence carried the precise technical reason search_issues is structurally superior to list_issues, not merely faster.
  5. [Auth Expert] Add a cross-reference in the difc: comment block pointing to the specific skill file and section where body-size caps and BATCH_ALLOW_LIST injection rails are enforced -- if a future edit to the skill removes those rails, this workflow's DIFC exemption loses its stated defence without any warning.

Architecture

classDiagram
    direction TB
    class TriagePanelWorkflow {
        <<WorkflowOrchestrator>>
        +mode: SCHEDULED_SWEEP|OPT_IN_RETRIAGE|MANUAL_DISPATCH
        +difc_read_integrity: low
        +timeout_minutes: 30
        +run()
    }
    class DIFCPolicy {
        <<SecurityGateway>>
        +write_class_outputs: list
        +read_integrity_floor: HIGH|LOW
        +filter(content, integrity) bool
    }
    class SafeOutputsAllowList {
        <<WriteGate>>
        +add_comment()
        +add_labels()
        +remove_labels()
        +assign_milestone()
        +dispatch_workflow()
    }
    class MCPGitHubServer {
        <<IOBoundary>>
        +search_issues(query, sort, per_page) list
        +get_issue(owner, repo, issue_number) Issue
        +list_issue_comments(owner, repo, issue_number) list
    }
    class ApmTriagePanelSkill {
        <<Strategy>>
        +run(issue) TriageVerdict
    }
    class TriageVerdict {
        <<ValueObject>>
        +labels: list
        +milestone: str
        +comment: str
    }
    class TriagePanelWorkflow:::touched
    TriagePanelWorkflow *-- SafeOutputsAllowList : write-gated by
    TriagePanelWorkflow ..> DIFCPolicy : exempts read-integrity via frontmatter
    TriagePanelWorkflow ..> MCPGitHubServer : reads issues via
    TriagePanelWorkflow *-- ApmTriagePanelSkill : delegates classification
    DIFCPolicy ..> MCPGitHubServer : filters responses
    ApmTriagePanelSkill ..> TriageVerdict : produces
    SafeOutputsAllowList ..> MCPGitHubServer : writes back via
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A(["Trigger: issues/labeled OR schedule OR workflow_dispatch"]) --> B["Mode selection"]
    B -->|"label=status/needs-triage"| C["OPT_IN_RETRIAGE\n[MCP] get_issue + list_issue_comments"]
    B -->|"workflow_dispatch"| D["MANUAL_DISPATCH\n[MCP] get_issue + list_issue_comments"]
    B -->|"schedule"| E["SCHEDULED_SWEEP\n[MCP] search_issues(sort:created-asc, per_page:30)"]
    C --> I["Run apm-triage-panel skill"]
    D --> I
    E --> H["Apply quota filter, cap to 10"] --> I
    I --> J{"TriageVerdict?"}
    J -->|yes| K["safe-outputs: add-comment"]
    J -->|"empty"| L["Exit cleanly"]
    K --> M["safe-outputs: add-labels / remove-labels / assign-milestone"]
    M --> O["safe-outputs: dispatch-workflow project-sync (conditional)"]
    subgraph DIFC ["DIFC Policy Layer"]
        P["Write-class outputs detected\n- normally elevate read-integrity to HIGH"]
        Q["difc: read-integrity: low\n- exemption: accept LOW-integrity MCP reads"]
        P -. overridden by .-> Q
    end
    C & D & E -.-> DIFC
    DIFC -.->|"External contributor issues now visible"| C & D & E
Loading
sequenceDiagram
    participant GHEvent as GitHub Event
    participant Workflow as triage-panel.md
    participant DIFC as gh-aw DIFC
    participant MCP as GitHub MCP Server
    participant Skill as apm-triage-panel skill
    participant SafeOut as safe-outputs
    GHEvent->>Workflow: trigger (label / schedule / dispatch)
    Workflow->>DIFC: frontmatter difc: read-integrity: low
    Note over DIFC: Exemption registered -- LOW-integrity MCP reads permitted
    Workflow->>MCP: get_issue / search_issues (authenticated)
    MCP-->>DIFC: response with LOW-integrity external issue
    DIFC-->>Workflow: passes through (exemption active)
    Workflow->>MCP: list_issue_comments
    MCP-->>Workflow: comment history
    Workflow->>Skill: run panel (issue + comments)
    Skill-->>Workflow: TriageVerdict
    Workflow->>SafeOut: add-comment (triage verdict)
    SafeOut-->>Workflow: comment posted
    Workflow->>SafeOut: add-labels / assign-milestone
    SafeOut-->>Workflow: labels and milestone applied
Loading

Recommendation

This PR fixes a real and significant bug: external contributor issues were silently invisible to the triage panel, which is among the highest-churn contributor experience failures possible. The fix is correct, the DIFC exemption is architecturally sound, and all active panelists agree on the approach. The MCP migration is a net improvement. Nothing in the panel findings warrants holding this change. The five follow-ups above are improvements to portability, security posture transparency, contributor communication, documentation precision, and governance traceability -- all of which can ship as follow-on PRs without holding the core fix. The maintainer should prioritize the hardcoded org/repo fix (follow-up 1) and the explicit posture trade-off statement (follow-up 2) in the next iteration, as both are low-effort and close real gaps. The CHANGELOG entry (follow-up 3) should ideally land in the same release window to capture the contributor-retention signal while the fix is fresh.


Full per-persona findings

Python Architect

  • [recommended] MCP call blocks hard-code owner/repo instead of using workflow template variables at .github/workflows/triage-panel.md:378
    The new get_issue and list_issue_comments blocks in OPT_IN_RETRIAGE hard-code owner: "microsoft", repo: "apm". If this workflow is replicated to a fork or mirror org, these strings will silently route the agent to the upstream org's issue tracker. The existing ${{ github.event.issue.number }} template variables in the same blocks show the pattern is available.
    Suggested: Replace with owner: "${{ github.repository_owner }}", repo: "${{ github.event.repository.name }}" in both MCP call blocks.

  • [nit] Frontmatter comment says get_issue fails with McpError but PR body says it silently drops -- minor inconsistency at .github/workflows/triage-panel.md:210
    The distinction (silent drop vs. explicit McpError) is meaningful for future debuggability. The comment is more precise and should be preserved; the PR body prose is just looser.

CLI Logging Expert

  • [recommended] No operator-visible signal when DIFC previously silently dropped external issues
    If gh-aw's DIFC policy changes in future, search_issues will again silently return a subset of issues with no McpError, no warning, no count discrepancy visible in workflow logs. Operators have no baseline to diff against.
    Suggested: Add an observability note in SCHEDULED_SWEEP instructing the agent to log the count of candidates returned by search_issues so operators can spot a sudden drop to org-member-only counts in workflow logs.

  • [nit] DIFC note in SCHEDULED_SWEEP is repeated verbatim concept from frontmatter comment
    Creates a two-source-of-truth risk: a future editor updating one may miss the other.
    Suggested: Collapse the inline DIFC note to a single forward-reference sentence pointing to the frontmatter block.

  • [nit] Line length inconsistency in the PGS dispatch paragraph
    Cosmetic inconsistency introduced by the edit. Wrap at ~100 chars to match surrounding prose style.

DevX UX Expert

  • [recommended] Silent-drop fix is itself silent -- no triage output signals that external-contributor issues are now visible
    If the exemption is ever accidentally removed or misconfigured, regressions will again be invisible. A one-line notice confirming difc: read-integrity: low is active would make the behavior auditable.
    Suggested: Add a Step 0 or preamble instructing the agent to emit a single notice line confirming DIFC read-integrity: low is active before the sweep begins.

  • [recommended] MCP pseudo-code hardcodes owner: "microsoft" / repo: "apm" instead of using template variables
    Any fork or mirror will silently triage the wrong repository.
    Suggested: Replace with ${{ github.repository_owner }} and ${{ github.event.repository.name }}.

  • [nit] DIFC frontmatter block is accurate but opaque to contributors who don't know what DIFC is
    No link to DIFC documentation or PRINCIPLES.md.

  • [nit] Long line in Step 2 prose exceeds ~100-char wrap used elsewhere
    Cosmetic inconsistency.

Supply Chain Security Expert

  • [recommended] dispatch_workflow is the highest-risk write surface downstream of LOW-integrity input; no system-level guard verifies the dispatched content_id matches the triggering issue
    An adversarial external issue body can attempt prompt injection to steer the agent into passing an unintended node ID to dispatch_workflow. In-prompt rails are LLM-evaluated and fail open. If project-sync can move or modify project board items for arbitrary node IDs, a crafted issue body could trigger board manipulation.
    Suggested: Audit what project-sync does with an arbitrary content_id. If possible, add a system-level assertion that the content_id dispatched must match the node ID returned by the authenticated MCP call for the triggering issue number.

  • [recommended] Safety rationale labels in-prompt controls as equivalent to DIFC enforcement; they are not -- this should be acknowledged as a deliberate posture downgrade
    DIFC enforcement is system-level and model-blind; prompt-level rails are LLM-evaluated and fail open against sophisticated adversarial payloads.
    Suggested: Add: Note: prompt-level injection rails are LLM-evaluated and weaker than DIFC system enforcement. This exemption accepts that residual risk in exchange for triage coverage of external issues.

  • [nit] add-comment write action could indirectly echo attacker-influenced classification reasoning into org-visible triage comments
    Low severity: attacker gains mislabeling, not code execution.

  • [nit] gh issue view -> MCP get_issue migration is a net security improvement and the rationale should say so explicitly
    Authenticated reads, consistent with the pipeline, no risk of CLI resolving a different token from the environment.

OSS Growth Hacker

  • [recommended] Add a CHANGELOG entry explicitly calling out the external-contributor fix
    Silent triage drops are invisible to contributors -- they file an issue, hear nothing, and churn.
    Suggested: fix(triage): external contributor issues were silently dropped by the triage panel due to DIFC integrity filtering; they are now triaged correctly.

  • [nit] Consider a pinned note on Discussions or social acknowledging the fix for affected contributors
    Contributors who filed issues in the affected window and got no response may have written off the project.

Auth Expert

  • [recommended] add-comment has no content allow-list, creating a prompt-injection surface via LOW-integrity issue bodies
    The safe-outputs block constrains label names via an explicit allow-list and caps comment count (max:12), but does NOT restrict comment text content. The claim that the panel never re-emits raw body text is a prompt instruction, not a runtime enforcement boundary.
    Suggested: Add a post-generation reminder in the agent prompt that triage comments MUST be entirely generated from the panel's own analysis, or document the accepted risk in the difc: block.

  • [nit] The unauthenticated gh CLI -> authenticated MCP improvement is correct but overstated for public repos
    Public issues are readable without auth; the MCP path is genuinely better (rate-limit headroom, pipeline consistency) but the framing slightly oversells the risk.
    Suggested: Rephrase to note unauthenticated rate-limit (60/hr) vs. gh-aw runtime token.

  • [nit] Body-size cap and BATCH_ALLOW_LIST prompt-injection rails referenced in the safety rationale are not visible in this diff
    If a future edit to the skill removes those rails, this workflow's DIFC exemption loses its stated defence without any warning.
    Suggested: Add a cross-reference in the difc: comment block pointing to the specific skill file and section where those rails are enforced.

Doc Writer

  • [recommended] SCHEDULED_SWEEP loses the specific insight that mid-page DIFC drops make list_issues pagination unreliable, not just wasteful
    The removed sentence carried the precise technical reason why search_issues is structurally superior -- not just slower but semantically broken under DIFC. The replacement 'wasteful and error-prone' is vague.
    Suggested: Restore: client-side, which is wasteful and unreliable -- DIFC can silently drop non-org-member results mid-page, making "is the next page worth fetching?" reasoning unsound.

  • [nit] MANUAL_DISPATCH cross-reference (Same MCP calls as OPT_IN_RETRIAGE) is correct and sufficient -- no action needed
    PROSE's Orchestrated Composition principle favours cross-references over repetition.

  • [nit] CHANGELOG not updated, but this is acceptable for an internal workflow-only fix
    No CLI command, config key, or user-facing behavior changed. A CHANGELOG entry is optional unless the team logs internal workflow fixes -- see the OSS Growth Hacker finding for the counter-argument.

Test Coverage Expert -- inactive

documentation-only PR -- no runtime code paths to defend

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.

Generated by PR Review Panel for issue #1462 · ● 1.9M ·

@github-actions github-actions Bot removed the panel-review Trigger the apm-review-panel gh-aw workflow label May 23, 2026
@danielmeppiel danielmeppiel added this pull request to the merge queue May 23, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 23, 2026
@danielmeppiel danielmeppiel added this pull request to the merge queue May 23, 2026
Merged via the queue into main with commit 50f25db May 23, 2026
26 checks passed
@danielmeppiel danielmeppiel deleted the fix/1344-triage-panel-difc-external-issues branch May 23, 2026 15:46
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.

[BUG] triage-panel skips external contributor issues due to DIFC integrity policy

3 participants