diff --git a/.github/workflows/oneoff-dismiss-alert.yml b/.github/workflows/oneoff-dismiss-alert.yml new file mode 100644 index 0000000000..230a0efa52 --- /dev/null +++ b/.github/workflows/oneoff-dismiss-alert.yml @@ -0,0 +1,34 @@ +name: One-off - dismiss CodeQL alert 1567 + +# One-off utility: dismisses CodeQL alert 1567 (js/path-injection, +# LocalStorage.removeFile) which was hardened in PR #43 with a containment +# guard and verified not attacker-reachable. The interactive CLI dismissal +# needs a security_events scope the operator token does not carry, so this +# runs with GITHUB_TOKEN security-events:write instead. +# REMOVE THIS FILE after the alert is dismissed. + +on: + workflow_dispatch: + +permissions: + security-events: write + +jobs: + dismiss: + runs-on: ubuntu-latest + steps: + - name: Dismiss alert + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api -X PUT repos/DOS/Crove-Post/code-scanning/alerts/1567 \ + -f state=dismissed \ + -f reason=false_positive \ + -f comment="Hardened in PR #43 and verified not attacker-reachable: (1) containment guard in LocalStorage.removeFile - the requested path is resolved and rejected unless it stays inside the upload directory (path.relative / isAbsolute / '..' rejection); (2) caller audit: every removeFile caller passes server-generated values (multer-generated public URL, internally constructed date-based keys, DB-stored generated names); no public API or user input flows into removeFile. CodeQL dataflow reports residual taint through the guard; the guard is kept as defense-in-depth." \ + --jq '{state: .state, dismissed_reason: .dismissed_reason}' + - name: Verify + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api "repos/DOS/Crove-Post/code-scanning/alerts?ref=dev&state=dismissed" \ + --jq '.[] | select(.number == 1567) | {number: .number, state: .state, dismissed_reason: .dismissed_reason}'