Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Sentinel Security Journal

## 2026-03-31 - HMAC SHA-256 Webhook Signatures
**Vulnerability:** Base64 pseudo-signature was used for `X-Webhook-Signature` in `WebhookService`, allowing webhook payload forgery and tampering.
**Learning:** Prototype implementations may fall back to Base64 encoding instead of cryptographic HMAC signing.
**Prevention:** Always use HMAC-SHA256 with a secure secret key (`WEBHOOK_SECRET` or `SESSION_SECRET`) for outgoing webhook signature verification.
16 changes: 11 additions & 5 deletions server/services/WebhookService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';
import crypto from 'crypto';
import { v4 as uuidv4 } from 'uuid';
import { storage } from '../storage';
import { Client as NotionClient } from '@notionhq/client';
Expand Down Expand Up @@ -285,14 +286,19 @@ export class WebhookService {
}

/**
* Generate a signature for webhook payload verification
* Generate HMAC-SHA256 signature for webhook payload verification
* SECURITY RISK: Base64 encoding allows attackers to easily forge or tamper with webhook payloads.
* FIX: Generate HMAC-SHA256 signature using a secret key and timestamp to ensure payload authenticity and integrity.
*/
private generateSignature(payload: WebhookPayload): string {
// In a real app, you would use a crypto library to generate HMAC signatures
// For this prototype, we're using a simple approach
const timestamp = new Date().getTime().toString();
const secret = process.env.WEBHOOK_SECRET || process.env.SESSION_SECRET;
if (!secret) {
throw new Error('WEBHOOK_SECRET or SESSION_SECRET environment variable is required to sign webhook payloads securely.');
}
const timestamp = Date.now().toString();
const payloadStr = JSON.stringify(payload);
return `${timestamp}.${Buffer.from(payloadStr).toString('base64')}`;
const signature = crypto.createHmac('sha256', secret).update(`${timestamp}.${payloadStr}`).digest('hex');
return `t=${timestamp},v1=${signature}`;
}

/**
Expand Down
Loading