From a38e9366eda72035149f679f60b3b6a05a6171fd Mon Sep 17 00:00:00 2001 From: TJCurnutte <211682330+TJCurnutte@users.noreply.github.com> Date: Wed, 13 May 2026 11:55:21 -0400 Subject: [PATCH] fix: add xyz dll security guard --- SECURITY.md | 11 ++++ package.json | 8 +++ scripts/security-audit.mjs | 103 +++++++++++++++++++++++++++++++++++++ security/denylist.json | 14 +++++ 4 files changed, 136 insertions(+) create mode 100644 SECURITY.md create mode 100644 package.json create mode 100644 scripts/security-audit.mjs create mode 100644 security/denylist.json diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e11bc69 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security guardrail + +Issue #9 reports `xyz.dll` as a vulnerable artifact. This repository should not ship or reference that DLL. + +Run the repository security guard before submitting changes: + +```bash +npm run security:audit +``` + +The audit checks tracked source paths and filenames for blocked artifacts listed in `security/denylist.json` so the vulnerable DLL cannot be reintroduced silently. diff --git a/package.json b/package.json new file mode 100644 index 0000000..37f1490 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "bounty-test-repository-security-guard", + "private": true, + "type": "module", + "scripts": { + "security:audit": "node scripts/security-audit.mjs" + } +} diff --git a/scripts/security-audit.mjs b/scripts/security-audit.mjs new file mode 100644 index 0000000..d39f2f4 --- /dev/null +++ b/scripts/security-audit.mjs @@ -0,0 +1,103 @@ +#!/usr/bin/env node +import { readdir, readFile, stat } from 'node:fs/promises'; +import path from 'node:path'; +import process from 'node:process'; + +const root = process.cwd(); +const denylistPath = path.join(root, 'security', 'denylist.json'); +const denylist = JSON.parse(await readFile(denylistPath, 'utf8')); + +const blockedArtifacts = (denylist.blockedArtifacts || []).map((entry) => ({ + name: String(entry.name || '').toLowerCase(), + reason: String(entry.reason || 'blocked artifact'), +})).filter((entry) => entry.name); + +const allowedReferences = new Set( + (denylist.allowedReferences || []).map((entry) => normalize(entry)), +); + +const skipDirs = new Set([ + '.git', + 'node_modules', + 'dist', + 'build', + '.next', + 'coverage', + 'temp-uploads', + 'issues', +]); + +const textExtensions = new Set([ + '.c', '.cc', '.cpp', '.css', '.h', '.hpp', '.html', '.js', '.jsx', + '.json', '.mjs', '.md', '.ts', '.tsx', '.txt', '.yaml', '.yml', +]); + +function normalize(filePath) { + return filePath.split(path.sep).join('/'); +} + +function relative(filePath) { + return normalize(path.relative(root, filePath)); +} + +function shouldSkipDirectory(dirName) { + return skipDirs.has(dirName) || dirName.startsWith('.cache'); +} + +function isTextFile(filePath) { + return textExtensions.has(path.extname(filePath).toLowerCase()); +} + +async function* walk(dir) { + for (const entry of await readdir(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (!shouldSkipDirectory(entry.name)) { + yield* walk(fullPath); + } + continue; + } + if (entry.isFile()) { + yield fullPath; + } + } +} + +const violations = []; + +for await (const filePath of walk(root)) { + const rel = relative(filePath); + const fileName = path.basename(filePath).toLowerCase(); + + for (const artifact of blockedArtifacts) { + if (fileName === artifact.name) { + violations.push(`${rel}: blocked filename ${artifact.name} — ${artifact.reason}`); + } + } + + if (allowedReferences.has(rel) || !isTextFile(filePath)) { + continue; + } + + const info = await stat(filePath); + if (info.size > 1024 * 1024) { + continue; + } + + const content = (await readFile(filePath, 'utf8')).toLowerCase(); + for (const artifact of blockedArtifacts) { + if (content.includes(artifact.name)) { + violations.push(`${rel}: references ${artifact.name} — ${artifact.reason}`); + } + } +} + +if (violations.length) { + console.error('Blocked vulnerable artifacts found:'); + for (const violation of violations) { + console.error(`- ${violation}`); + } + process.exit(1); +} + +console.log(`No blocked artifacts found (${blockedArtifacts.map((a) => a.name).join(', ') || 'empty denylist'}).`); diff --git a/security/denylist.json b/security/denylist.json new file mode 100644 index 0000000..0b5bd11 --- /dev/null +++ b/security/denylist.json @@ -0,0 +1,14 @@ +{ + "blockedArtifacts": [ + { + "name": "xyz.dll", + "reason": "Issue #9 reports xyz.dll as vulnerable; it must not be shipped, referenced, or reintroduced." + } + ], + "allowedReferences": [ + "SECURITY.md", + "package.json", + "scripts/security-audit.mjs", + "security/denylist.json" + ] +}