Skip to content

Instantly share code, notes, and snippets.

@austinsonger
Last active March 6, 2025 23:03
Show Gist options
  • Save austinsonger/f58fb1eccf7a4c1cbbef5cc569bea400 to your computer and use it in GitHub Desktop.
Save austinsonger/f58fb1eccf7a4c1cbbef5cc569bea400 to your computer and use it in GitHub Desktop.
Proof of Concept - Script Change & Tamper Detection for Webpages using Hono [PCI DSS 4.0.1 - 11.6.1 | HIPAA -164.312(c)(1) | FedRAMP SI-7, SI-4, CM-3, AU-2 | SOC 2 - CC7.1, CC7.2, CC6.6]

Proof of Concept - Script Change & Tamper Detection for Webpages using Hono

This solution provides a fully automated AWS-native mechanism to monitor, detect, and alert on unauthorized changes to page scripts. It leverages AWS CloudFront, Lambda@Edge, and the Hono framework to seamlessly enforce content integrity and real-time monitoring for scripts that are served as part of processing pages.

✨ Key Features

Feature Description
✅ Real-Time Script Integrity Checks Every time a script (.js) is served, its cryptographic hash is recalculated and compared to a known-good hash stored securely in AWS Systems Manager Parameter Store.
✅ Content Security Policy (CSP) Enforcement A strict CSP header is injected into all page responses to enforce only trusted scripts are allowed to run in the user’s browser.
✅ Subresource Integrity (SRI) Ready The system uses SHA-384 hashes, compatible with SRI if browser-based integrity checks are later added.
✅ Tamper Detection & Alerting If any unauthorized modification is detected (added, modified, or replaced script), the system sends a real-time SNS alert to security teams or SIEM platforms.
✅ Full Audit Logging Every script served is logged, including its path and hash value, creating a tamper-resistant audit trail.
✅ Edge Performance This logic runs at the CloudFront Edge, ensuring there is no added latency for the origin and allowing global enforcement at scale.
✅ Flexible Origin Support Works with S3 origins, ALB origins, or custom origins, making it suitable for both static and dynamic payment pages.

🔒 Critical Pages Where Script Integrity is Important

Type of Page Why Script Integrity Matters
Login Pages Unauthorized script changes could steal credentials (keylogging or credential harvesting). This maps to authentication security requirements in frameworks like SOC 2, ISO 27001, and NIST.
Customer Account Pages If scripts on “My Account” or “Profile” pages are compromised, attackers could exfiltrate personal data (PII) — relevant to GDPR, HIPAA, and SOC 2.
Checkout Pages (non-card payment) Even if payments aren’t made by card (e.g., Apple Pay, PayPal), checkout pages are still a high-value target. Protecting them is a best practice for e-commerce security.
Healthcare Patient Portals Unauthorized scripts could steal sensitive Protected Health Information (PHI), mapping to HIPAA 164.312(c)(1) (Integrity).
Banking and Financial Apps Financial institutions need to ensure all online banking pages are protected from tampering. This maps to GLBA, FFIEC, and ISO 27001 requirements.
Internal Admin Portals Unauthorized changes to internal admin dashboards could allow privilege escalation or data manipulation. Relevant to SOC 2 CC6/7, ISO 27001 A.9 (Access Control).
Support/Case Management Pages Unauthorized changes could expose support tickets, customer messages, or sensitive attachments. This is important for SOC 2 (confidentiality).
Document Upload Pages Any page allowing users to upload important documents (e.g., tax forms, contracts, medical records) benefits from ensuring uploaded scripts are unmodified.

📋 index.js

import { Hono } from 'hono';
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
import crypto from 'crypto';

// Setup AWS clients
const REGION = 'us-east-1';
const ssm = new SSMClient({ region: REGION });
const sns = new SNSClient({ region: REGION });

const TAMPER_ALERT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:ScriptTamperAlerts';
const HASH_PARAM_NAME = '/tampering/known-good-script-hashes';

const CSP_HEADER_VALUE = "script-src 'self' https://trusted-cdn.example.com; report-uri https://example.com/csp-report";

// In-memory cache for known-good hashes
let knownHashes = null;

// Load known-good hashes from Parameter Store
async function loadKnownHashes() {
    if (knownHashes) return knownHashes;
    const command = new GetParameterCommand({ Name: HASH_PARAM_NAME });
    const response = await ssm.send(command);
    knownHashes = JSON.parse(response.Parameter.Value);
    return knownHashes;
}

// Calculate SHA-384 hash for SRI
function calculateHash(content) {
    return 'sha384-' + crypto.createHash('sha384').update(content, 'utf8').digest('base64');
}

// Send tamper alert to SNS
async function sendTamperAlert(script, expected, actual) {
    const message = `TAMPER DETECTED: ${script}\nExpected: ${expected}\nActual: ${actual}`;
    console.error(message);

    const command = new PublishCommand({
        TopicArn: TAMPER_ALERT_TOPIC_ARN,
        Subject: 'Script Tamper Alert',
        Message: message,
    });

    await sns.send(command);
}

