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
34 changes: 34 additions & 0 deletions webhook-payload-redaction-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Webhook Payload Redaction Guard

Self-contained Enterprise Tooling slice for `SCIBASE-AI/SCIBASE.AI#19`.

The guard validates outbound institutional webhook/API payloads before delivery.
It checks schema allowlists, private-project field leakage, PII and direct
identifier exposure, private storage links, data-residency routing, event
signature metadata, and dataset access safety. It emits deterministic event
decisions so unsafe payloads are blocked or redacted before institutional sync.

This is intentionally separate from webhook replay ledgers, admin notification
escalation, connector certification, API change governance, data export approval,
deposit reconciliation, SCIM/HRIS deprovisioning, LMS roster passback, usage
cost allocation, incident response, data residency policy, and secret rotation
slices. Its job is outbound payload minimization and redaction before delivery.

## Run

```bash
npm run check
npm test
npm run demo
npm run demo:video
```

## Outputs

- `reports/summary.json`
- `reports/reviewer-packet.md`
- `reports/summary.svg`
- `reports/demo.webm`

All data is synthetic. The module does not call live webhook delivery,
repository sync, LMS sync, identity, storage, or external provider systems.
173 changes: 173 additions & 0 deletions webhook-payload-redaction-guard/demo-video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
const fs = require("fs");
const os = require("os");
const path = require("path");
const { execFileSync } = require("child_process");

const reportDir = path.join(__dirname, "reports");
const outputPath = path.join(reportDir, "demo.webm");

const chromeCandidates = [
process.env.CHROME_PATH,
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
].filter(Boolean);

function findBrowser() {
const found = chromeCandidates.find((candidate) => fs.existsSync(candidate));
if (!found) {
throw new Error("Chrome or Edge was not found. Set CHROME_PATH to generate reports/demo.webm.");
}
return found;
}

function fileUrl(filePath) {
return `file:///${filePath.replace(/\\/g, "/")}`;
}

const html = String.raw`<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Webhook payload redaction guard demo</title>
<style>
html, body { margin: 0; background: #f8fafc; }
canvas { width: 960px; height: 540px; }
pre { white-space: pre-wrap; word-break: break-all; color: #f8fafc; font-size: 1px; }
</style>
</head>
<body>
<canvas id="stage" width="960" height="540"></canvas>
<pre id="out">recording</pre>
<script>
const canvas = document.getElementById("stage");
const ctx = canvas.getContext("2d");
const out = document.getElementById("out");
const checks = [
["Schema allowlist", "Only approved event types and payload fields can leave the platform."],
["Redaction", "PII, private notes, reviewer comments, and storage URLs are blocked."],
["Residency", "Destination regions must match institutional routing policy."],
["Signing", "Webhook events require production signature metadata before delivery."]
];

function roundRect(x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}

function draw(frame) {
const t = frame / 52;
ctx.fillStyle = "#f8fafc";
ctx.fillRect(0, 0, 960, 540);
ctx.fillStyle = "#111827";
ctx.font = "bold 34px Arial";
ctx.fillText("Webhook Payload Redaction Guard", 48, 64);
ctx.font = "19px Arial";
ctx.fillStyle = "#475569";
ctx.fillText("SCIBASE #19 enterprise institutional delivery safety demo", 48, 98);

ctx.fillStyle = "#e5e7eb";
roundRect(48, 126, 864, 30, 8);
ctx.fill();
ctx.fillStyle = "#4f46e5";
roundRect(48, 126, 864 * Math.min(1, t), 30, 8);
ctx.fill();

checks.forEach(([title, text], index) => {
const y = 194 + index * 70;
const active = Math.floor(t * 4.4) >= index;
ctx.fillStyle = active ? "#ffffff" : "#eef2f7";
ctx.strokeStyle = active ? "#4f46e5" : "#cbd5e1";
ctx.lineWidth = active ? 3 : 1;
roundRect(48, y, 864, 54, 8);
ctx.fill();
ctx.stroke();
ctx.fillStyle = active ? "#4338ca" : "#64748b";
ctx.font = "bold 18px Arial";
ctx.fillText(title, 70, y + 22);
ctx.fillStyle = "#334155";
ctx.font = "16px Arial";
ctx.fillText(text, 70, y + 44);
});

ctx.fillStyle = "#64748b";
ctx.font = "15px Arial";
ctx.fillText("Synthetic data only. No live webhook delivery, repository sync, LMS sync, identity, storage, or provider calls.", 48, 504);
}

async function main() {
if (!window.MediaRecorder) {
out.textContent = "ERROR: MediaRecorder unavailable";
return;
}
draw(0);
const stream = canvas.captureStream(12);
const recorder = new MediaRecorder(stream, { mimeType: "video/webm;codecs=vp8" });
const chunks = [];
recorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
chunks.push(event.data);
}
};
recorder.onstop = () => {
const blob = new Blob(chunks, { type: "video/webm" });
const reader = new FileReader();
reader.onloadend = () => {
out.textContent = reader.result;
};
reader.readAsDataURL(blob);
};
recorder.start();
let frame = 0;
const timer = setInterval(() => {
draw(frame);
frame += 1;
if (frame >= 52) {
clearInterval(timer);
recorder.stop();
stream.getTracks().forEach((track) => track.stop());
}
}, 83);
}

main();
</script>
</body>
</html>`;

fs.mkdirSync(reportDir, { recursive: true });

const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "webhook-redaction-demo-"));
const htmlPath = path.join(tempDir, "demo.html");
const profileDir = path.join(tempDir, "profile");
fs.writeFileSync(htmlPath, html, "utf8");

const stdout = execFileSync(
findBrowser(),
[
"--headless=new",
"--disable-gpu",
"--disable-dev-shm-usage",
"--autoplay-policy=no-user-gesture-required",
"--run-all-compositor-stages-before-draw",
"--virtual-time-budget=7500",
`--user-data-dir=${profileDir}`,
"--dump-dom",
fileUrl(htmlPath)
],
{ encoding: "utf8", maxBuffer: 30 * 1024 * 1024 }
);

const match = stdout.match(/data:video\/webm;base64,([A-Za-z0-9+/=]+)/);
if (!match) {
throw new Error(`Demo video generation failed. Browser output ended with: ${stdout.slice(-600)}`);
}

fs.writeFileSync(outputPath, Buffer.from(match[1], "base64"));
console.log(`Generated ${path.relative(process.cwd(), outputPath)}`);
18 changes: 18 additions & 0 deletions webhook-payload-redaction-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const fs = require("fs");
const path = require("path");
const { project } = require("./sample-data");
const { buildReviewPacket, renderMarkdownReport, renderSvgSummary } = require("./index");

const reportDir = path.join(__dirname, "reports");
fs.mkdirSync(reportDir, { recursive: true });

const packet = buildReviewPacket(project);

fs.writeFileSync(path.join(reportDir, "summary.json"), `${JSON.stringify(packet, null, 2)}\n`, "utf8");
fs.writeFileSync(path.join(reportDir, "reviewer-packet.md"), renderMarkdownReport(packet), "utf8");
fs.writeFileSync(path.join(reportDir, "summary.svg"), renderSvgSummary(packet), "utf8");

console.log(`Generated reports for ${packet.guard}`);
console.log(`Decision: ${packet.decision}`);
console.log(`Score: ${packet.score}`);
console.log(`Findings: ${packet.findings.length}`);
Loading