Files
NetMesh/scripts/spam-comment-filter.cjs

95 lines
2.5 KiB
JavaScript
Raw Normal View History

"use strict";
const TRUSTED_AUTHOR_ASSOCIATIONS = new Set([
"OWNER",
"MEMBER",
"COLLABORATOR",
]);
const DANGEROUS_FILE_EXTENSION =
"(?:zip|7z|rar|tar\\.gz|tgz|exe|msi|dmg|pkg|deb|rpm|appimage|bat|cmd|ps1|scr|vbs)";
const dangerousFilePattern = new RegExp(
`(?:^|[\\s([<"'=])([^\\s()[\\]<>\"']+\\.${DANGEROUS_FILE_EXTENSION})(?=$|[\\s)\\]>\"']|[.,!?;:](?:$|\\s))`,
"gi"
);
const zipFilePattern = new RegExp(
`(?:^|[\\s([<"'=])([^\\s()[\\]<>\"']+\\.zip)(?=$|[\\s)\\]>\"']|[.,!?;:](?:$|\\s))`,
"gi"
);
const githubUserAttachmentPattern =
/^https:\/\/github\.com\/user-attachments\/files\/\d+\//i;
function normalizeAssociation(authorAssociation) {
return String(authorAssociation || "").trim().toUpperCase();
}
function isTrustedAuthor(authorAssociation) {
return TRUSTED_AUTHOR_ASSOCIATIONS.has(normalizeAssociation(authorAssociation));
}
function extractMatches(body, pattern) {
const files = [];
for (const match of body.matchAll(pattern)) {
files.push(match[1]);
}
return [...new Set(files)];
}
function extractDangerousFiles(body) {
return extractMatches(body, dangerousFilePattern);
}
function extractZipFiles(body) {
return extractMatches(body, zipFilePattern);
}
function isGitHubUserAttachment(file) {
return githubUserAttachmentPattern.test(file);
}
function detectSpamComment({ body, authorAssociation, userType } = {}) {
const normalizedBody = String(body || "").replace(/\s+/g, " ").trim();
const dangerousFiles = extractDangerousFiles(normalizedBody);
const zipFiles = extractZipFiles(normalizedBody);
const trustedAuthor = isTrustedAuthor(authorAssociation);
const botAuthor = String(userType || "").toLowerCase() === "bot";
const reasons = [];
let score = 0;
if (zipFiles.length > 0) {
score += 10;
reasons.push(`zip attachment or link: ${zipFiles.join(", ")}`);
} else if (dangerousFiles.length > 0) {
score += 10;
reasons.push(`dangerous downloadable file: ${dangerousFiles.join(", ")}`);
}
// Hard rule: any zip (or other dangerous downloadable) from an untrusted
// human is deleted. Maintainers and bots are exempt.
const spam =
!trustedAuthor &&
!botAuthor &&
(zipFiles.length > 0 || dangerousFiles.length > 0);
return {
spam,
score: spam ? score : 0,
reasons: spam ? reasons : [],
dangerousFiles: zipFiles.length > 0 ? zipFiles : dangerousFiles,
trustedAuthor,
botAuthor,
};
}
module.exports = {
detectSpamComment,
extractDangerousFiles,
extractZipFiles,
isGitHubUserAttachment,
isTrustedAuthor,
};