// Fetch script from origin (adjust if using custom origins)
async function fetchFromOrigin(path) {
    const originUrl = `https://your-origin-bucket.s3.amazonaws.com${path}`;
    const response = await fetch(originUrl);
    return await response.text();
}

// Hono app setup
const app = new Hono();

// Middleware to inject CSP Header
app.use('*', async (c, next) => {
    c.res.headers.set('Content-Security-Policy', CSP_HEADER_VALUE);
    await next();
});

// Handle JavaScript files
app.get('*.js', async (c) => {
    const path = c.req.path;
    const scriptContent = await fetchFromOrigin(path);
    const scriptHash = calculateHash(scriptContent);

    const hashes = await loadKnownHashes();
    const scriptName = path.split('/').pop();
    const expectedHash = hashes[scriptName];

    console.log(`Serving ${path} with hash ${scriptHash}`);

    if (expectedHash && expectedHash !== scriptHash) {
        await sendTamperAlert(path, expectedHash, scriptHash);
    }

    return new Response(scriptContent, { headers: { 'Content-Type': 'application/javascript' } });
});

export default app;

🧰 What This Control Covers Across Frameworks

Control Area Explanation
Change Detection Tracks any changes to critical page scripts.
Tamper Detection Ensures scripts are not modified maliciously (formjacking, Magecart, etc.).
Audit Trail Logs all script changes and detections (maps to audit and event logging controls).
Incident Response Trigger Automatically sends alerts (helps with detection, response, and breach notification processes).
Integrity Protection Ensures only approved, signed scripts are allowed (integrity).
Defense-in-Depth Works with CSP and SRI for additional browser-based enforcement.
  • It addresses file integrity monitoring for scripts (relevant to many standards).
  • It includes real-time logging and alerting (key to auditability and incident response).
  • It fits into any Secure Software Development Life Cycle (SDLC) by adding controls at the delivery layer (edge/CDN).

📜 Requirement Mapping

Framework Relevant Requirements
NIST 800-53 (Rev 5) - SI-7 (Software, Firmware, and Information Integrity)- SI-4 (Information System Monitoring)- AU-2 (Auditable Events)- AU-6 (Audit Review and Reporting)
ISO 27001 (Annex A) - A.12.2.1 (Control of Technical Vulnerabilities)- A.12.4.1 (Event Logging)- A.12.1.2 (Change Management)- A.14.2.4 (System Security Testing)
SOC 2 (TSC) - CC7.1 (Detect and Monitor Unauthorized Changes)- CC7.2 (Identify Changes to Components)- CC6.6 (Prevent and Detect System Vulnerabilities)
CIS Controls v8 - Control 2 (Inventory and Control of Software Assets)- Control 5 (Account Management - Integrity Monitoring)- Control 6 (Access Control Management)- Control 13 (Data Protection)- Control 14 (Security Awareness and Skills Training - Change Risk Awareness)
FedRAMP (Moderate) - SI-7 (Software and Information Integrity)- SI-4 (System Monitoring)- CM-3 (Configuration Change Control)- AU-2 (Audit Events)
**HIPAA ** - 164.312(c)(1) Integrity (Implement policies to protect electronic PHI from alteration/destruction)- 164.308(a)(5)(ii)(B) Protection from Malicious Software- 164.312(b) Audit Controls
Payment Card Industry (PCI DSS) - 11.6.1 (The primary driver)- 6.2 (System Components Protection)- 10.3 (Audit Trail Requirements)

🚀 Deployment Script

zip function.zip index.js

aws lambda create-function \
    --function-name hono-singlefile \
    --runtime nodejs18.x \
    --role arn:aws:iam::123456789012:role/lambda-edge-role \
    --handler index.default \
    --zip-file fileb://function.zip \
    --region us-east-1

aws cloudfront update-distribution \
    --id YOUR_CLOUDFRONT_DISTRIBUTION_ID \
    --default-cache-behavior '{
        "LambdaFunctionAssociations": [
            {
                "EventType": "origin-response",
                "LambdaFunctionARN": "'$(aws lambda get-function --function-name hono-singlefile --query "Configuration.FunctionArn" --output text):$LATEST'"
            }
        ]
    }'

Functionality

🧩 1️⃣ Imports / Dependencies

import { Hono } from 'hono';
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
import crypto from 'crypto';
Line What It Does
import { Hono } from 'hono'; Pulls in Hono framework to handle routes and requests. This makes building Lambda@Edge handlers cleaner.
import { SSMClient, GetParameterCommand } Loads AWS SDK v3 clients to talk to SSM Parameter Store, which stores the known-good hashes for payment scripts.
import { SNSClient, PublishCommand } Loads AWS SDK v3 clients to talk to SNS, which will send tamper alerts if unauthorized changes are detected.
import crypto from 'crypto'; Built-in Node.js library to calculate SHA-384 hashes for Subresource Integrity (SRI) checks.

