Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const metadata = {
scope: "package",
title: "Ensure strict mode is enabled when using the React client",
description: "When React is enabled in Project Settings, StrictMode must also be enabled in Project Security for security and stability",
authors: ["Jurre Tanja <jurre.tanja@siemens.com>"],
custom: {
category: "Security",
rulename: "StrictModeWithOptimizedClient",
severity: "HIGH",
rulenumber: "001_0009",
remediation: "Enable Strict mode in the Security settings",
input: ".*\\$ProjectSettings\\.yaml"
}
};

function rule(input = {}) {
const errors = [];

try {
// Check if UseOptimizedClient is enabled
if (input.UseOptimizedClient === true) {
// Read the ProjectSecurity.yaml file to check StrictMode
try {
const securityContent = mxlint.io.readfile("Security$ProjectSecurity.yaml");

// Check if StrictMode is enabled
if (!securityContent || securityContent.StrictMode !== true) {
errors.push(`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] StrictMode must be enabled in Project Security when UseOptimizedClient is enabled in Project Settings`);
}
} catch (securityError) {
errors.push(`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] Failed to read Security$ProjectSecurity.yaml: ${securityError.message}`);
}
}
} catch (e) {
errors.push(`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] Error checking strict mode configuration: ${e.message}`);
}

return {
allow: errors.length === 0,
errors
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
TestCases:
- name: allow when UseOptimizedClient is false
allow: true
input:
UseOptimizedClient: false
files:
"Security$ProjectSecurity.yaml":
StrictMode: false
- name: allow when both UseOptimizedClient and StrictMode are true
allow: true
input:
UseOptimizedClient: true
files:
"Security$ProjectSecurity.yaml":
StrictMode: true
- name: do not allow UseOptimizedClient true but StrictMode false
allow: false
input:
UseOptimizedClient: true
files:
"Security$ProjectSecurity.yaml":
StrictMode: false
- name: do not allow UseOptimizedClient true but StrictMode missing
allow: false
input:
UseOptimizedClient: true
files:
"Security$ProjectSecurity.yaml": {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
const metadata = {
scope: "package",
title: "Commit actions with a loop (including submicroflows)",
description: "Commiting objects within a loop will fire a SQL Update query for each iteration. This includes commits in called submicroflows.",
authors: ["Viktor Berlov <viktor@cinaq.com>"],
custom: {
category: "Microflows",
rulename: "AvoidCommitInLoopWithSubmicroflows",
severity: "MEDIUM",
rulenumber: "005_0006",
remediation: "Consider committing objects outside the loop. Within the loop, add them to a list.",
input: ".*\\$Microflow\\.yaml"
}
};

function rule(input = {}) {
const errors = [];
const visitedMicroflows = new Set();

try {
// Check for commits directly in loops
checkCommitsInLoops(input, errors, "");

// Check for commits in called submicroflows within loops
checkSubmicroflowsInLoops(input, errors, visitedMicroflows, "");
} catch (e) {
errors.push(`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] Error checking commit actions in loop: ${e.message}`);
}

return {
allow: errors.length === 0,
errors
};
}

/**
* Check for direct commit actions within loops
*/
function checkCommitsInLoops(microflow, errors, chain) {
const name = microflow.Name || "Unknown";
const mainFunction = microflow.MainFunction || [];

// Check for LoopedActivity with commit actions
mainFunction.forEach(attr => {
if (attr.Attributes && attr.Attributes["$Type"] === "Microflows$LoopedActivity") {
const objects = attr.Attributes.ObjectCollection?.Objects || [];

objects.forEach(obj => {
// Check for direct CommitAction
if (obj.Action && obj.Action["$Type"] === "Microflows$CommitAction") {
const errorChain = chain ? `${chain} → ${name}` : name;
errors.push(
`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] Commit actions inside ${errorChain} loop`
);
}

// Check for ChangeAction with Commit set to Yes
if (obj.Action && obj.Action["$Type"] === "Microflows$ChangeAction" && obj.Action.Commit === "Yes") {
const errorChain = chain ? `${chain} → ${name}` : name;
errors.push(
`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] Commit set to Yes for Change actions inside ${errorChain} loop`
);
}
});
}
});
}

/**
* Recursively check for submicroflow calls within loops and their contents
*/
function checkSubmicroflowsInLoops(microflow, errors, visitedMicroflows, chain) {
const name = microflow.Name || "Unknown";

// Prevent infinite loops
if (visitedMicroflows.has(name)) {
return;
}
visitedMicroflows.add(name);

const mainFunction = microflow.MainFunction || [];

mainFunction.forEach(attr => {
if (attr.Attributes && attr.Attributes["$Type"] === "Microflows$LoopedActivity") {
const objects = attr.Attributes.ObjectCollection?.Objects || [];

objects.forEach(obj => {
// Check for MicroflowCallAction (submicroflow calls)
if (obj.Action && obj.Action["$Type"] === "Microflows$MicroflowCallAction") {
const submicroflowName = extractMicroflowName(obj.Action);

if (submicroflowName) {
try {
// Build the chain for error messages
const newChain = chain ? `${chain} → ${name}` : name;

// Try to read the submicroflow
const submicroflowContent = mxlint.io.readfile(`${submicroflowName}$Microflow.yaml`);

if (submicroflowContent) {
// Check if the submicroflow contains ANY commits (anywhere, not just in loops)
checkForAnyCommits(submicroflowContent, errors, newChain);
}
} catch (e) {
// Silently continue if submicroflow cannot be read
// This is normal as not all microflows may be available
}
}
}
});
}
});
}

/**
* Recursively check if a microflow or any of its called submicroflows contains any commits
*/
function checkForAnyCommits(microflow, errors, chain) {
const name = microflow.Name || "Unknown";
const mainFunction = microflow.MainFunction || [];

mainFunction.forEach(attr => {
if (attr.Attributes) {
const objects = attr.Attributes.ObjectCollection?.Objects || [];

objects.forEach(obj => {
// Check for any CommitAction
if (obj.Action && obj.Action["$Type"] === "Microflows$CommitAction") {
const errorChain = chain ? `${chain} → ${name}` : name;
errors.push(
`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] Commit actions found in submicroflow called from loop: ${errorChain}`
);
}

// Check for ChangeAction with Commit set to Yes
if (obj.Action && obj.Action["$Type"] === "Microflows$ChangeAction" && obj.Action.Commit === "Yes") {
const errorChain = chain ? `${chain} → ${name}` : name;
errors.push(
`[${metadata.custom.severity}, ${metadata.custom.category}, ${metadata.custom.rulenumber}] Commit set to Yes in submicroflow called from loop: ${errorChain}`
);
}

// Recursively check nested submicroflow calls
if (obj.Action && obj.Action["$Type"] === "Microflows$MicroflowCallAction") {
const submicroflowName = extractMicroflowName(obj.Action);

if (submicroflowName) {
try {
const newChain = chain ? `${chain} → ${name}` : name;
const submicroflowContent = mxlint.io.readfile(`${submicroflowName}$Microflow.yaml`);

if (submicroflowContent) {
checkForAnyCommits(submicroflowContent, errors, newChain);
}
} catch (e) {
// Silently continue if submicroflow cannot be read
}
}
}
});
}
});
}

/**
* Extract microflow name from MicroflowCallAction
* The microflow reference is typically in MicroflowName or microflowname property
*/
function extractMicroflowName(action) {
if (!action) return null;

// Try various possible property names
const possibleNames = [
action.MicroflowName,
action.microflowname,
action.MicroflowQualifiedName,
action.microflowQualifiedName
];

for (const name of possibleNames) {
if (name && typeof name === "string") {
// If it's a qualified name like "Module.Microflow", keep as is
return name;
}
}

return null;
}
Loading
Loading