Skip to content

Fix getter mode baking env vars into generated code#8

Merged
karngyan merged 1 commit intomainfrom
fix-getter-mode-bu
Feb 8, 2026
Merged

Fix getter mode baking env vars into generated code#8
karngyan merged 1 commit intomainfrom
fix-getter-mode-bu

Conversation

@karngyan
Copy link
Member

@karngyan karngyan commented Feb 8, 2026

Summary

In getter mode, envoverride.Apply() was running at generation time, which baked runtime environment variable values (e.g. secrets) into the generated source code as defaults. This fix skips env overrides during generation when mode is getter, since getter mode already generates os.Getenv() calls for runtime resolution.

Changes

  • Move default mode assignment earlier so it's available before the env override check
  • Skip envoverride.Apply() when in getter mode
  • Add test verifying env var values are not embedded in generated getter-mode output

Copilot AI review requested due to automatic review settings February 8, 2026 22:23
@github-actions
Copy link

github-actions bot commented Feb 8, 2026

📊 Code Coverage Report

total:									(statements)			50.9%
Coverage by file
github.com/gomantics/cfgx/cfgx.go:88:					GenerateFromFile		78.9%
github.com/gomantics/cfgx/cfgx.go:182:					Generate			100.0%
github.com/gomantics/cfgx/cfgx.go:199:					GenerateWithOptions		62.5%
github.com/gomantics/cfgx/cmd/cfgx/common.go:20:			parseFileSize			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:38:				init				0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:43:				runDiff				0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:77:				parseTomlFile			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:104:				computeDiffs			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:178:				deepEqual			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:185:				outputText			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:221:				formatValue			0.0%
github.com/gomantics/cfgx/cmd/cfgx/diff.go:242:				outputJSON			0.0%
github.com/gomantics/cfgx/cmd/cfgx/generate.go:60:			init				0.0%
github.com/gomantics/cfgx/cmd/cfgx/main.go:18:				main				0.0%
github.com/gomantics/cfgx/cmd/cfgx/main.go:32:				init				0.0%
github.com/gomantics/cfgx/cmd/cfgx/watch.go:168:			init				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:33:		Logging				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:37:		File				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:44:		Format				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:51:		Level				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:58:		Rotation			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:62:		Compress			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:71:		MaxAge				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:80:		MaxSize				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:89:		Name				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:96:		Version				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:103:		Enabled				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:112:		MaxEntries			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:121:		Outputs				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:128:		Redis				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:132:		Addr				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:139:		Db				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:148:		Password			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:155:		Ttl				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:164:		ConnMaxLifetime			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:173:		Dsn				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:180:		MaxIdleConns			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:189:		MaxOpenConns			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:198:		Pool				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:202:		Enabled				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:211:		MaxSize				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:220:		MinSize				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:229:		Methods				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:236:		Path				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:243:		RateLimit			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:252:		Enabled				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:261:		Name				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:268:		Priority			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:277:		Addr				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:284:		Cert				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:332:		Debug				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:341:		IdleTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:350:		MaxHeaderBytes			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:359:		ReadTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:368:		ShutdownTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:377:		Timeout				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:386:		WriteTimeout			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:395:		AllowedOrigins			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:402:		Features			0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:409:		Name				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:416:		Ports				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:423:		Weights				0.0%
github.com/gomantics/cfgx/example/getter_config/config.go:430:		Name				0.0%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:13:	Apply				50.0%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:40:	applyNested			88.2%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:79:	convertValue			93.3%
github.com/gomantics/cfgx/internal/envoverride/envoverride.go:111:	convertArray			88.9%
github.com/gomantics/cfgx/internal/generator/file_handler.go:11:	isFileReference			100.0%
github.com/gomantics/cfgx/internal/generator/file_handler.go:18:	loadFileContent			81.2%
github.com/gomantics/cfgx/internal/generator/generator.go:26:		WithPackageName			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:33:		WithEnvOverride			0.0%
github.com/gomantics/cfgx/internal/generator/generator.go:40:		WithInputDir			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:47:		WithMaxFileSize			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:54:		WithMode			100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:61:		New				100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:77:		stripSuffix			40.0%
github.com/gomantics/cfgx/internal/generator/generator.go:88:		writeGetterImports		100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:109:		needsStrconvImport		100.0%
github.com/gomantics/cfgx/internal/generator/generator.go:119:		checkStrconvNeeded		69.2%
github.com/gomantics/cfgx/internal/generator/generator.go:148:		Generate			86.4%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:24:		generateStructsAndVars		63.8%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:128:		collectNestedStructs		57.1%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:166:		generateStruct			89.5%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:208:		generateStructInit		85.2%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:272:		writeArrayOfTablesInit		58.8%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:317:		writeArrayOfStructs		0.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:374:		generateStructsAndGetters	68.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:473:		collectNestedStructsForGetters	57.1%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:502:		generateGetterMethods		62.8%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:578:		generateGetterMethod		100.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:586:		generateTopLevelGetter		100.0%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:599:		writeGetterBody			90.6%
github.com/gomantics/cfgx/internal/generator/struct_gen.go:655:		envVarName			100.0%
github.com/gomantics/cfgx/internal/generator/validation.go:10:		validateFileReferences		100.0%
github.com/gomantics/cfgx/internal/generator/validation.go:20:		validateFileReferencesValue	84.6%
github.com/gomantics/cfgx/internal/generator/validation.go:51:		needsTimeImport			100.0%
github.com/gomantics/cfgx/internal/generator/validation.go:60:		needsTimeImportValue		88.9%
github.com/gomantics/cfgx/internal/generator/validation.go:82:		isDurationString		100.0%
github.com/gomantics/cfgx/internal/generator/value_writer.go:17:	toGoType			94.1%
github.com/gomantics/cfgx/internal/generator/value_writer.go:61:	writeValue			100.0%
github.com/gomantics/cfgx/internal/generator/value_writer.go:66:	writeValueWithIndent		76.5%
github.com/gomantics/cfgx/internal/generator/value_writer.go:105:	writeByteArrayLiteral		88.2%
github.com/gomantics/cfgx/internal/generator/value_writer.go:141:	writeDurationLiteral		80.0%
github.com/gomantics/cfgx/internal/generator/value_writer.go:197:	writeArray			80.0%
github.com/gomantics/cfgx/internal/pkgutil/pkgutil.go:11:		InferName			63.6%
total:									(statements)			50.9%

