Repository: firecow/gitlab-ci-local
Pull request #1356 (2024-10-02 08:12:05)

Fixes undefined vars becoming null and erroring in conditions
When running the following

```
> gitlab-ci-local@4.53.0 start
> ts-node --log-error src/index.ts --cwd ../some-dir --list-json
```

The following error occurs

```
Error attempting to evaluate the following rules:
  rules:
    - if: '"main" && null =~ /\bdependency_scanning\b/ && null == "true"'
as
```javascript
"main" && null.match(/\bdependency_scanning\b/)  != null&& null == "true"
```

```
TypeError: Cannot read properties of null (reading 'match')
    at eval (eval at evaluateRuleIf (gitlab-ci-local/src/utils.ts:258:19), <anonymous>:1:20)
    at Function.evaluateRuleIf (gitlab-ci-local/src/utils.ts:258:19)
    at Function.getRulesResult (gitlab-ci-local/src/utils.ts:181:24)
    at new Job (gitlab-ci-local/src/job.ts:217:38)
    at gitlab-ci-local/src/parser.ts:179:29
    at Function.forEachRealJob (gitlab-ci-local/src/utils.ts:54:13)
    at Parser.init (gitlab-ci-local/src/parser.ts:165:15)
    at async Function.create (gitlab-ci-local/src/parser.ts:60:9)
    at async handler (gitlab-ci-local/src/handler.ts:63:18)
    at async Object.handler (gitlab-ci-local/src/index.ts:37:21)
```


I suspect this happens on a yml that contains
```
include:
  - template: Security/SAST.gitlab-ci.yml
  - template: Jobs/Dependency-Scanning.gitlab-ci.yml
```

which expands to the following:

```
gemnasium-maven-dependency_scanning:
  stage: test
  script:
    - /analyzer run
  artifacts:
    access: developer
    reports:
      dependency_scanning: gl-dependency-scanning-report.json
      cyclonedx: '**/gl-sbom-*.cdx.json'
    paths:
      - '**/gl-sbom-*.cdx.json'
  dependencies: []
  rules:
    - if: $DEPENDENCY_SCANNING_DISABLED == 'true' || $DEPENDENCY_SCANNING_DISABLED == '1'
      when: never
    - if: $DS_EXCLUDED_ANALYZERS =~ /gemnasium-maven/
      when: never
    - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && $CI_GITLAB_FIPS_MODE == "true"
      exists:
        - '**/{build.gradle,build.gradle.kts,build.sbt,pom.xml}'
      variables:
        DS_IMAGE_SUFFIX: '-fips'
    - if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/
      exists:
        - '**/{build.gradle,build.gradle.kts,build.sbt,pom.xml}'
  allow_failure: true
  variables:
    DS_ANALYZER_IMAGE: $SECURE_ANALYZERS_PREFIX/$DS_ANALYZER_NAME:$DS_MAJOR_VERSION
    DS_ANALYZER_NAME: gemnasium-maven
  image:
    name: $DS_ANALYZER_IMAGE$DS_IMAGE_SUFFIX
  cache:
    - *ref_0
  before_script:
    - >-
      if command -v git && [ $OPS_CONSOLE_SHA ]; then (cd services/opsConsole; git fetch --depth 1 origin $OPS_CONSOLE_SHA; git switch --detach
      $OPS_CONSOLE_SHA); fi
    - >-
      if [ $(npm --version) ] && [ "$CI_JOB_NAME" = "${CI_JOB_NAME#gemnasium}" ]; then npm ci --cache .npm --prefer-offline; else echo "container does not use
      NPM"; fi
