chore: resolve open code-scanning alerts (workflow permissions + Gusto URL sanitization) (#81)
Some checks failed
Deploy / deploy (push) Has been cancelled

* 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
This commit is contained in:
Adam Moussa 2026-07-23 16:38:07 -04:00 • committed by GitHub
parent bf235e70d7
commit 849f33a0bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 26 additions and 2 deletions

View file

@ -3,6 +3,9 @@ on:
pull_request: pull_request:
branches: [main] branches: [main]
permissions:
contents: read
jobs: jobs:
ci: ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@fd60e4c9041784f666ac0fdefb9bec3c7fbf5143 # main uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@fd60e4c9041784f666ac0fdefb9bec3c7fbf5143 # main

View file

@ -1,6 +1,10 @@
name: Dependency Review name: Dependency Review
on: on:
pull_request: pull_request:
permissions:
contents: read
jobs: jobs:
review: review:
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@fd60e4c9041784f666ac0fdefb9bec3c7fbf5143 # main uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@fd60e4c9041784f666ac0fdefb9bec3c7fbf5143 # main

View file

@ -36,6 +36,24 @@ const formatCurrency = (v) =>
currency: "USD", currency: "USD",
}).format(Number(v || 0)); }).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) { function classifyEmail(from, subject, text) {
const fromAddr = (from?.text || from || "").toLowerCase(); const fromAddr = (from?.text || from || "").toLowerCase();
const subj = (subject || "").toLowerCase(); const subj = (subject || "").toLowerCase();
@ -54,8 +72,7 @@ function classifyEmail(from, subject, text) {
} }
const strippedSubj = subj.replace(/^fwd?:\s*/i, ""); const strippedSubj = subj.replace(/^fwd?:\s*/i, "");
const body = (text || "").toLowerCase(); const bodyMentionsGusto = bodyMentionsAllowedGustoUrl(text || "");
const bodyMentionsGusto = body.includes("gusto.com");
if (bodyMentionsGusto && strippedSubj.includes("payroll confirmation")) { if (bodyMentionsGusto && strippedSubj.includes("payroll confirmation")) {
return "employee"; return "employee";