fix: redact secret values from env (#252)

This commit is contained in:
jdx 2025-08-22 09:38:59 -05:00 committed by GitHub
parent 8a7168b4f6
commit 5e785b73cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 228 additions and 5 deletions

72
.github/workflows/test-redacted-env.yml vendored Normal file
View file

@ -0,0 +1,72 @@
name: Test Redacted Environment Variables
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
test-redacted-env:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create test mise config with sensitive values
run: |
cat > .mise.toml << 'EOF'
[env]
PUBLIC_VAR = "this-is-public"
API_KEY = {value = "secret-api-key-12345", redact = true}
SECRET_TOKEN = {value = "supersecret-token-xyz", redact = true}
DATABASE_PASSWORD = {value = "db-pass-789", redact = true}
EOF
- name: Setup mise
uses: ./
- name: Verify environment variables are exported
run: |
echo "Checking if environment variables are set..."
# Check that public var is set
if [ "$PUBLIC_VAR" != "this-is-public" ]; then
echo "ERROR: PUBLIC_VAR not set correctly"
exit 1
fi
echo "✓ PUBLIC_VAR is set correctly"
# Check that sensitive vars are set (but their values should be masked in logs)
if [ -z "$API_KEY" ]; then
echo "ERROR: API_KEY not set"
exit 1
fi
echo "✓ API_KEY is set"
if [ -z "$SECRET_TOKEN" ]; then
echo "ERROR: SECRET_TOKEN not set"
exit 1
fi
echo "✓ SECRET_TOKEN is set"
if [ -z "$DATABASE_PASSWORD" ]; then
echo "ERROR: DATABASE_PASSWORD not set"
exit 1
fi
echo "✓ DATABASE_PASSWORD is set"
- name: Test that sensitive values are masked (will show *** if properly masked)
run: |
echo "Testing value masking..."
echo "API_KEY value: $API_KEY"
echo "SECRET_TOKEN value: $SECRET_TOKEN"
echo "DATABASE_PASSWORD value: $DATABASE_PASSWORD"
echo "PUBLIC_VAR value: $PUBLIC_VAR"
# This should show the actual values in the step output,
# but GitHub Actions should mask them if core.setSecret was called
- name: Verify mise version
run: mise --version

69
dist/index.js generated vendored
View file

@ -50028,8 +50028,7 @@ async function run() {
await miseLs();
const loadEnv = core.getBooleanInput('env');
if (loadEnv) {
const output = await exec.getExecOutput('mise', ['env', '--dotenv']);
fs.appendFileSync(process.env.GITHUB_ENV, output.stdout);
await exportMiseEnv();
}
}
catch (err) {
@ -50039,6 +50038,72 @@ async function run() {
throw err;
}
}
async function exportMiseEnv() {
core.startGroup('Exporting mise environment variables');
// Check if mise supports --redacted flags based on version input
const supportsRedacted = checkMiseSupportsRedacted();
if (supportsRedacted) {
try {
// First, get the redacted values to identify what needs masking
const redactedOutput = await exec.getExecOutput('mise', ['env', '--redacted', '--json'], { silent: true });
const redactedVars = JSON.parse(redactedOutput.stdout);
// Mask sensitive values in GitHub Actions
for (const [key, actualValue] of Object.entries(redactedVars)) {
core.setSecret(actualValue);
core.info(`Masked sensitive value for: ${key}`);
}
// Then get the actual values
const actualOutput = await exec.getExecOutput('mise', ['env', '--json']);
const actualVars = JSON.parse(actualOutput.stdout);
// Export all environment variables
for (const [key, value] of Object.entries(actualVars)) {
if (typeof value === 'string') {
core.exportVariable(key, value);
}
}
}
catch {
// Fall back to dotenv format if the redacted command fails
core.info('Falling back to dotenv format');
const output = await exec.getExecOutput('mise', ['env', '--dotenv']);
fs.appendFileSync(process.env.GITHUB_ENV, output.stdout);
}
}
else {
// Fall back to the old --dotenv format for older versions
const output = await exec.getExecOutput('mise', ['env', '--dotenv']);
fs.appendFileSync(process.env.GITHUB_ENV, output.stdout);
}
core.endGroup();
}
function checkMiseSupportsRedacted() {
const version = core.getInput('version');
// If no version is specified, assume latest which supports redacted
if (!version) {
return true;
}
// Parse the version string (remove 'v' prefix if present)
const cleanVersion = version.replace(/^v/, '');
const versionMatch = cleanVersion.match(/^(\d+)\.(\d+)\.(\d+)/);
if (!versionMatch) {
// If we can't parse the version, assume it supports redacted
return true;
}
const [, year, month, patch] = versionMatch;
const yearNum = parseInt(year, 10);
const monthNum = parseInt(month, 10);
const patchNum = parseInt(patch, 10);
// Check if version is >= 2025.8.17
if (yearNum > 2025)
return true;
if (yearNum === 2025) {
if (monthNum > 8)
return true;
if (monthNum === 8 && patchNum >= 17)
return true;
}
return false;
}
async function setEnvVars() {
core.startGroup('Setting env vars');
const set = (k, v) => {

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

7
package-lock.json generated
View file

@ -13,6 +13,7 @@
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.5.0",
"@types/handlebars": "^4.0.40",
"handlebars": "^4.7.8"
},
"devDependencies": {
@ -1912,6 +1913,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/handlebars": {
"version": "4.0.40",
"resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.40.tgz",
"integrity": "sha512-sGWNtsjNrLOdKha2RV1UeF8+UbQnPSG7qbe5wwbni0mw4h2gHXyPFUMOC+xwGirIiiydM/HSqjDO4rk6NFB18w==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",

View file

@ -33,6 +33,7 @@
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.5.0",
"@types/handlebars": "^4.0.40",
"handlebars": "^4.7.8"
},
"devDependencies": {

View file

@ -71,8 +71,7 @@ async function run(): Promise<void> {
await miseLs()
const loadEnv = core.getBooleanInput('env')
if (loadEnv) {
const output = await exec.getExecOutput('mise', ['env', '--dotenv'])
fs.appendFileSync(process.env.GITHUB_ENV!, output.stdout)
await exportMiseEnv()
}
} catch (err) {
if (err instanceof Error) core.setFailed(err.message)
@ -80,6 +79,85 @@ async function run(): Promise<void> {
}
}
async function exportMiseEnv(): Promise<void> {
core.startGroup('Exporting mise environment variables')
// Check if mise supports --redacted flags based on version input
const supportsRedacted = checkMiseSupportsRedacted()
if (supportsRedacted) {
try {
// First, get the redacted values to identify what needs masking
const redactedOutput = await exec.getExecOutput(
'mise',
['env', '--redacted', '--json'],
{ silent: true }
)
const redactedVars = JSON.parse(redactedOutput.stdout)
// Mask sensitive values in GitHub Actions
for (const [key, actualValue] of Object.entries(redactedVars)) {
core.setSecret(actualValue as string)
core.info(`Masked sensitive value for: ${key}`)
}
// Then get the actual values
const actualOutput = await exec.getExecOutput('mise', ['env', '--json'])
const actualVars = JSON.parse(actualOutput.stdout)
// Export all environment variables
for (const [key, value] of Object.entries(actualVars)) {
if (typeof value === 'string') {
core.exportVariable(key, value)
}
}
} catch {
// Fall back to dotenv format if the redacted command fails
core.info('Falling back to dotenv format')
const output = await exec.getExecOutput('mise', ['env', '--dotenv'])
fs.appendFileSync(process.env.GITHUB_ENV!, output.stdout)
}
} else {
// Fall back to the old --dotenv format for older versions
const output = await exec.getExecOutput('mise', ['env', '--dotenv'])
fs.appendFileSync(process.env.GITHUB_ENV!, output.stdout)
}
core.endGroup()
}
function checkMiseSupportsRedacted(): boolean {
const version = core.getInput('version')
// If no version is specified, assume latest which supports redacted
if (!version) {
return true
}
// Parse the version string (remove 'v' prefix if present)
const cleanVersion = version.replace(/^v/, '')
const versionMatch = cleanVersion.match(/^(\d+)\.(\d+)\.(\d+)/)
if (!versionMatch) {
// If we can't parse the version, assume it supports redacted
return true
}
const [, year, month, patch] = versionMatch
const yearNum = parseInt(year, 10)
const monthNum = parseInt(month, 10)
const patchNum = parseInt(patch, 10)
// Check if version is >= 2025.8.17
if (yearNum > 2025) return true
if (yearNum === 2025) {
if (monthNum > 8) return true
if (monthNum === 8 && patchNum >= 17) return true
}
return false
}
async function setEnvVars(): Promise<void> {
core.startGroup('Setting env vars')
const set = (k: string, v: string): void => {