From ad3af2fc86bb4f9a11c0b730035b9a38cc68ac83 Mon Sep 17 00:00:00 2001 From: franks883 Date: Tue, 21 Oct 2025 18:48:10 +0100 Subject: [PATCH 1/3] Create README.md --- .../README.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Server-Side Components/Business Rules/Quarantine risky attachments by type or size/README.md diff --git a/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/README.md b/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/README.md new file mode 100644 index 0000000000..e0d426b15a --- /dev/null +++ b/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/README.md @@ -0,0 +1,29 @@ +# Quarantine risky attachments by type or size + +## What this solves +Users sometimes upload executables or very large files via email or forms. This rule quarantines risky attachments by copying them off the original record, deleting the original, and keeping an audit trail. + +## Where to use +- Table: `sys_attachment` +- When: before insert +- Order: early (for example 50) + +## How it works +- Checks file extension and size against configurable thresholds +- Creates or reuses a quarantine record (table `x_quarantine_attachment` or default `incident` as a safe example) +- Copies the new attachment to the quarantine record via `GlideSysAttachment.copy` +- Deletes the original attachment via `GlideSysAttachment.deleteAttachment` +- Logs what happened with minimal, readable messages + +## Configure +In the Business Rule: +- `BLOCKED_EXTS`: extensions to quarantine +- `MAX_SIZE_MB`: size threshold +- `QUARANTINE_TABLE`: table to hold quarantined items +- `ASSIGNMENT_GROUP_SYSID`: optional group to triage quarantines + +## References +- GlideSysAttachment API + https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideSysAttachment/concept/c_GlideSysAttachmentAPI.html +- Business Rules + https://www.servicenow.com/docs/bundle/zurich-application-development/page/build/applications/concept/c_BusinessRules.html From d961e5b60db62f511cd885d5fd477d027bcfccca Mon Sep 17 00:00:00 2001 From: franks883 Date: Tue, 21 Oct 2025 18:48:47 +0100 Subject: [PATCH 2/3] Create QuarantineAttachmentUtils.js --- .../QuarantineAttachmentUtils.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Server-Side Components/Business Rules/Quarantine risky attachments by type or size/QuarantineAttachmentUtils.js diff --git a/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/QuarantineAttachmentUtils.js b/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/QuarantineAttachmentUtils.js new file mode 100644 index 0000000000..c0263fee8f --- /dev/null +++ b/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/QuarantineAttachmentUtils.js @@ -0,0 +1,33 @@ +// Script Include: QuarantineAttachmentUtils +// Purpose: Utilities for quarantining attachments. +// Scope: global or scoped. Client callable false. + +var QuarantineAttachmentUtils = Class.create(); +QuarantineAttachmentUtils.prototype = { + initialize: function() {}, + + ensureQuarantineRecord: function(table, fileName, reason, groupSysId) { + var gr = new GlideRecord(table); + gr.initialize(); + if (gr.isValidField('short_description')) + gr.short_description = 'Quarantined attachment: ' + fileName; + if (gr.isValidField('description')) + gr.description = 'Reason: ' + reason; + if (groupSysId && gr.isValidField('assignment_group')) + gr.assignment_group = groupSysId; + return gr.insert(); + }, + + copyAndDelete: function(fromTable, fromSysId, toTable, toSysId, attachSysId) { + var gsa = new GlideSysAttachment(); + gsa.copy(fromTable, fromSysId, toTable, toSysId, attachSysId); + gsa.deleteAttachment(attachSysId); + }, + + getExt: function(fileName) { + var m = String(fileName || '').match(/\.([A-Za-z0-9]+)$/); + return m ? m[1].toLowerCase() : ''; + }, + + type: 'QuarantineAttachmentUtils' +}; From 3fbd2134ecf62ecfc6ecae445e537eada79268b5 Mon Sep 17 00:00:00 2001 From: franks883 Date: Tue, 21 Oct 2025 18:49:09 +0100 Subject: [PATCH 3/3] Create br_quarantine_risky_attachments.js --- .../br_quarantine_risky_attachments.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Server-Side Components/Business Rules/Quarantine risky attachments by type or size/br_quarantine_risky_attachments.js diff --git a/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/br_quarantine_risky_attachments.js b/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/br_quarantine_risky_attachments.js new file mode 100644 index 0000000000..7c7f1b51b7 --- /dev/null +++ b/Server-Side Components/Business Rules/Quarantine risky attachments by type or size/br_quarantine_risky_attachments.js @@ -0,0 +1,39 @@ +// Business Rule: Quarantine risky attachments by type or size +// Table: sys_attachment | When: before insert + +(function executeRule(current, previous /*null*/) { + try { + // Config + var BLOCKED_EXTS = ['exe', 'bat', 'cmd', 'ps1', 'js']; + var MAX_SIZE_MB = 25; // quarantine files larger than this + var QUARANTINE_TABLE = 'incident'; // replace with your quarantine table if available + var ASSIGNMENT_GROUP_SYSID = ''; // optional triage group + + // Skip non-file or missing metadata + if (!current.table_name || !current.file_name) return; + + var utils = new QuarantineAttachmentUtils(); + var ext = utils.getExt(current.file_name); + var sizeBytes = Number(current.size_bytes || 0); + var isBlocked = BLOCKED_EXTS.indexOf(ext) !== -1; + var isTooLarge = sizeBytes > (MAX_SIZE_MB * 1024 * 1024); + + if (!(isBlocked || isTooLarge)) return; + + var reason = isBlocked ? ('blocked extension .' + ext) : ('size ' + sizeBytes + ' bytes exceeds ' + MAX_SIZE_MB + ' MB'); + + // Create quarantine record + var quarantineId = utils.ensureQuarantineRecord(QUARANTINE_TABLE, current.file_name, reason, ASSIGNMENT_GROUP_SYSID); + + // Copy attachment to quarantine and delete original + utils.copyAndDelete(current.table_name, current.table_sys_id, QUARANTINE_TABLE, quarantineId, current.sys_id); + + gs.info('[ATTACHMENT-QUARANTINE] file=' + current.file_name + + ' ext=' + ext + + ' size=' + sizeBytes + + ' reason=' + reason + + ' quarantined_to=' + QUARANTINE_TABLE + ':' + quarantineId); + } catch (e) { + gs.error('Attachment quarantine failed: ' + e.message); + } +})(current, previous);