Skip to content

Commit f8972cb

Browse files
authored
Extract constants to improve maintainability (#777)
Why Hard-coded values scattered across files made updates difficult. Summary Centralized BUILD_ENV_VARS, DANGEROUS_ENV_VARS, DEFAULT_EXPORT_DIR, and DEFAULT_CONFIG_FILE constants in types.ts. Key improvements - Single source of truth for environment variable whitelists/blacklists - Replaced 10+ magic strings with named constants - Added security documentation for DANGEROUS_ENV_VARS Impact - Existing: No functional changes, purely refactoring - New: Easier to modify default paths and security policies Risks None.
1 parent 0af3948 commit f8972cb

File tree

4 files changed

+252
-44
lines changed

4 files changed

+252
-44
lines changed

package/configExporter/cli.ts

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@ import { resolve, dirname, sep, delimiter, basename } from "path"
66
import { inspect } from "util"
77
import { load as loadYaml } from "js-yaml"
88
import yargs from "yargs"
9-
import { ExportOptions, ConfigMetadata, FileOutput } from "./types"
9+
import {
10+
ExportOptions,
11+
ConfigMetadata,
12+
FileOutput,
13+
BUILD_ENV_VARS,
14+
isBuildEnvVar,
15+
isDangerousEnvVar,
16+
DEFAULT_EXPORT_DIR,
17+
DEFAULT_CONFIG_FILE
18+
} from "./types"
1019
import { YamlSerializer } from "./yamlSerializer"
1120
import { FileWriter } from "./fileWriter"
1221
import { ConfigFileLoader, generateSampleConfigFile } from "./configFile"
@@ -27,19 +36,6 @@ try {
2736
)
2837
}
2938

30-
/**
31-
* Environment variable names that can be set by build configurations
32-
*/
33-
const BUILD_ENV_VARS = [
34-
"NODE_ENV",
35-
"RAILS_ENV",
36-
"NODE_OPTIONS",
37-
"BABEL_ENV",
38-
"WEBPACK_SERVE",
39-
"CLIENT_BUNDLE_ONLY",
40-
"SERVER_BUNDLE_ONLY"
41-
] as const
42-
4339
/**
4440
* Saves current values of build environment variables for later restoration
4541
* @returns Object mapping variable names to their current values (or undefined)
@@ -132,8 +128,7 @@ export async function run(args: string[]): Promise<number> {
132128
if (resolvedOptions.build) {
133129
const loader = new ConfigFileLoader(resolvedOptions.configFile)
134130
if (!loader.exists()) {
135-
const configPath =
136-
resolvedOptions.configFile || "config/shakapacker-builds.yml"
131+
const configPath = resolvedOptions.configFile || DEFAULT_CONFIG_FILE
137132
throw new Error(
138133
`--build requires a config file but ${configPath} not found. Run --init to create it.`
139134
)
@@ -182,8 +177,7 @@ QUICK START (for troubleshooting):
182177
.option("init", {
183178
type: "boolean",
184179
default: false,
185-
description:
186-
"Generate config/shakapacker-builds.yml (use with --ssr for SSR builds)"
180+
description: `Generate ${DEFAULT_CONFIG_FILE} (use with --ssr for SSR builds)`
187181
})
188182
.option("ssr", {
189183
type: "boolean",
@@ -206,8 +200,7 @@ QUICK START (for troubleshooting):
206200
})
207201
.option("config-file", {
208202
type: "string",
209-
description:
210-
"Path to config file (default: config/shakapacker-builds.yml)"
203+
description: `Path to config file (default: ${DEFAULT_CONFIG_FILE})`
211204
})
212205
// Validation Options
213206
.option("validate", {
@@ -459,17 +452,14 @@ function applyDefaults(options: ExportOptions): ExportOptions {
459452
!updatedOptions.output &&
460453
!updatedOptions.saveDir
461454
) {
462-
updatedOptions.saveDir = resolve(
463-
process.cwd(),
464-
"shakapacker-config-exports"
465-
)
455+
updatedOptions.saveDir = resolve(process.cwd(), DEFAULT_EXPORT_DIR)
466456
}
467457

468458
return updatedOptions
469459
}
470460

471461
function runInitCommand(options: ExportOptions): number {
472-
const configPath = options.configFile || "config/shakapacker-builds.yml"
462+
const configPath = options.configFile || DEFAULT_CONFIG_FILE
473463
const fullPath = resolve(process.cwd(), configPath)
474464

475465
// Check if SSR variant is requested via --ssr flag
@@ -576,7 +566,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
576566
// Validate that config file exists
577567
const loader = new ConfigFileLoader(options.configFile)
578568
if (!loader.exists()) {
579-
const configPath = options.configFile || "config/shakapacker-builds.yml"
569+
const configPath = options.configFile || DEFAULT_CONFIG_FILE
580570
throw new Error(
581571
`Config file ${configPath} not found. Run --init to create it.`
582572
)
@@ -609,7 +599,7 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
609599
// Handle empty builds edge case
610600
if (buildsToValidate.length === 0) {
611601
throw new Error(
612-
`No builds found in config file. Add at least one build to config/shakapacker-builds.yml or run --init to see examples.`
602+
`No builds found in config file. Add at least one build to ${DEFAULT_CONFIG_FILE} or run --init to see examples.`
613603
)
614604
}
615605
}
@@ -722,8 +712,7 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
722712

723713
const loader = new ConfigFileLoader(resolvedOptions.configFile)
724714
if (!loader.exists()) {
725-
const configPath =
726-
resolvedOptions.configFile || "config/shakapacker-builds.yml"
715+
const configPath = resolvedOptions.configFile || DEFAULT_CONFIG_FILE
727716
throw new Error(
728717
`Config file ${configPath} not found. Run --init to create it.`
729718
)
@@ -810,7 +799,7 @@ async function runDoctorMode(
810799
const createdFiles: string[] = []
811800

812801
// Check if config file exists - always use it for doctor mode
813-
const configFilePath = options.configFile || "config/shakapacker-builds.yml"
802+
const configFilePath = options.configFile || DEFAULT_CONFIG_FILE
814803
const loader = new ConfigFileLoader(configFilePath)
815804

816805
if (loader.exists()) {
@@ -1102,29 +1091,20 @@ async function loadConfigsForEnv(
11021091

11031092
// Set environment variables from config
11041093
// Security: Only allow specific environment variables to prevent malicious configs
1105-
const DANGEROUS_ENV_VARS = [
1106-
"PATH",
1107-
"HOME",
1108-
"LD_PRELOAD",
1109-
"LD_LIBRARY_PATH",
1110-
"DYLD_LIBRARY_PATH",
1111-
"DYLD_INSERT_LIBRARIES"
1112-
]
1113-
11141094
if (options.verbose) {
11151095
console.log(
11161096
`[Config Exporter] Setting environment variables from build config...`
11171097
)
11181098
}
11191099

11201100
for (const [key, value] of Object.entries(resolvedBuild.environment)) {
1121-
if (DANGEROUS_ENV_VARS.includes(key)) {
1101+
if (isDangerousEnvVar(key)) {
11221102
console.warn(
11231103
`[Config Exporter] Warning: Skipping dangerous environment variable: ${key}`
11241104
)
11251105
continue
11261106
}
1127-
if (!(BUILD_ENV_VARS as readonly string[]).includes(key)) {
1107+
if (!isBuildEnvVar(key)) {
11281108
console.warn(
11291109
`[Config Exporter] Warning: Skipping non-whitelisted environment variable: ${key}. ` +
11301110
`Allowed variables are: ${BUILD_ENV_VARS.join(", ")}`

package/configExporter/configFile.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
BundlerConfigFile,
66
BuildConfig,
77
ResolvedBuildConfig,
8-
ExportOptions
8+
ExportOptions,
9+
DEFAULT_CONFIG_FILE
910
} from "./types"
1011

1112
/**
@@ -18,12 +19,12 @@ export class ConfigFileLoader {
1819
private configFilePath: string
1920

2021
/**
21-
* @param configFilePath - Path to config file (defaults to config/shakapacker-builds.yml in cwd)
22+
* @param configFilePath - Path to config file (defaults to DEFAULT_CONFIG_FILE in cwd)
2223
* @throws Error if path is outside project directory
2324
*/
2425
constructor(configFilePath?: string) {
2526
this.configFilePath =
26-
configFilePath || resolve(process.cwd(), "config/shakapacker-builds.yml")
27+
configFilePath || resolve(process.cwd(), DEFAULT_CONFIG_FILE)
2728
this.validateConfigPath()
2829
}
2930

