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' +}; 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 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);