Ignore hidden entries during secrets bootstrap
This commit is contained in:
@@ -293,10 +293,13 @@ operation once before starting concurrent work. The selected path may itself be
|
||||
symlink; ConfigKit resolves it to an existing canonical directory before examining
|
||||
its immediate entries.
|
||||
|
||||
The bootstrap enumerates at most 1,024 entries and sorts them by filename. Every
|
||||
entry must have an ASCII environment-variable name and be a regular, nonsymlink
|
||||
file. Metadata for all entries is validated before file content is read or process
|
||||
state is changed. Existing environment variables win, including variables whose
|
||||
The bootstrap enumerates at most 1,024 entries, including dot-prefixed entries.
|
||||
Entries whose names begin with `.` (files, directories, or symlinks, such as `.git`
|
||||
and `.gitignore`) are ignored before name or metadata validation, are never opened,
|
||||
and are omitted from the report. Remaining entries are sorted by filename; each
|
||||
must have an ASCII environment-variable name and be a regular, nonsymlink
|
||||
file. Metadata for all remaining entries is validated before file content is read
|
||||
or process state is changed. Existing environment variables win, including variables whose
|
||||
value is empty; their files are not opened. Each eligible file is limited to 1 MiB,
|
||||
and eligible content is limited to 8 MiB in total. ConfigKit removes exactly one
|
||||
terminal LF or CRLF, preserves all other whitespace, accepts an empty value, and
|
||||
|
||||
@@ -130,6 +130,7 @@ type stagedSecret struct {
|
||||
// LoadSecretsDir explicitly loads immediate regular files from directory into
|
||||
// previously unset process environment variables. Applications should call it
|
||||
// once during startup, before concurrent work; Load never calls it.
|
||||
// Dot-prefixed entries are ignored and omitted from the report.
|
||||
func LoadSecretsDir(directory string) (*SecretsReport, error) {
|
||||
return loadSecretsDir(directory, secretsEnvironment{lookup: os.LookupEnv, set: os.Setenv}, secretsAccess{
|
||||
openDirectory: os.Open,
|
||||
@@ -168,13 +169,17 @@ func loadSecretsDir(directory string, environment secretsEnvironment, access sec
|
||||
if closeErr != nil {
|
||||
return report, secretsFailure(CodeSecretsRead, "secrets directory could not be closed: "+strconv.Quote(canonical))
|
||||
}
|
||||
entryLimitExceeded := len(entries) > secretsEntryLimit
|
||||
entries = slices.DeleteFunc(entries, func(entry fs.DirEntry) bool {
|
||||
return strings.HasPrefix(entry.Name(), ".")
|
||||
})
|
||||
slices.SortFunc(entries, func(left, right fs.DirEntry) int { return strings.Compare(left.Name(), right.Name()) })
|
||||
report.entries = make([]SecretFileResult, len(entries))
|
||||
for index, entry := range entries {
|
||||
name := entry.Name()
|
||||
report.entries[index] = SecretFileResult{Name: name, Path: filepath.Join(canonical, name), Status: SecretFileNotApplied}
|
||||
}
|
||||
if len(entries) > secretsEntryLimit {
|
||||
if entryLimitExceeded {
|
||||
return report, secretsFailure(CodeSecretsLimitExceeded, "secrets directory exceeds the 1024-entry limit: "+strconv.Quote(canonical))
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,41 @@ func TestLoadSecretsDirLoadsCanonicalSortedValues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSecretsDirIgnoresDotEntries(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
if err := os.Mkdir(filepath.Join(directory, ".git"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeSecretTestFile(t, directory, ".gitignore", "CANARY\x00")
|
||||
writeSecretTestFile(t, directory, ".DS_Store", "CANARY\x00")
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := os.Symlink(filepath.Join(directory, "missing"), filepath.Join(directory, ".hidden-link")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
access := testSecretsAccess()
|
||||
access.lstat = func(path string) (fs.FileInfo, error) {
|
||||
if strings.HasPrefix(filepath.Base(path), ".") {
|
||||
t.Fatalf("inspected ignored entry %q", path)
|
||||
}
|
||||
return os.Lstat(path)
|
||||
}
|
||||
environment := map[string]string{}
|
||||
report, err := loadSecretsDir(directory, mapSecretsEnvironment(environment), access)
|
||||
if err != nil || len(report.Entries()) != 0 || len(environment) != 0 {
|
||||
t.Fatalf("hidden-only directory: error = %v, report = %#v, environment = %#v", err, report.Entries(), environment)
|
||||
}
|
||||
writeSecretTestFile(t, directory, "SECRET", "value\n")
|
||||
report, err = loadSecretsDir(directory, mapSecretsEnvironment(environment), access)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []SecretFileResult{{Name: "SECRET", Path: filepath.Join(canonicalTestPath(t, directory), "SECRET"), Status: SecretFileLoaded}}
|
||||
if !reflect.DeepEqual(report.Entries(), want) || !reflect.DeepEqual(environment, map[string]string{"SECRET": "value"}) {
|
||||
t.Fatalf("mixed directory: report = %#v, environment = %#v", report.Entries(), environment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSecretsDirPreservesExistingWithoutReading(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
writeSecretTestFile(t, directory, "EMPTY_WINNER", strings.Repeat("CANARY", int(secretFileByteLimit)))
|
||||
|
||||
@@ -88,6 +88,7 @@ func (e *SecretsError) Diagnostics() []Diagnostic {
|
||||
// LoadSecretsDir explicitly loads immediate regular files from directory into
|
||||
// previously unset process environment variables. Applications should call it
|
||||
// once during startup, before concurrent work; Load never calls it.
|
||||
// Dot-prefixed entries are ignored and omitted from the report.
|
||||
func LoadSecretsDir(directory string) (*SecretsReport, error) {
|
||||
report, err := engine.LoadSecretsDir(directory)
|
||||
result := &SecretsReport{value: report}
|
||||
|
||||
Reference in New Issue
Block a user