🧩 2️⃣ Configuration / Constants

const REGION = 'us-east-1';
const ssm = new SSMClient({ region: REGION });
const sns = new SNSClient({ region: REGION });

const TAMPER_ALERT_TOPIC_ARN = 'arn:aws:sns:us-east-1:123456789012:ScriptTamperAlerts';
const HASH_PARAM_NAME = '/pci-dss/known-good-script-hashes';

const CSP_HEADER_VALUE = "script-src 'self' https://trusted-cdn.example.com; report-uri https://example.com/csp-report";
What What It Does
REGION Sets AWS region to us-east-1, required for Lambda@Edge.
ssm / sns Creates AWS SDK clients to interact with Parameter Store and SNS.
TAMPER_ALERT_TOPIC_ARN The SNS Topic ARN that gets notified if tampering is detected.
HASH_PARAM_NAME SSM Parameter name where known-good hashes are stored.
CSP_HEADER_VALUE Sets a strict Content Security Policy (CSP) header to help browsers block unauthorized scripts. This is critical to PCI DSS 11.6.1.

🧩 3️⃣ Cache and Load Known-Good Hashes

let knownHashes = null;

async function loadKnownHashes() {
    if (knownHashes) return knownHashes;
    const command = new GetParameterCommand({ Name: HASH_PARAM_NAME });
    const response = await ssm.send(command);
    knownHashes = JSON.parse(response.Parameter.Value);
    return knownHashes;
}
What What It Does
knownHashes Simple in-memory cache to avoid reloading hashes on every request.
loadKnownHashes() - Talks to SSM Parameter Store to fetch the JSON list of trusted script hashes.- Example stored value: { "payment.js": "sha384-abc123..." }

🧩 4️⃣ Hash Calculator

function calculateHash(content) {
    return 'sha384-' + crypto.createHash('sha384').update(content, 'utf8').digest('base64');
}
What What It Does
calculateHash() Hashes the content of a script using SHA-384, which is the recommended algorithm for Subresource Integrity (SRI).
Output Example sha384-ABC123...

🧩 5️⃣ Tamper Detection Alert

async function sendTamperAlert(script, expected, actual) {
    const message = `TAMPER DETECTED: ${script}\nExpected: ${expected}\nActual: ${actual}`;
    console.error(message);

    const command = new PublishCommand({
        TopicArn: TAMPER_ALERT_TOPIC_ARN,
        Subject: 'Script Tamper Alert',
        Message: message,
    });

    await sns.send(command);
}
What What It Does
sendTamperAlert() - Sends a formatted message to SNS.- This could trigger email, Slack, PagerDuty, etc.- Message contains the script name, expected hash, and actual hash.

🧩 6️⃣ Fetch the Actual Script from S3 (or Origin)

async function fetchFromOrigin(path) {
    const originUrl = `https://your-origin-bucket.s3.amazonaws.com${path}`;
    const response = await fetch(originUrl);
    return await response.text();
}
What What It Does
fetchFromOrigin() - Fetches the script file from your origin.- In this case, it assumes an S3 bucket, but you could point to any origin (e.g., ALB, EC2, etc.).

🧩 7️⃣ Initialize the Hono App

const app = new Hono();
What What It Does
app This is the main Hono instance, where all routes, middleware, and logic are attached.

🧩 8️⃣ CSP Header Middleware

app.use('*', async (c, next) => {
    c.res.headers.set('Content-Security-Policy', CSP_HEADER_VALUE);
    await next();
});
What What It Does
Adds CSP Every response (all paths) gets the strict CSP Header. This protects against malicious injected scripts.

🧩 9️⃣ Main Route Handler — Script Integrity Check

app.get('*.js', async (c) => {
    const path = c.req.path;
    const scriptContent = await fetchFromOrigin(path);
    const scriptHash = calculateHash(scriptContent);

    const hashes = await loadKnownHashes();
    const scriptName = path.split('/').pop();
    const expectedHash = hashes[scriptName];

    console.log(`Serving ${path} with hash ${scriptHash}`);

    if (expectedHash && expectedHash !== scriptHash) {
        await sendTamperAlert(path, expectedHash, scriptHash);
    }

    return new Response(scriptContent, { headers: { 'Content-Type': 'application/javascript' } });
});
What What It Does
Match .js This handler only runs for files ending in .js (your payment scripts).
Fetch Script Downloads the script directly from origin (S3).
Hash Script Calculates SHA-384 hash of the script content.
Compare Hash Compares to known-good hash from SSM Parameter Store.
Tamper Alert If the hash doesn’t match, send a SNS alert immediately.
Serve Script If no tamper detected, the script is served back to the user.

🧩 🔟 Export Hono App

export default app;
What What It Does
Expose App Makes sure this works with Lambda@Edge’s expected handler signature.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment