Welcome to Sock Darning! Have a sock with a thinning heel or a toe hole you want to fix? Share your damage maps, yarn blend questions, and stitch experiments with other darning-minded visitors. Reading along is welcome too, so pull up a chair and join the conversation.

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

  • Thread starter Thread starter Austin Songer, CISSP
  • Start date Start date
A

Austin Songer, CISSP

Guest
poc.md

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

https://gist.github.com/#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

FeatureDescription
✅ Real-Time Script Integrity ChecksEvery 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) EnforcementA 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) ReadyThe system uses SHA-384 hashes, compatible with SRI if browser-based integrity checks are later added.
✅ Tamper Detection & AlertingIf 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 LoggingEvery script served is logged, including its path and hash value, creating a tamper-resistant audit trail.
✅ Edge PerformanceThis logic runs at the CloudFront Edge, ensuring there is no added latency for the origin and allowing global enforcement at scale.
✅ Flexible Origin SupportWorks 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 PageWhy Script Integrity Matters
Login PagesUnauthorized 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 PagesIf 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 PortalsUnauthorized scripts could steal sensitive Protected Health Information (PHI), mapping to HIPAA 164.312(c)(1) (Integrity).
Banking and Financial AppsFinancial institutions need to ensure all online banking pages are protected from tampering. This maps to GLBA, FFIEC, and ISO 27001 requirements.
Internal Admin PortalsUnauthorized 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 PagesUnauthorized changes could expose support tickets, customer messages, or sensitive attachments. This is important for SOC 2 (confidentiality).
Document Upload PagesAny page allowing users to upload important documents (e.g., tax forms, contracts, medical records) benefits from ensuring uploaded scripts are unmodified.

index.js​

https://gist.github.com/#--indexjs

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​

https://gist.github.com/#-what-this-control-covers-across-frameworks
Control AreaExplanation
Change DetectionTracks any changes to critical page scripts.
Tamper DetectionEnsures scripts are not modified maliciously (formjacking, Magecart, etc.).
Audit TrailLogs all script changes and detections (maps to audit and event logging controls).
Incident Response TriggerAutomatically sends alerts (helps with detection, response, and breach notification processes).
Integrity ProtectionEnsures only approved, signed scripts are allowed (integrity).
Defense-in-DepthWorks 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​

https://gist.github.com/#-requirement-mapping
FrameworkRelevant 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​

https://gist.github.com/#-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​

https://gist.github.com/#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';
LineWhat 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";
WhatWhat It Does
REGIONSets AWS region to us-east-1, required for Lambda@Edge.
ssm / snsCreates AWS SDK clients to interact with Parameter Store and SNS.
TAMPER_ALERT_TOPIC_ARNThe SNS Topic ARN that gets notified if tampering is detected.
HASH_PARAM_NAMESSM Parameter name where known-good hashes are stored.
CSP_HEADER_VALUESets 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;
}
WhatWhat It Does
knownHashesSimple 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');
}
WhatWhat It Does
calculateHash()Hashes the content of a script using SHA-384, which is the recommended algorithm for Subresource Integrity (SRI).
Output Examplesha384-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);
}
WhatWhat 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();
}
WhatWhat 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();
WhatWhat It Does
appThis 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();
});
WhatWhat It Does
Adds CSPEvery 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' } });
});
WhatWhat It Does
Match .jsThis handler only runs for files ending in .js (your payment scripts).
Fetch ScriptDownloads the script directly from origin (S3).
Hash ScriptCalculates SHA-384 hash of the script content.
Compare HashCompares to known-good hash from SSM Parameter Store.
Tamper AlertIf the hash doesn’t match, send a SNS alert immediately.
Serve ScriptIf no tamper detected, the script is served back to the user.

Export Hono App


export default app;
WhatWhat It Does
Expose AppMakes sure this works with Lambda@Edge’s expected handler signature.
 
Top