packages/opencode/src/bundled-skills/instance-security/SKILL.md
This skill should be used when the user asks to "instance security", "hardening", "security best practices", "authentication", "SSO", "MFA", "session", "XSS", "injection", or any ServiceNow Instance Security development.
npx skillsauth add groeimetai/snow-flow instance-securityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Instance Security covers authentication, authorization, and security hardening.
Network Security
↓
Authentication (SSO, MFA)
↓
Session Management
↓
Authorization (ACLs, Roles)
↓
Data Protection
↓
Audit & Logging
| Table | Purpose |
| ------------------ | ----------------- |
| sys_user | User accounts |
| sys_user_role | Roles |
| sys_security_acl | Access controls |
| sysevent_log | Security events |
| sys_properties | Security settings |
// Check password strength (ES5 ONLY!)
function validatePasswordStrength(password) {
var issues = []
// Minimum length
var minLength = parseInt(gs.getProperty("glide.security.password.min_length", "8"), 10)
if (password.length < minLength) {
issues.push("Password must be at least " + minLength + " characters")
}
// Complexity requirements
if (!/[A-Z]/.test(password)) {
issues.push("Password must contain uppercase letter")
}
if (!/[a-z]/.test(password)) {
issues.push("Password must contain lowercase letter")
}
if (!/[0-9]/.test(password)) {
issues.push("Password must contain number")
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
issues.push("Password must contain special character")
}
return {
valid: issues.length === 0,
issues: issues,
}
}
// Check session validity (ES5 ONLY!)
function isSessionSecure() {
var session = gs.getSession()
// Check session age
var maxAge = parseInt(gs.getProperty("glide.security.session.timeout", "60"), 10)
var sessionAge = session.getSessionAge() / 60000 // Convert to minutes
if (sessionAge > maxAge) {
return { valid: false, reason: "Session expired" }
}
// Check for session fixation
var clientIP = gs.getSession().getClientIP()
var originalIP = session.getValue("original_ip")
if (originalIP && clientIP !== originalIP) {
return { valid: false, reason: "IP address changed" }
}
return { valid: true }
}
// Force re-authentication
function requireReauthentication(reason) {
gs.getSession().invalidate()
gs.addErrorMessage(reason)
// Redirect to login
response.sendRedirect("/login.do")
}
// Check if user has MFA enabled (ES5 ONLY!)
function hasMFAEnabled(userSysId) {
var user = new GlideRecord("sys_user")
if (!user.get(userSysId)) {
return false
}
// Check MFA settings
var mfa = new GlideRecord("sys_user_mfa")
mfa.addQuery("user", userSysId)
mfa.addQuery("active", true)
mfa.query()
return mfa.hasNext()
}
// Enforce MFA for sensitive operations
function requireMFA(operation) {
var userId = gs.getUserID()
if (!hasMFAEnabled(userId)) {
gs.addErrorMessage("MFA required for " + operation)
return false
}
// Check if MFA verified this session
var session = gs.getSession()
var mfaVerified = session.getValue("mfa_verified")
if (mfaVerified !== "true") {
// Trigger MFA challenge
gs.eventQueue("user.mfa.challenge", null, userId, operation)
return false
}
return true
}
// Sanitize user input (ES5 ONLY!)
function sanitizeInput(input) {
if (!input) return ""
// Encode HTML entities
var sanitized = input
.toString()
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'")
return sanitized
}
// Validate and sanitize before storage
function validateUserInput(tableName, fieldName, value) {
// Check field type
var dict = new GlideRecord("sys_dictionary")
dict.addQuery("name", tableName)
dict.addQuery("element", fieldName)
dict.query()
if (!dict.next()) {
return { valid: false, error: "Field not found" }
}
var fieldType = dict.getValue("internal_type")
// Validate based on type
if (fieldType === "string" || fieldType === "html") {
// Check for script injection
if (/<script/i.test(value)) {
return { valid: false, error: "Script tags not allowed" }
}
// Check for event handlers
if (/on\w+\s*=/i.test(value)) {
return { valid: false, error: "Event handlers not allowed" }
}
}
return { valid: true, sanitized: sanitizeInput(value) }
}
// Safe query building (ES5 ONLY!)
// NEVER concatenate user input directly into queries
// BAD - Vulnerable to injection
// gr.addEncodedQuery('name=' + userInput);
// GOOD - Use parameterized queries
function safeQuery(tableName, fieldName, value) {
var gr = new GlideRecord(tableName)
// GlideRecord methods handle escaping
gr.addQuery(fieldName, value)
gr.query()
return gr
}
// For encoded queries, validate input
function safeEncodedQuery(tableName, userQuery) {
// Whitelist allowed fields
var allowedFields = ["number", "short_description", "state", "priority"]
// Parse and validate query
var parts = userQuery.split("^")
var safeQuery = []
for (var i = 0; i < parts.length; i++) {
var part = parts[i]
var match = part.match(/^(\w+)(=|!=|LIKE|CONTAINS)(.+)$/)
if (match) {
var field = match[1]
if (allowedFields.indexOf(field) !== -1) {
safeQuery.push(part)
}
}
}
var gr = new GlideRecord(tableName)
if (safeQuery.length > 0) {
gr.addEncodedQuery(safeQuery.join("^"))
}
gr.query()
return gr
}
// Get security-related properties (ES5 ONLY!)
function getSecuritySettings() {
return {
// Session settings
session_timeout: gs.getProperty("glide.security.session.timeout"),
session_cookie_secure: gs.getProperty("glide.security.session.cookie.secure"),
// Password settings
password_min_length: gs.getProperty("glide.security.password.min_length"),
password_history: gs.getProperty("glide.security.password.history"),
password_expiry: gs.getProperty("glide.security.password.expiry"),
// Login settings
max_failed_logins: gs.getProperty("glide.security.password.max_failed_logins"),
lockout_duration: gs.getProperty("glide.security.password.lockout_duration"),
// General security
csrf_protection: gs.getProperty("glide.security.csrf.strict_validation"),
xss_protection: gs.getProperty("glide.security.xss.strict"),
}
}
// Update security property
function setSecurityProperty(name, value, requireRestart) {
gs.setProperty(name, value)
if (requireRestart) {
gs.warn("Security property changed: " + name + ". Restart may be required.")
}
// Log security change
gs.eventQueue("security.property.changed", null, name, value)
}
// Log security event (ES5 ONLY!)
function logSecurityEvent(eventType, details) {
var log = new GlideRecord("syslog")
log.initialize()
log.setValue("level", "warning")
log.setValue("source", "Security")
log.setValue("message", eventType + ": " + JSON.stringify(details))
log.insert()
// Also queue for security monitoring
gs.eventQueue("security.event", null, eventType, JSON.stringify(details))
}
// Track failed login attempts
function trackFailedLogin(username, ipAddress) {
logSecurityEvent("failed_login", {
username: username,
ip: ipAddress,
timestamp: new GlideDateTime().getDisplayValue(),
})
// Check for brute force
var recentFailures = countRecentFailures(username, 5) // Last 5 minutes
var maxFailures = parseInt(gs.getProperty("glide.security.password.max_failed_logins", "5"), 10)
if (recentFailures >= maxFailures) {
lockAccount(username)
logSecurityEvent("account_locked", {
username: username,
reason: "Too many failed login attempts",
failures: recentFailures,
})
}
}
// Check instance security health (ES5 ONLY!)
function securityHealthCheck() {
var issues = []
// Check for default admin password
var admin = new GlideRecord("sys_user")
if (admin.get("user_name", "admin")) {
if (admin.getValue("password") === gs.getProperty("glide.security.default.admin.password")) {
issues.push({ severity: "critical", issue: "Default admin password not changed" })
}
}
// Check session timeout
var timeout = parseInt(gs.getProperty("glide.security.session.timeout", "0"), 10)
if (timeout === 0 || timeout > 60) {
issues.push({ severity: "high", issue: "Session timeout too long or disabled" })
}
// Check password policy
var minLength = parseInt(gs.getProperty("glide.security.password.min_length", "0"), 10)
if (minLength < 12) {
issues.push({ severity: "medium", issue: "Password minimum length less than 12" })
}
// Check HTTPS enforcement
if (gs.getProperty("glide.security.session.cookie.secure") !== "true") {
issues.push({ severity: "high", issue: "Secure cookies not enforced" })
}
return {
healthy: issues.length === 0,
issues: issues,
}
}
| Tool | Purpose |
| --------------------------------- | ------------------------- |
| snow_property_get | Check security properties |
| snow_execute_script_with_output | Test security scripts |
| snow_review_access_control | Review ACLs |
// 1. Check security properties
await snow_property_get({
name: "glide.security.session.timeout",
})
// 2. Run security health check
await snow_execute_script_with_output({
script: `
var health = securityHealthCheck();
gs.info(JSON.stringify(health));
`,
})
// 3. Review access controls
await snow_query_table({
table: "sys_security_acl",
query: "active=true^admin_overrides=true",
fields: "name,operation,type,script",
})
development
This skill should be used when the user asks to "App Engine Studio", "workspace builder", "custom workspace", "AES", "low code", "app development", "studio", or any ServiceNow App Engine Studio development.
tools
This skill should be used when the user asks to "create a widget", "build a widget", "service portal widget", "sp_widget", "fix widget", "widget not working", "ng-click not working", or any Service Portal widget development.
development
This skill should be used when the user asks to "create chatbot", "virtual agent", "VA topic", "NLU", "conversation", "chat flow", "topic block", or any ServiceNow Virtual Agent development.
development
This skill should be used when the user asks to "vendor", "supplier", "contract", "procurement", "SLA", "vendor risk", "vendor performance", or any ServiceNow Vendor Management development.