Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21ae2159f7 | |||
| b9425c9ae2 | |||
| fbe7b61c79 |
3
.codebase-memory/.gitattributes
vendored
3
.codebase-memory/.gitattributes
vendored
@@ -1,3 +0,0 @@
|
||||
# Auto-generated by codebase-memory-mcp
|
||||
# Prevent merge conflicts on compressed artifact
|
||||
graph.db.zst merge=ours binary
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"commit": "88e86de03ebd87038b0c98b164865f2c3b82a94c",
|
||||
"indexed_at": "2026-09-12T18:30:58Z",
|
||||
"project": "home-eric-Workspace-ConfigKit",
|
||||
"nodes": 1641,
|
||||
"edges": 7961,
|
||||
"original_size": 8126464,
|
||||
"compressed_size": 1350795,
|
||||
"compression_level": 3
|
||||
}
|
||||
Binary file not shown.
4
.gitignore
vendored
4
.gitignore
vendored
@@ -25,6 +25,9 @@ go.work.sum
|
||||
# env file
|
||||
.env
|
||||
|
||||
# Local codebase-memory tooling artifacts
|
||||
/.codebase-memory/
|
||||
|
||||
# ---> Linux
|
||||
*~
|
||||
|
||||
@@ -81,4 +84,3 @@ Temporary Items
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
@@ -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
|
||||
|
||||
43
docs/releases/v0.3.1.md
Normal file
43
docs/releases/v0.3.1.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# ConfigKit v0.3.1
|
||||
|
||||
This release fixes secrets-directory bootstrap when the directory contains Git
|
||||
metadata or other dot-prefixed entries.
|
||||
|
||||
## Summary
|
||||
|
||||
`LoadSecretsDir` now ignores dot-prefixed files, directories, and symlinks,
|
||||
including `.git`, `.gitignore`, and `.DS_Store`. These entries no longer prevent
|
||||
otherwise valid secrets from loading and are omitted from the bootstrap report.
|
||||
See the [bootstrap contract](../config.md#explicit-secrets-directory-bootstrap).
|
||||
|
||||
## Compatibility
|
||||
|
||||
This is a backward-compatible bug fix for previously rejected directory contents.
|
||||
There are no Go API signature changes or migrations for previously accepted
|
||||
inputs. Validation of non-hidden entries, existing-environment precedence, and
|
||||
the existing enumeration and content limits remain unchanged. Hidden entries
|
||||
still count toward the directory enumeration limit.
|
||||
|
||||
The minimum Go version remains 1.25. Linux remains release-validated and macOS
|
||||
remains best-effort. The explicitly pinned YAML dependency remains
|
||||
`go.yaml.in/yaml/v4 v4.0.0-rc.6`; reassessment found no newer published version.
|
||||
Retaining this prerelease pin avoids unrelated parser changes in this patch.
|
||||
|
||||
## Upgrade
|
||||
|
||||
Update the module dependency and rebuild the consuming application:
|
||||
|
||||
```sh
|
||||
go get gitea.maximumdirect.net/eric/ConfigKit@v0.3.1
|
||||
```
|
||||
|
||||
No configuration migration is required. A secrets directory can retain its
|
||||
dot-prefixed metadata without treating those entries as secret candidates.
|
||||
|
||||
## Changes
|
||||
|
||||
- Filter hidden entries before filename and metadata validation and reporting.
|
||||
- Cover hidden-only and mixed directories, including Git metadata and a dangling
|
||||
hidden symlink, with regression tests.
|
||||
- Update public API comments and the canonical bootstrap contract.
|
||||
- Stop tracking local codebase-memory tooling artifacts and ignore their directory.
|
||||
@@ -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