Replace manual tmp file creation with tmp library which uses security best practices

This commit is contained in:
Jessica Rudder
2025-08-12 13:49:47 -07:00
parent a62dfeda7b
commit 3ba8e1b39d
6 changed files with 924 additions and 53 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ function mockInputs(inputs: Record<string, string> = {}): void {
*/ */
function verifyStandardResponse(): void { function verifyStandardResponse(): void {
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'response', 'Hello, user!') expect(core.setOutput).toHaveBeenNthCalledWith(1, 'response', 'Hello, user!')
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'response-file', expect.stringContaining('modelResponse.txt')) expect(core.setOutput).toHaveBeenNthCalledWith(2, 'response-file', expect.stringContaining('modelResponse-'))
} }
vi.mock('fs', () => ({ vi.mock('fs', () => ({
Generated Vendored
+891 -37
View File
File diff suppressed because it is too large Load Diff
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+18 -1
View File
@@ -11,9 +11,11 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.11.1",
"@modelcontextprotocol/sdk": "^1.15.1", "@modelcontextprotocol/sdk": "^1.15.1",
"@types/tmp": "^0.2.6",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"openai": "^5.11.0", "openai": "^5.11.0",
"pkce-challenge": "^5.0.0" "pkce-challenge": "^5.0.0",
"tmp": "^0.2.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.3.0", "@eslint/compat": "^1.3.0",
@@ -2492,6 +2494,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/tmp": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz",
"integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.34.0", "version": "8.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz",
@@ -8943,6 +8951,15 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/tmp": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"license": "MIT",
"engines": {
"node": ">=14.14"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+3 -1
View File
@@ -25,9 +25,11 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.11.1",
"@modelcontextprotocol/sdk": "^1.15.1", "@modelcontextprotocol/sdk": "^1.15.1",
"@types/tmp": "^0.2.6",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"openai": "^5.11.0", "openai": "^5.11.0",
"pkce-challenge": "^5.0.0" "pkce-challenge": "^5.0.0",
"tmp": "^0.2.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.3.0", "@eslint/compat": "^1.3.0",
+10 -12
View File
@@ -1,7 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as fs from 'fs' import * as fs from 'fs'
import * as os from 'os' import * as tmp from 'tmp'
import * as path from 'path'
import {connectToGitHubMCP} from './mcp.js' import {connectToGitHubMCP} from './mcp.js'
import {simpleInference, mcpInference} from './inference.js' import {simpleInference, mcpInference} from './inference.js'
import {loadContentFromFileOrInput, buildInferenceRequest} from './helpers.js' import {loadContentFromFileOrInput, buildInferenceRequest} from './helpers.js'
@@ -13,8 +12,6 @@ import {
parseFileTemplateVariables, parseFileTemplateVariables,
} from './prompt.js' } from './prompt.js'
const RESPONSE_FILE = 'modelResponse.txt'
/** /**
* The main function for the action. * The main function for the action.
* *
@@ -93,11 +90,17 @@ export async function run(): Promise<void> {
core.setOutput('response', modelResponse || '') core.setOutput('response', modelResponse || '')
const responseFilePath = path.join(tempDir(), RESPONSE_FILE) // Create a secure temporary file instead of using the temp directory directly
core.setOutput('response-file', responseFilePath) const responseFile = tmp.fileSync({
prefix: 'modelResponse-',
postfix: '.txt',
keep: true, // Keep the file so the action can read it
})
core.setOutput('response-file', responseFile.name)
if (modelResponse && modelResponse !== '') { if (modelResponse && modelResponse !== '') {
fs.writeFileSync(responseFilePath, modelResponse, 'utf-8') fs.writeFileSync(responseFile.name, modelResponse, 'utf-8')
} }
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@@ -112,8 +115,3 @@ export async function run(): Promise<void> {
// Force exit to prevent hanging on open connections // Force exit to prevent hanging on open connections
process.exit(0) process.exit(0)
} }
function tempDir(): string {
const tempDirectory = process.env['RUNNER_TEMP'] || os.tmpdir()
return tempDirectory
}