@karngyan karngyan merged commit d609abd into main Feb 8, 2026
6 checks passed
@karngyan karngyan deleted the fix-getter-mode-bu branch February 8, 2026 22:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a security/behavior issue in “getter” generation mode where environment-variable overrides were being applied during code generation, potentially baking runtime env values (including secrets) into generated source code.

Changes:

  • Default mode is determined earlier in GenerateFromFile so it can gate env-override behavior.
  • envoverride.Apply() is skipped when mode is getter.
  • Adds a regression test ensuring getter-mode output doesn’t embed env var values and still generates os.Getenv(...) lookups.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
cfgx.go Skips generation-time env overrides in getter mode by computing mode earlier and gating envoverride.Apply().
cfgx_test.go Adds a test verifying getter-mode generation doesn’t bake env values into generated code.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +103 to +119
err = GenerateFromFile(opts)
require.NoError(t, err, "GenerateFromFile() should not error")

output, err := os.ReadFile(outputFile)
require.NoError(t, err)

outputStr := string(output)

// The generated code should use the TOML default, NOT the env var value
require.Contains(t, outputStr, `"set-from-env"`,
"getter mode should use TOML default, not env var value")
require.NotContains(t, outputStr, "sk-real-secret-key-12345",
"getter mode must not bake env var values into generated code")

// It should still have the os.Getenv call for runtime override
require.Contains(t, outputStr, `os.Getenv("CONFIG_SECRETS_API_KEY")`,
"getter mode should still generate runtime env var lookups")
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts on generated source text but doesn't verify the generated getter-mode file compiles (unlike the other GenerateFromFile tests below). Adding a quick go build on outputFile would catch missing imports / syntax issues and make the regression test stronger.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +107
// Set default mode if not specified
mode := opts.Mode
if mode == "" {
mode = "static"
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateFromFile’s behavior (including whether env overrides are applied) depends on the exact opts.Mode string matching "getter". Consider normalizing/validating opts.Mode here (e.g., trim + strings.ToLower and reject unknown values) so typos/casing differences don’t accidentally fall back to static-mode behavior and reintroduce secret-baking during generation.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +93
os.Setenv("CONFIG_SECRETS_API_KEY", "sk-real-secret-key-12345")
defer os.Unsetenv("CONFIG_SECRETS_API_KEY")
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use testing.T.Setenv (or capture/restore the prior value) instead of os.Setenv + defer os.Unsetenv. Unsetting can clobber an existing value in the parent environment and makes this test less isolated, especially if tests are run in parallel or the env var is already set on the machine/CI runner.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant