From 849f33a0bcd060cd9606aa940ec95aecaa6c4975 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:38:07 -0400 Subject: [PATCH] chore: resolve open code-scanning alerts (workflow permissions + Gusto URL sanitization) (#81) * ci: add least-privilege permissions blocks to workflow callers Resolves code scanning alerts #4 and #5 (actions/missing-workflow-permissions). Both callable workflows only need contents: read; the dependency-review callable already declares it internally, this caps the caller token to match. * enhance(email): improve detection of allowed Gusto URLs in email classification Resolves code scanning alert #2 --- .github/workflows/ci.yaml | 3 +++ .github/workflows/dependency-review.yml | 4 ++++ src/processPayrollEmail.js | 21 +++++++++++++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f990b1e..dd4f80b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,6 +3,9 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: ci: uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@fd60e4c9041784f666ac0fdefb9bec3c7fbf5143 # main diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 2cd8119..42002bb 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,6 +1,10 @@ name: Dependency Review on: pull_request: + +permissions: + contents: read + jobs: review: uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@fd60e4c9041784f666ac0fdefb9bec3c7fbf5143 # main diff --git a/src/processPayrollEmail.js b/src/processPayrollEmail.js index 161ffed..3979b06 100644 --- a/src/processPayrollEmail.js +++ b/src/processPayrollEmail.js @@ -36,6 +36,24 @@ const formatCurrency = (v) => currency: "USD", }).format(Number(v || 0)); +function isAllowedGustoHost(hostname) { + const h = (hostname || "").toLowerCase(); + return h === "gusto.com" || h.endsWith(".gusto.com"); +} + +function bodyMentionsAllowedGustoUrl(text) { + const urlMatches = (text || "").match(/\bhttps?:\/\/[^\s<>"')]+/gi) || []; + for (const rawUrl of urlMatches) { + try { + const parsed = new URL(rawUrl); + if (isAllowedGustoHost(parsed.hostname)) return true; + } catch { + // Ignore malformed URLs in email text. + } + } + return false; +} + function classifyEmail(from, subject, text) { const fromAddr = (from?.text || from || "").toLowerCase(); const subj = (subject || "").toLowerCase(); @@ -54,8 +72,7 @@ function classifyEmail(from, subject, text) { } const strippedSubj = subj.replace(/^fwd?:\s*/i, ""); - const body = (text || "").toLowerCase(); - const bodyMentionsGusto = body.includes("gusto.com"); + const bodyMentionsGusto = bodyMentionsAllowedGustoUrl(text || ""); if (bodyMentionsGusto && strippedSubj.includes("payroll confirmation")) { return "employee";