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
11 changes: 11 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "bounty-test-repository-security-guard",
"private": true,
"type": "module",
"scripts": {
"security:audit": "node scripts/security-audit.mjs"
}
}
103 changes: 103 additions & 0 deletions scripts/security-audit.mjs
Original file line number Diff line number Diff line change
@@ -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'}).`);
14 changes: 14 additions & 0 deletions security/denylist.json
Original file line number Diff line number Diff line change
@@ -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"
]
}