Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ The mCode Action accepts the following inputs:
| --- | --- | --- | --- | ---
| | `mayhem-url` | string | Path to a custom Mayhem for Code instance. | https://app.mayhem.security |
| | `mayhem-token` | string | Mayhem for Code account token. **Only required within** `mayhem.yml` **if overriding** `mayhem-url`. |
| | `duration` | number | Duration of the run in seconds. Takes precedence over any `--duration` passed via `args`. | 60 |
| | `args` | string | Additional CLI override [arguments](https://app.mayhem.security/docs/code-testing/reference/mayhem-cli-commands/#run) such as specifying the `--testsuite` directory path for a seed test suite. |
| | `sarif-output` | string | Path for generating a SARIF report output file. |
| | `junit-output` | string | Path for generating a jUnit report output file. |
Expand Down
5 changes: 4 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ inputs:
description: Path to the Mayhem package relative to the repository root
required: false
default: "."
duration:
description: Duration of the run in seconds. Takes precedence over any '--duration' passed via 'args'. Defaults to 60 seconds if neither is set.
required: false
args:
description: Command line arguments to override CLI behavior
required: false
runs:
using: "node20"
using: "node24"
main: "dist/index.js"
outputs:
runId:
Expand Down
51 changes: 49 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,17 @@ function getConfig() {
const eventPath = process.env["GITHUB_EVENT_PATH"] || "event.json";
const event = JSON.parse((0, fs_1.readFileSync)(eventPath, "utf-8")) || {};
const eventPullRequest = event.pull_request;
// Optional typed run duration (in seconds). When set it must be a positive
// integer; it takes precedence over any `--duration` passed via `args`.
const rawDuration = (0, core_1.getInput)("duration");
const duration = rawDuration
? validateDuration(rawDuration, "duration input")
: "";
return {
githubToken,
mayhemToken: (0, core_1.getInput)("mayhem-token") || githubToken,
packagePath: (0, core_1.getInput)("package") || ".",
duration,
sarifOutputDir: (0, core_1.getInput)("sarif-output") || "",
junitOutputDir: (0, core_1.getInput)("junit-output") || "",
coverageOutputDir: (0, core_1.getInput)("coverage-output") || "",
Expand All @@ -73,6 +80,24 @@ function getConfig() {
mergeBaseBranchName: eventPullRequest ? eventPullRequest.base.ref : "main",
};
}
/**
* Validates a run duration (in seconds) and returns it in canonical form. A
* duration must be a positive integer; anything else (a decimal like "30.5", a
* suffix like "20m", zero, a missing value) is rejected, since the CLI would
* otherwise treat a malformed duration as an unbounded run. Leading zeros are
* stripped ("000000120" -> "120") so the CLI receives a clean value. Throws
* with a message naming `source` on invalid input.
* @param value the raw duration string to validate.
* @param source human-readable origin of the value, used in the error message.
* @return the duration normalized to its canonical decimal integer string.
*/
function validateDuration(value, source) {
if (!/^\d+$/.test(value) || parseInt(value, 10) <= 0) {
throw Error(`invalid duration '${value}' (${source}): ` +
"it must be a positive integer number of seconds.");
}
return String(parseInt(value, 10));
}
/**
* Downloads the mCode CLI from the given Mayhem cluster, marks it as executable, and returns the
* path to the downloaded CLI.
Expand All @@ -91,15 +116,37 @@ function downloadCli(url, os) {
/** Mapping action arguments to CLI arguments and completing a run */
function run() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
// Validate the action inputs and create a Config object from them.
const config = getConfig();
// Download the mCode CLI for Linux.
const cli = yield downloadCli(mayhemUrl, CliOsPath.Linux);
const args = ((0, core_1.getInput)("args") || "").split(" ");
// defaults next
if (!args.includes("--duration")) {
// Resolve the effective run duration. Precedence:
// 1. the typed `duration` input,
// 2. a `--duration` passed inside `args`,
// 3. the documented default of 60 seconds.
const argsDurationIndex = args.indexOf("--duration");
if (config.duration) {
if (argsDurationIndex !== -1) {
// The typed input wins over a --duration smuggled through args.
args.splice(argsDurationIndex, 2, "--duration", config.duration);
}
else {
args.push("--duration", config.duration);
}
(0, core_1.info)(`Duration: ${config.duration}s (from the 'duration' input).`);
}
else if (argsDurationIndex !== -1) {
const argsDuration = validateDuration((_a = args[argsDurationIndex + 1]) !== null && _a !== void 0 ? _a : "", "--duration in args");
// Write the normalized value back so the CLI gets a clean duration.
args[argsDurationIndex + 1] = argsDuration;
(0, core_1.info)(`Duration: ${argsDuration}s (from '--duration' in 'args').`);
}
else {
args.push("--duration", "60");
(0, core_1.info)("Duration: 60s (default).");
}
if (!args.includes("--image")) {
args.push("--image", "forallsecure/debian-buster:latest");
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

55 changes: 53 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Config = {
mayhemToken: string;

packagePath: string;
duration: string;
sarifOutputDir: string;
junitOutputDir: string;
coverageOutputDir: string;
Expand Down Expand Up @@ -61,10 +62,18 @@ function getConfig(): Config {
const event = JSON.parse(readFileSync(eventPath, "utf-8")) || {};
const eventPullRequest = event.pull_request;

// Optional typed run duration (in seconds). When set it must be a positive
// integer; it takes precedence over any `--duration` passed via `args`.
const rawDuration = getInput("duration");
const duration = rawDuration
? validateDuration(rawDuration, "duration input")
: "";

return {
githubToken,
mayhemToken: getInput("mayhem-token") || githubToken,
packagePath: getInput("package") || ".",
duration,
sarifOutputDir: getInput("sarif-output") || "",
junitOutputDir: getInput("junit-output") || "",
coverageOutputDir: getInput("coverage-output") || "",
Expand All @@ -84,6 +93,27 @@ function getConfig(): Config {
};
}

/**
* Validates a run duration (in seconds) and returns it in canonical form. A
* duration must be a positive integer; anything else (a decimal like "30.5", a
* suffix like "20m", zero, a missing value) is rejected, since the CLI would
* otherwise treat a malformed duration as an unbounded run. Leading zeros are
* stripped ("000000120" -> "120") so the CLI receives a clean value. Throws
* with a message naming `source` on invalid input.
* @param value the raw duration string to validate.
* @param source human-readable origin of the value, used in the error message.
* @return the duration normalized to its canonical decimal integer string.
*/
function validateDuration(value: string, source: string): string {
if (!/^\d+$/.test(value) || parseInt(value, 10) <= 0) {
throw Error(
`invalid duration '${value}' (${source}): ` +
"it must be a positive integer number of seconds.",
);
}
return String(parseInt(value, 10));
}

/**
* Downloads the mCode CLI from the given Mayhem cluster, marks it as executable, and returns the
* path to the downloaded CLI.
Expand All @@ -109,9 +139,30 @@ async function run(): Promise<void> {

const args: string[] = (getInput("args") || "").split(" ");

// defaults next
if (!args.includes("--duration")) {
// Resolve the effective run duration. Precedence:
// 1. the typed `duration` input,
// 2. a `--duration` passed inside `args`,
// 3. the documented default of 60 seconds.
const argsDurationIndex = args.indexOf("--duration");
if (config.duration) {
if (argsDurationIndex !== -1) {
// The typed input wins over a --duration smuggled through args.
args.splice(argsDurationIndex, 2, "--duration", config.duration);
} else {
args.push("--duration", config.duration);
}
info(`Duration: ${config.duration}s (from the 'duration' input).`);
} else if (argsDurationIndex !== -1) {
const argsDuration = validateDuration(
args[argsDurationIndex + 1] ?? "",
"--duration in args",
);
// Write the normalized value back so the CLI gets a clean duration.
args[argsDurationIndex + 1] = argsDuration;
info(`Duration: ${argsDuration}s (from '--duration' in 'args').`);
} else {
args.push("--duration", "60");
info("Duration: 60s (default).");
}
if (!args.includes("--image")) {
args.push("--image", "forallsecure/debian-buster:latest");
Expand Down
Loading