Skip to content

accept sendgrid webhook posts and logs them#1256

Open
jacksonloper wants to merge 7 commits intomainfrom
JacksonLoper/sendgridwebhook
Open

accept sendgrid webhook posts and logs them#1256
jacksonloper wants to merge 7 commits intomainfrom
JacksonLoper/sendgridwebhook

Conversation

@jacksonloper
Copy link
Collaborator

@jacksonloper jacksonloper commented Feb 27, 2026

This is the first step in addressing #713.

Next steps. Once it is merged and live, I will make sendgrid actually send to the endpoint, and look at the logs. If they look sensible, I will then submit a second PR that (1) checks the sendgrid signature and (2) if it is right uses the sendgrid information to update the voter roll history appropriately with "dropped" or "bounced" events.

This PR also goes through a Large Number of Cases where we were logging PII and instead logs pseudoanonymized versions. That is, instead of email, you'll see some [abc123], and if two different logs are about the same email and the logs are happening on the same pod that hasn't been restarted then both logs will report [abc123]. This strikes I think an acceptable balance for now between

  • we don't want to store PII to the logs
  • it would be nice to be able to tell the difference between "this IP address has problems 60 times" and "60 different ip addresses all had problems"

This PR also supresses debug logs in production.

@netlify
Copy link

netlify bot commented Feb 27, 2026

Deploy Preview for bettervoting ready!

Name Link
🔨 Latest commit 5d6998d
🔍 Latest deploy log https://app.netlify.com/projects/bettervoting/deploys/69ab0e7c202fa900073e89f6
😎 Deploy Preview https://deploy-preview-1256--bettervoting.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

const signature = String(req.headers['x-twilio-email-event-webhook-signature'] ?? 'missing');
const timestamp = String(req.headers['x-twilio-email-event-webhook-timestamp'] ?? 'missing');
Logger.info(req, `SendGridWebhook signature=${signature} timestamp=${timestamp}`);
Logger.info(req, `SendGridWebhook body: ${rawBody.toString('utf8')}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this contain the email address or other sensitive info?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Almost certainly! I fixed it.

Copy link
Collaborator

@JonBlauvelt JonBlauvelt left a comment

Choose a reason for hiding this comment

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

Overall looks like a great improvement to clean all of this up! Just had a couple questions/concerns/clarifications

})
if (duplicateRolls.length > 0) {
throw new BadRequest(`Some submitted voters already exist: ${duplicateRolls.map( (roll: ElectionRoll) => `${roll.voter_id ? roll.voter_id : ''} ${roll.email ? roll.email : ''}`).join(',')}`)
throw new BadRequest(`Some submitted voters already exist (${duplicateRolls.length} duplicates found)`)
Copy link
Collaborator

Choose a reason for hiding this comment

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

At first glance, this seems odd to me - the client is providing either an id or an email as input. It seems unusual to redact that in the error response and not inform the client where the problem is. Am I missing it? Is there some vulnerability here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My impression is that (1) this kind of exception gets logged and (2) user is not going to be well-poised to make use of the backend error anyway. In theory the frontend should not let you submit duplicates, so its really only a race condition I guess (or something weird and nefarious)?

We could just status(400).json reply to them I guess, but I'm not sure if there's a Clever Way to use BadRequest that (1) gives the info but (2) doesn't let it get logged.

So that's my ill-formed thinking around it.

// PROMINENT LOGGING - This action should be highly visible in logs
Logger.error(req, `🚨 BREAK GLASS ACTION 🚨 ${className}.revealVoterIdByEmail - Election: ${electionId}, Email: ${email}, Actor: ${actor}`);
console.error(`🚨🚨🚨 BREAK GLASS: Voter ID revealed for ${email} in election ${electionId} by user ${actor} 🚨🚨🚨`);
Logger.error(req, `BREAK GLASS ACTION - ${className}.revealVoterIdByEmail - Election: ${electionId}, Email: ${logSafeHash(email)}, Actor: ${logSafeHash(actor)}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

this seems good - do we still have some way to understand who this was later?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If a voter id is revealed, that fact gets added to their history in the rolls. The people who can read that history are system_admin, owner, admin, auditor, credentialer.

}

debug(context?:ILoggingContext, message?: any, ...optionalParams: any[]):void{
if (process.env.NODE_ENV === 'production') return;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we not have a way to just configure the logging level on the environment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Haven't heard from Arend, but I don't think so. I can't find anything in the argocd or the helm chart. So... I guess this works?

Copy link
Collaborator

Choose a reason for hiding this comment

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

😭

@jacksonloper jacksonloper requested a review from JonBlauvelt March 9, 2026 19:08
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.

2 participants