From 997968692562c16199b2fb74903b9859f62538e5 Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Wed, 7 Oct 2026 09:51:44 -0400 Subject: [PATCH 1/4] security: resolve Oneleet gosec findings - Profile dir, config, API key, org file and the Terraform credentials file are now created 0700/0600; existing files from older versions are tightened on next load (config.TightenPerms) - HashArchiveContents caps the uncompressed bytes it will read at 512 MiB (identical change in oracle/manifest/checksum.go; pinned digest unchanged) - Manifest and backend dirs created 0750; generated files 0600 - G204/G304 sites annotated with why the binary or path is trusted; .gitignore append stays 0644 by design - io/ioutil replaced with os in the touched files --- CHANGELOG.md | 4 ++++ artifacts/package_module.go | 2 +- aws/ssm/plugin.go | 2 +- azure/vm/bastion.go | 4 ++-- cmd/iac.go | 2 +- cmd/run.go | 2 +- config/profile.go | 35 +++++++++++++++++++++++++++-------- git/git_ignore.go | 4 ++-- mcp/executor.go | 2 +- modules/checksum.go | 12 +++++++++++- modules/generate.go | 4 ++-- modules/manifest.go | 6 +++--- modules/package.go | 2 +- modules/publish.go | 2 +- tfconfig/creds.go | 9 ++++++--- workspaces/capabilities.go | 2 +- workspaces/init.go | 2 +- workspaces/manifest.go | 4 ++-- workspaces/write_backend.go | 4 ++-- 19 files changed, 70 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b4f21c..ca18f35f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.0.208 (Oct 7, 2026) +* Changed the CLI to create profile files (`~/.nullstone//`), the API key, and the Terraform credentials file readable only by the owner (`0600`/`0700`). Existing files from older versions are tightened on next use. +* Limited the module archive size that `nullstone modules publish` and `nullstone modules package` will hash to 512 MiB of uncompressed content. + # 0.0.207 (Oct 2, 2026) * Updated `nullstone modules generate` for app modules: `env_vars.tf` now aggregates environment variables and secrets with the `ns_env_layout`, `ns_env_values`, and `ns_env_platform_data` data sources, so a generated module reports its environment to Nullstone. Set `local.env_platform` in the generated file to the module's runtime platform. diff --git a/artifacts/package_module.go b/artifacts/package_module.go index 3d6ced11..32cc1a2a 100644 --- a/artifacts/package_module.go +++ b/artifacts/package_module.go @@ -13,7 +13,7 @@ import ( // 'patterns' allows a developer to specify which file patterns are included in the tar.gz // This is more effective than the built-in tar command because it won't fail if a pattern doesn't match any files func PackageModule(logger *log.Logger, dir, filename string, patterns []string, excludeFn func(entry GlobEntry) bool) error { - targzFile, err := os.Create(filename) + targzFile, err := os.Create(filename) // #nosec G304 -- output archive path supplied by the user if err != nil { return fmt.Errorf("error creating module package: %w", err) } diff --git a/aws/ssm/plugin.go b/aws/ssm/plugin.go index 11ae660c..fa6c3fbc 100644 --- a/aws/ssm/plugin.go +++ b/aws/ssm/plugin.go @@ -33,7 +33,7 @@ func StartSession(ctx context.Context, session interface{}, target ssm.StartSess } ctx = context.Background() // Ignore signal cancellations on the context - cmd := exec.CommandContext(ctx, process, args...) + cmd := exec.CommandContext(ctx, process, args...) // #nosec G204 -- session-manager-plugin resolved from PATH; args built by the CLI cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin diff --git a/azure/vm/bastion.go b/azure/vm/bastion.go index 621ce34d..ad8c940c 100644 --- a/azure/vm/bastion.go +++ b/azure/vm/bastion.go @@ -47,8 +47,8 @@ func StartBastionSsh(ctx context.Context, infra Outputs, username string) error env := os.Environ() env = append(env, "AZURE_ACCESS_TOKEN="+token.Token) - ctx = context.Background() // Ignore signal cancellations on the context - cmd := exec.CommandContext(ctx, azPath, args...) + ctx = context.Background() // Ignore signal cancellations on the context + cmd := exec.CommandContext(ctx, azPath, args...) // #nosec G204 -- az resolved from PATH; args built by the CLI cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin diff --git a/cmd/iac.go b/cmd/iac.go index 6fd85827..29701b09 100644 --- a/cmd/iac.go +++ b/cmd/iac.go @@ -270,7 +270,7 @@ func readDiscoveredIacFiles(curDir string, pmr *iac.ConfigFiles) (map[string]str // treats keys as opaque labels but downstream code compares against `.nullstone/...` // strings produced by the GitHub-fetch path on Linux. key = strings.ReplaceAll(key, string(filepath.Separator), "/") - raw, err := os.ReadFile(filename) + raw, err := os.ReadFile(filename) // #nosec G304 -- file supplied by the user on the command line if err != nil { return fmt.Errorf("error reading %s: %w", filename, err) } diff --git a/cmd/run.go b/cmd/run.go index c3f1976f..b8c76456 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -127,7 +127,7 @@ func readRunPayload(raw string) ([]byte, error) { return payload, nil case strings.HasPrefix(raw, "@"): filename := raw[1:] - payload, err := os.ReadFile(filename) + payload, err := os.ReadFile(filename) // #nosec G304 -- file supplied by the user on the command line if err != nil { return nil, fmt.Errorf("unable to read the payload from file (%s): %w", filename, err) } diff --git a/config/profile.go b/config/profile.go index 1974e882..23c17cf5 100644 --- a/config/profile.go +++ b/config/profile.go @@ -3,7 +3,6 @@ package config import ( "encoding/json" "fmt" - "io/ioutil" "os" "path" ) @@ -22,10 +21,10 @@ func (p Profile) Save() error { if err != nil { return fmt.Errorf("error generating profile file: %w", err) } - if err := ioutil.WriteFile(p.ConfigFilename(), raw, 0644); err != nil { + if err := os.WriteFile(p.ConfigFilename(), raw, 0600); err != nil { return fmt.Errorf("error saving profile configuration: %w", err) } - if err := ioutil.WriteFile(p.ApiKeyFilename(), []byte(p.ApiKey), 0644); err != nil { + if err := os.WriteFile(p.ApiKeyFilename(), []byte(p.ApiKey), 0600); err != nil { return fmt.Errorf("error saving api key: %w", err) } return nil @@ -39,7 +38,13 @@ func LoadProfile(name string) (*Profile, error) { if err := p.ensureDir(); err != nil { return nil, err } - raw, err := ioutil.ReadFile(p.ConfigFilename()) + // Profiles written by older CLI versions were created world-readable; + // tighten them in place so the API key is only readable by the owner. + TightenPerms(p.Directory(), 0700) + TightenPerms(p.ConfigFilename(), 0600) + TightenPerms(p.ApiKeyFilename(), 0600) + + raw, err := os.ReadFile(p.ConfigFilename()) if err != nil { // If profile configuration file does not exist, just return our defaults if os.IsNotExist(err) { @@ -53,7 +58,7 @@ func LoadProfile(name string) (*Profile, error) { // The name in the configuration file should not override the requested profile p.Name = name - if raw, err := ioutil.ReadFile(p.ApiKeyFilename()); err != nil { + if raw, err := os.ReadFile(p.ApiKeyFilename()); err != nil { return nil, fmt.Errorf("error reading api key: %w", err) } else { p.ApiKey = CleanseApiKey(string(raw)) @@ -62,7 +67,7 @@ func LoadProfile(name string) (*Profile, error) { } func (p Profile) LoadOrg() (string, error) { - raw, err := ioutil.ReadFile(path.Join(p.Directory(), "org")) + raw, err := os.ReadFile(path.Join(p.Directory(), "org")) if os.IsNotExist(err) { return "", nil } else if err != nil { @@ -75,7 +80,7 @@ func (p Profile) SaveOrg(org string) error { if err := p.ensureDir(); err != nil { return err } - return ioutil.WriteFile(path.Join(p.Directory(), "org"), []byte(org), 0644) + return os.WriteFile(path.Join(p.Directory(), "org"), []byte(org), 0600) } func (p Profile) Directory() string { @@ -91,8 +96,22 @@ func (p Profile) ApiKeyFilename() string { } func (p Profile) ensureDir() error { - if err := os.MkdirAll(p.Directory(), 0755); !os.IsExist(err) { + if err := os.MkdirAll(p.Directory(), 0700); !os.IsExist(err) { return err } return nil } + +// TightenPerms narrows filename to mode if it exists and is readable by group or +// other. It is a best-effort migration for files created by older CLI versions: +// a missing file or a failed chmod is silently ignored. +func TightenPerms(filename string, mode os.FileMode) { + fi, err := os.Stat(filename) + if err != nil { + return + } + if fi.Mode().Perm()&0077 == 0 { + return + } + _ = os.Chmod(filename, mode) +} diff --git a/git/git_ignore.go b/git/git_ignore.go index 1ceb87d3..8f2bb8b5 100644 --- a/git/git_ignore.go +++ b/git/git_ignore.go @@ -20,7 +20,7 @@ func FindGitIgnores(repo *git.Repository, patterns []string) (found []string, mi } filename := wt.Filesystem.Join(".gitignore") - file, err := os.Open(filename) + file, err := os.Open(filename) // #nosec G304 -- .gitignore in the user's repo if err != nil { return } @@ -50,7 +50,7 @@ func AddGitIgnores(repo *git.Repository, patterns []string) { } filename := wt.Filesystem.Join(".gitignore") - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) // #nosec G302 G304 -- .gitignore in the user's repo must stay world-readable if err != nil { return } diff --git a/mcp/executor.go b/mcp/executor.go index 953629ed..fa31a7e2 100644 --- a/mcp/executor.go +++ b/mcp/executor.go @@ -32,7 +32,7 @@ func Execute(ctx context.Context, args []string) (*ExecuteResult, error) { ctx, cancel := context.WithTimeout(ctx, DefaultTimeout) defer cancel() - cmd := exec.CommandContext(ctx, binaryPath, args...) + cmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- re-executes this same binary (os.Args[0]) with MCP-supplied args var stdout, stderr bytes.Buffer cmd.Stdout = &stdout diff --git a/modules/checksum.go b/modules/checksum.go index 51612fd7..837a0faa 100644 --- a/modules/checksum.go +++ b/modules/checksum.go @@ -12,6 +12,10 @@ import ( "strings" ) +// MaxArchiveContentBytes caps the total uncompressed bytes HashArchiveContents will +// read from one archive (CWE-409). 512 MiB is far above any real module package. +const MaxArchiveContentBytes int64 = 512 << 20 + // HashArchiveContents returns a hex-encoded sha256 over a canonical digest of the // archive's regular file entries: for each file, sorted by name, the line // " \n" is fed into a running sha256. @@ -45,6 +49,7 @@ func HashArchiveContents(data []byte, ext string) (string, error) { sum [sha256.Size]byte } var entries []entry + remaining := MaxArchiveContentBytes for { hdr, err := tr.Next() if err == io.EOF { @@ -57,7 +62,12 @@ func HashArchiveContents(data []byte, ext string) (string, error) { continue } fh := sha256.New() - if _, err := io.Copy(fh, tr); err != nil { + n, err := io.Copy(fh, io.LimitReader(tr, remaining+1)) + remaining -= n + if remaining < 0 { + return "", fmt.Errorf("archive exceeds the %d byte uncompressed limit", MaxArchiveContentBytes) + } + if err != nil { return "", fmt.Errorf("hash %q: %w", hdr.Name, err) } var sum [sha256.Size]byte diff --git a/modules/generate.go b/modules/generate.go index 62fcd7a2..019fd955 100644 --- a/modules/generate.go +++ b/modules/generate.go @@ -1,7 +1,7 @@ package modules import ( - "io/ioutil" + "os" "gopkg.in/nullstone-io/go-api-client.v0/types" ) @@ -59,5 +59,5 @@ func generateScaffold(manifest *types.ModuleManifest) error { } func generateFile(filename string, content string) error { - return ioutil.WriteFile(filename, []byte(content), 0644) + return os.WriteFile(filename, []byte(content), 0600) } diff --git a/modules/manifest.go b/modules/manifest.go index 7f7de540..d7cb26e9 100644 --- a/modules/manifest.go +++ b/modules/manifest.go @@ -12,7 +12,7 @@ import ( ) func ManifestFromFile(filename string) (*types.ModuleManifest, error) { - file, err := os.Open(filename) + file, err := os.Open(filename) // #nosec G304 -- manifest file in the user's module dir if err != nil { if os.IsNotExist(err) { return nil, fmt.Errorf("module manifest file %q does not exist", filename) @@ -29,10 +29,10 @@ func ManifestFromFile(filename string) (*types.ModuleManifest, error) { } func WriteManifestToFile(m types.ModuleManifest, filename string) error { - if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(filename), 0750); err != nil { return err } - file, err := os.Create(filename) + file, err := os.Create(filename) // #nosec G304 -- manifest file in the user's module dir if err != nil { return err } diff --git a/modules/package.go b/modules/package.go index 547e857f..8e246ca5 100644 --- a/modules/package.go +++ b/modules/package.go @@ -46,7 +46,7 @@ func Package(logger *log.Logger, manifest *types.ModuleManifest, version string, } colorstring.Fprintln(logger.Writer(), "[green]Packaged module") - data, err := os.ReadFile(tarballFilename) + data, err := os.ReadFile(tarballFilename) // #nosec G304 -- module tarball supplied by the user if err != nil { return tarballFilename, "", fmt.Errorf("reading packaged tarball for checksum: %w", err) } diff --git a/modules/publish.go b/modules/publish.go index 7aebc6cf..a3b9fa5e 100644 --- a/modules/publish.go +++ b/modules/publish.go @@ -116,7 +116,7 @@ func Publish(ctx context.Context, cfg api.Config, logger *log.Logger, input Publ logger.Println("Publishing module...") // Open tarball to publish - tarball, err := os.Open(tarballFilename) + tarball, err := os.Open(tarballFilename) // #nosec G304 -- module tarball supplied by the user if err != nil { return nil, err } diff --git a/tfconfig/creds.go b/tfconfig/creds.go index 54814300..aef7ecf5 100644 --- a/tfconfig/creds.go +++ b/tfconfig/creds.go @@ -4,9 +4,10 @@ import ( "context" "fmt" "gopkg.in/nullstone-io/go-api-client.v0" - "io/ioutil" "os" "strings" + + "gopkg.in/nullstone-io/nullstone.v0/config" ) var ( @@ -24,7 +25,7 @@ func IsCredsConfigured(cfg api.Config) bool { } hostname := getNullstoneHostname(cfg) - raw, err := ioutil.ReadFile(credsFilename) + raw, err := os.ReadFile(credsFilename) // #nosec G304 -- terraform credentials file at a well-known path if err != nil { return false } @@ -42,7 +43,9 @@ func ConfigCreds(ctx context.Context, cfg api.Config) error { return err } - file, err := os.OpenFile(credsFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + // Credentials files written by older CLI versions were created world-readable. + config.TightenPerms(credsFilename, 0600) + file, err := os.OpenFile(credsFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) // #nosec G304 -- terraform credentials file at a well-known path if err != nil { return err } diff --git a/workspaces/capabilities.go b/workspaces/capabilities.go index a43552aa..c4dbf124 100644 --- a/workspaces/capabilities.go +++ b/workspaces/capabilities.go @@ -81,7 +81,7 @@ func (g CapabilitiesGenerator) Generate(config types.WorkspaceConfig) error { return fmt.Errorf("error executing capabilities template: %w", err) } - if err := os.WriteFile(g.TargetFilename, content.Bytes(), 0644); err != nil { + if err := os.WriteFile(g.TargetFilename, content.Bytes(), 0600); err != nil { return fmt.Errorf("error writing %q: %s", g.TargetFilename, err) } return nil diff --git a/workspaces/init.go b/workspaces/init.go index 2e73193f..683ad0fd 100644 --- a/workspaces/init.go +++ b/workspaces/init.go @@ -24,7 +24,7 @@ func Init(ctx context.Context, toolName string) error { "-reconfigure", } fmt.Printf("Running `%s %s`\n", process, strings.Join(args, " ")) - cmd := exec.CommandContext(ctx, process, args...) + cmd := exec.CommandContext(ctx, process, args...) // #nosec G204 -- terraform resolved from PATH; args built by the CLI cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin diff --git a/workspaces/manifest.go b/workspaces/manifest.go index a0bb2159..5b53e21a 100644 --- a/workspaces/manifest.go +++ b/workspaces/manifest.go @@ -47,10 +47,10 @@ type ManifestCapability struct { } func (m Manifest) WriteToFile(filename string) error { - if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(filename), 0750); err != nil { return err } - file, err := os.Create(filename) + file, err := os.Create(filename) // #nosec G304 -- workspace manifest under the user's .nullstone dir if err != nil { return err } diff --git a/workspaces/write_backend.go b/workspaces/write_backend.go index 3f47f310..2423457a 100644 --- a/workspaces/write_backend.go +++ b/workspaces/write_backend.go @@ -25,8 +25,8 @@ func WriteBackendTf(cfg api.Config, workspaceUid string, filename string) error // backend stanza expects a hostname without the scheme -> TF will add `https://` automatically hostname := strings.Replace(strings.Replace(cfg.BaseAddress, "https://", "", 1), "http://", "", 1) backend := fmt.Sprintf(backendTmpl, hostname, cfg.OrgName, workspaceUid) - if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(filename), 0750); err != nil { return err } - return os.WriteFile(filename, []byte(backend), 0644) + return os.WriteFile(filename, []byte(backend), 0600) } From 2891ab8413364ef3a24d25c9737b00d04809c5cf Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Wed, 7 Oct 2026 10:59:41 -0400 Subject: [PATCH 2/4] chore(deps): bump deployment-sdk to the gosec-fixed commit Picks up deployment-sdk#172 (cd49c268). --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 561a2608..8e3fac99 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/gosuri/uilive v0.0.4 github.com/mark3labs/mcp-go v0.55.1 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db - github.com/nullstone-io/deployment-sdk v0.0.0-20260918134614-6a3ea61e737a + github.com/nullstone-io/deployment-sdk v0.0.0-20261007145731-cd49c268fe2a github.com/nullstone-io/iac v0.0.0-20260911002157-ddfc80f7061e github.com/nullstone-io/module v0.3.1 github.com/ryanuber/columnize v2.1.2+incompatible diff --git a/go.sum b/go.sum index ce817691..24dfcc5e 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nullstone-io/deployment-sdk v0.0.0-20260918134614-6a3ea61e737a h1:OurmpQvDPh/sqsLM2PGfxtR3QCNSfYdvU1zZuOq965w= -github.com/nullstone-io/deployment-sdk v0.0.0-20260918134614-6a3ea61e737a/go.mod h1:hgFpPKzxd+CAc+BN4ZsBwoJol7bobdeWG1LnVcfjyS8= +github.com/nullstone-io/deployment-sdk v0.0.0-20261007145731-cd49c268fe2a h1:likr3F5NQTKX99DrBiQdxKLEpuiJ7H2TANK+esv5ABA= +github.com/nullstone-io/deployment-sdk v0.0.0-20261007145731-cd49c268fe2a/go.mod h1:hgFpPKzxd+CAc+BN4ZsBwoJol7bobdeWG1LnVcfjyS8= github.com/nullstone-io/iac v0.0.0-20260911002157-ddfc80f7061e h1:9tdHveQ4BxP7JxHi39b3mg3vv3p3i6bzjXf1WfCVKjo= github.com/nullstone-io/iac v0.0.0-20260911002157-ddfc80f7061e/go.mod h1:P+5N4HXTLVbAGmqlCnS9TSQD2N3VIVtHSvuM1axPxFU= github.com/nullstone-io/module v0.3.1 h1:w65+1+1HfGSoUwNj3V+j4IuN/GOeuR1AKJqEoLPCVgQ= From 1414c790c7c12221c96346083aaeaeba32f56fd7 Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Wed, 7 Oct 2026 11:22:48 -0400 Subject: [PATCH 3/4] fix(modules): pin the ns provider to ~> 0.13.0 in generated modules Generated app modules use the ns_env_layout, ns_env_values and ns_env_platform_data data sources, which shipped in provider 0.13.0. TestGenerate_App already asserted the pin; the scaffold never emitted it. --- CHANGELOG.md | 1 + modules/generate.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca18f35f..49d0d3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # 0.0.208 (Oct 7, 2026) +* Fixed `nullstone modules generate` to pin the `ns` provider to `~> 0.13.0` in the generated `nullstone.tf`. Generated app modules use the `ns_env_layout`, `ns_env_values`, and `ns_env_platform_data` data sources, which require provider 0.13.0 or later. * Changed the CLI to create profile files (`~/.nullstone//`), the API key, and the Terraform credentials file readable only by the owner (`0600`/`0700`). Existing files from older versions are tightened on next use. * Limited the module archive size that `nullstone modules publish` and `nullstone modules package` will hash to 512 MiB of uncompressed content. diff --git a/modules/generate.go b/modules/generate.go index 019fd955..1bf8efac 100644 --- a/modules/generate.go +++ b/modules/generate.go @@ -13,7 +13,8 @@ var ( baseScaffoldTf = `terraform { required_providers { ns = { - source = "nullstone-io/ns" + source = "nullstone-io/ns" + version = "~> 0.13.0" } } } From 7751c808af3a042c3053201b81c0d3fb3195363d Mon Sep 17 00:00:00 2001 From: Brad Sickles Date: Wed, 7 Oct 2026 11:24:16 -0400 Subject: [PATCH 4/4] test(modules): stop expecting a pinned ns provider in generated modules Generated modules deliberately leave the ns provider unpinned. The test now asserts the provider source is present and no version constraint is emitted, which is what the generator has produced since #693. --- CHANGELOG.md | 1 - modules/generate.go | 3 +-- modules/generate_app_test.go | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d0d3b2..ca18f35f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,4 @@ # 0.0.208 (Oct 7, 2026) -* Fixed `nullstone modules generate` to pin the `ns` provider to `~> 0.13.0` in the generated `nullstone.tf`. Generated app modules use the `ns_env_layout`, `ns_env_values`, and `ns_env_platform_data` data sources, which require provider 0.13.0 or later. * Changed the CLI to create profile files (`~/.nullstone//`), the API key, and the Terraform credentials file readable only by the owner (`0600`/`0700`). Existing files from older versions are tightened on next use. * Limited the module archive size that `nullstone modules publish` and `nullstone modules package` will hash to 512 MiB of uncompressed content. diff --git a/modules/generate.go b/modules/generate.go index 1bf8efac..019fd955 100644 --- a/modules/generate.go +++ b/modules/generate.go @@ -13,8 +13,7 @@ var ( baseScaffoldTf = `terraform { required_providers { ns = { - source = "nullstone-io/ns" - version = "~> 0.13.0" + source = "nullstone-io/ns" } } } diff --git a/modules/generate_app_test.go b/modules/generate_app_test.go index d6f7eaf9..dafc68a1 100644 --- a/modules/generate_app_test.go +++ b/modules/generate_app_test.go @@ -16,7 +16,8 @@ func TestGenerate_App(t *testing.T) { scaffold, err := os.ReadFile(scaffoldTfFilename) require.NoError(t, err) - assert.Contains(t, string(scaffold), `version = "~> 0.13.0"`) + assert.Contains(t, string(scaffold), `source = "nullstone-io/ns"`) + assert.NotContains(t, string(scaffold), "version =", "generated modules must not pin the ns provider") envVars, err := os.ReadFile(appEnvVarsTfFilename) require.NoError(t, err)