```

With the problematic line being

```
- if: $CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && $CI_GITLAB_FIPS_MODE == "true"
```

This can be avoided by defining the variable to be empty, however the proposed solution is likely more future proof.

ANGkeith: can help to update the test cases so that we can have regression test

https://github.com/firecow/gitlab-ci-local/blob/master/tests/rules-regex.test.ts#L74
ANGkeith: btw i pushed directly into your branch, hope you're ok with it

Patch:
diff --git a/src/utils.ts b/src/utils.ts
index eff62d7..19b6ad5 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -218,7 +218,7 @@ export class Utils {
                 default:
                     throw operator;
             }
-            return `.match(${rhs}${flags})${remainingTokens} ${_operator} null`;
+            return `.match(${rhs}${flags})${remainingTokens} ${_operator} null `;
         });
 
         // Scenario when RHS is surrounded by double-quotes
@@ -250,8 +250,10 @@ Refer to https://docs.gitlab.com/ee/ci/jobs/job_rules.html#unexpected-behavior-f
         });
 
         // Convert all null.match functions to false
-        evalStr = evalStr.replace(/null.match\(.+?\) != null/g, "false");
-        evalStr = evalStr.replace(/null.match\(.+?\) == null/g, "false");
+        evalStr = evalStr.replace(/null.match\(.+?\)\s*!=\s*null/g, "false");
+        evalStr = evalStr.replace(/null.match\(.+?\)\s*==\s*null/g, "false");
+
+        evalStr = evalStr.trim();
 
         let res;
         try {


Test patch:
diff --git a/tests/rules-regex.test.ts b/tests/rules-regex.test.ts
index d24f29e..8ad4c65 100644
--- a/tests/rules-regex.test.ts
+++ b/tests/rules-regex.test.ts
@@ -72,6 +72,11 @@ const tests = [
         jsExpression: '"23".match(/1234/) != null',
         evalResult: false,
     },
+    {
+        rule: '$CI_COMMIT_BRANCH && $GITLAB_FEATURES =~ /\bdependency_scanning\b/ && $CI_GITLAB_FIPS_MODE == "true"',
+        jsExpression: 'null && false && null == "true"',
+        evalResult: false,
+    },
 ];
 /* eslint-enable @typescript-eslint/quotes */
 
diff --git a/tests/test-cases/include-project-file-ref/integration.include-project-file-ref.test.ts b/tests/test-cases/include-project-file-ref/integration.include-project-file-ref.test.ts
index 5a3cab2..5e081f1 100644
--- a/tests/test-cases/include-project-file-ref/integration.include-project-file-ref.test.ts
+++ b/tests/test-cases/include-project-file-ref/integration.include-project-file-ref.test.ts
@@ -1,7 +1,7 @@
 import {WriteStreamsMock} from "../../../src/write-streams";
 import {handler} from "../../../src/handler";
 import chalk from "chalk";
-import {initBashSpy, initSpawnSpy} from "../../mocks/utils.mock";
+import {initSpawnSpy} from "../../mocks/utils.mock";
 import {WhenStatics} from "../../mocks/when-statics";
 import fs from "fs-extra";
 
@@ -12,12 +12,6 @@ test("include-project-file-ref <deploy-job>", async () => {
         cmdArgs: ["git", "remote", "get-url", "origin"],
         returnValue: {stdout: "git@gitlab.com:gcl/test-hest.git"},
     };
-    const target = ".gitlab-ci-local/includes/gitlab.com/firecow/gitlab-ci-local-includes/HEAD/";
-    const spyGitArchive = {
-        cmd: `git archive --remote=ssh://git@gitlab.com:22/firecow/gitlab-ci-local-includes.git HEAD .gitlab-module.yml | tar -f - -xC ${target}`,
-        returnValue: {output: ""},
-    };
-    initBashSpy([spyGitArchive]);
     initSpawnSpy([...WhenStatics.all, spyGitRemote]);
     const mock = "tests/test-cases/include-project-file-ref/mock-gitlab-module.yml";
     const mockTarget = "tests/test-cases/include-project-file-ref/.gitlab-ci-local/includes/gitlab.com/firecow/gitlab-ci-local-includes/HEAD/.gitlab-module.yml";