package/configExporter/types.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,67 @@
1+
/**
2+
* Environment variable names that can be set by build configurations.
3+
* These are the only environment variables that build configs are allowed to set.
4+
* This whitelist prevents malicious configs from modifying critical system variables.
5+
*/
6+
export const BUILD_ENV_VARS = [
7+
"NODE_ENV",
8+
"RAILS_ENV",
9+
"NODE_OPTIONS",
10+
"BABEL_ENV",
11+
"WEBPACK_SERVE",
12+
"CLIENT_BUNDLE_ONLY",
13+
"SERVER_BUNDLE_ONLY"
14+
] as const
15+
16+
/**
17+
* Environment variables that must never be set by build configurations.
18+
* Setting these could compromise system security or cause unexpected behavior.
19+
*/
20+
export const DANGEROUS_ENV_VARS = [
21+
"PATH",
22+
"HOME",
23+
"LD_PRELOAD",
24+
"LD_LIBRARY_PATH",
25+
"DYLD_LIBRARY_PATH",
26+
"DYLD_INSERT_LIBRARIES"
27+
] as const
28+
29+
/**
30+
* Type predicate to check if a string is in the BUILD_ENV_VARS whitelist
31+
*
32+
* Note: The type assertion is necessary because TypeScript's type system cannot
33+
* infer that .includes() on a readonly const array will properly narrow the type.
34+
* The assertion is safe because we're only widening the type for the includes() check.
35+
*/
36+
export function isBuildEnvVar(
37+
key: string
38+
): key is (typeof BUILD_ENV_VARS)[number] {
39+
return (BUILD_ENV_VARS as readonly string[]).includes(key)
40+
}
41+
42+
/**
43+
* Type predicate to check if a string is in the DANGEROUS_ENV_VARS blacklist
44+
*
45+
* Note: The type assertion is necessary because TypeScript's type system cannot
46+
* infer that .includes() on a readonly const array will properly narrow the type.
47+
* The assertion is safe because we're only widening the type for the includes() check.
48+
*/
49+
export function isDangerousEnvVar(
50+
key: string
51+
): key is (typeof DANGEROUS_ENV_VARS)[number] {
52+
return (DANGEROUS_ENV_VARS as readonly string[]).includes(key)
53+
}
54+
55+
/**
56+
* Default directory for config exports when using --doctor or file output modes.
57+
*/
58+
export const DEFAULT_EXPORT_DIR = "shakapacker-config-exports"
59+
60+
/**
61+
* Default config file path for bundler build configurations.
62+
*/
63+
export const DEFAULT_CONFIG_FILE = "config/shakapacker-builds.yml"
64+
165
export interface ExportOptions {
266
doctor?: boolean
367
saveDir?: string

0 commit comments

Comments
 (0)