CVE-2022-35954 (JSON, published 2022-08-15T11:21Z)
Description: value: The GitHub Actions ToolKit provides a set of packages to make creating actions easier. The `core.exportVariable` function uses a well known delimiter that attackers can use to break out of that specific variable and assign values to other arbitrary variables. Workflows that write untrusted values to the `GITHUB_ENV` file may cause the path or other environment variables to be modified without the intention of the workflow or action author. Users should upgrade to `@actions/core v1.9.1`. If you are unable to upgrade the `@actions/core` package, you can modify your action to ensure that any user input does not contain the delimiter `_GitHubActionsFileCommandDelimeter_` before calling `core.exportVariable`.
Severity: nan (CVSS v3 base score: 5)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
Repository: https://github.com/actions/toolkit
Files changed:
packages/core/RELEASES.md
packages/core/__tests__/core.test.ts
packages/core/package-lock.json
packages/core/package.json
packages/core/src/core.ts
Commit message: Merge pull request from GHSA-7r3h-m5j6-3q42

* use uuid as our multiline env delimiter

* remove extra fn

* Fix version

* also throw error if delimiter is found in name or value

* move delimiter and uuid to global var in test

* upgrade uuid to newest version

* remove spy variable

* Update packages/core/src/core.ts

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

* Update packages/core/src/core.ts

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

Vulnerable code:
      `my var<<_GitHubActionsFileCommandDelimeter_${os.EOL}var val${os.EOL}_GitHubActionsFileCommandDelimeter_${os.EOL}`
      `my var<<_GitHubActionsFileCommandDelimeter_${os.EOL}true${os.EOL}_GitHubActionsFileCommandDelimeter_${os.EOL}`
      `my var<<_GitHubActionsFileCommandDelimeter_${os.EOL}5${os.EOL}_GitHubActionsFileCommandDelimeter_${os.EOL}`
  "version": "1.9.0",
      "version": "1.8.1",
        "@actions/http-client": "^2.0.1"
        "@types/node": "^12.0.2"
  "version": "1.9.0",
    "@actions/http-client": "^2.0.1"
    "@types/node": "^12.0.2"
    const delimiter = '_GitHubActionsFileCommandDelimeter_'

Fixed code:
### 1.9.1
- Randomize delimiter when calling `core.exportVariable`

import * as uuid from 'uuid'

jest.mock('uuid')
const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
const DELIMITER = `ghadelimiter_${UUID}`


    jest.spyOn(uuid, 'v4').mockImplementation(() => {
      return UUID
    })
  })

  afterEach(() => {
    jest.restoreAllMocks()
      `my var<<${DELIMITER}${os.EOL}var val${os.EOL}${DELIMITER}${os.EOL}`
      `my var<<${DELIMITER}${os.EOL}true${os.EOL}${DELIMITER}${os.EOL}`
      `my var<<${DELIMITER}${os.EOL}5${os.EOL}${DELIMITER}${os.EOL}`
  it('exportVariable does not allow delimiter as value', () => {
    const command = 'ENV'
    createFileCommandFile(command)
    
    expect(() => {
      core.exportVariable('my var', `good stuff ${DELIMITER} bad stuff`)
    }).toThrow(`Unexpected input: value should not contain the delimiter "${DELIMITER}"`)

    const filePath = path.join(__dirname, `test/${command}`)
    fs.unlinkSync(filePath)
  })

  it('exportVariable does not allow delimiter as name', () => {
    const command = 'ENV'
    createFileCommandFile(command)
    
    expect(() => {
      core.exportVariable(`good stuff ${DELIMITER} bad stuff`, 'test')
    }).toThrow(`Unexpected input: name should not contain the delimiter "${DELIMITER}"`)
    
    const filePath = path.join(__dirname, `test/${command}`)
    fs.unlinkSync(filePath)
  })

  "version": "1.9.1",
      "version": "1.9.1",
        "@actions/http-client": "^2.0.1",
        "uuid": "^8.3.2"
        "@types/node": "^12.0.2",
        "@types/uuid": "^8.3.4"
    "node_modules/@types/uuid": {
      "version": "8.3.4",
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
      "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
      "dev": true
    },
    },
    "node_modules/uuid": {
      "version": "8.3.2",
      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
      "bin": {
        "uuid": "dist/bin/uuid"
      }
    "@types/uuid": {
      "version": "8.3.4",
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
      "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
      "dev": true
    },
    },
    "uuid": {
      "version": "8.3.2",
      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
  "version": "1.9.1",
    "@actions/http-client": "^2.0.1",
    "uuid": "^8.3.2"
    "@types/node": "^12.0.2",
    "@types/uuid": "^8.3.4"
import { v4 as uuidv4 } from 'uuid'
    const delimiter = `ghadelimiter_${uuidv4()}`

    // These should realistically never happen, but just in case someone finds a way to exploit uuid generation let's not allow keys or values that contain the delimiter.  
    if (name.includes(delimiter)) {
      throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`)
    }

    if (convertedVal.includes(delimiter)) {
      throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`)
    }