Conversation
Introduce TrustStore type persisted at ~/.config/basecamp/trusted-configs.json. Path-based trust (not content-based) allows authority keys from local/repo configs when explicitly approved via the trust store. Canonical paths (EvalSymlinks + Abs) prevent symlink bypasses. When a file is deleted, parent-directory symlink resolution preserves canonical form so stale entries can still be revoked.
loadFromFile now accepts a *TrustStore parameter. Authority keys (base_url, default_profile, profiles) from local/repo sources are accepted when the file is trusted, rejected otherwise. Warnings include the exact file path with shell-safe quoting so users can copy-paste the remediation command. Export RepoConfigPath for reuse by the trust subcommand.
basecamp config trust [path] — approve a local/repo config for authority keys basecamp config untrust [path] — revoke trust (works even if file was deleted) basecamp config trust --list — show all trusted paths config set now warns when writing authority keys to untrusted local config. Trust and untrust use separate path resolution: trust requires the file to exist, untrust accepts explicit nonexistent paths for stale entry cleanup.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f4e6e7e78
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds a path-based “trust” mechanism to allow otherwise-rejected authority keys (base_url, default_profile, profiles) from local/repo configs when explicitly approved, and wires this into both config loading and CLI UX.
Changes:
- Introduces
TrustStorepersisted totrusted-configs.json, with canonical path handling. - Updates config loading to allow authority keys from trusted local/repo configs and improves warning remediation text.
- Adds
basecamp config trust|untrustcommands (plus tests) and warns onconfig setof authority keys to untrusted local config.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| internal/config/trust.go | New trust store implementation, persistence, and path canonicalization. |
| internal/config/trust_test.go | Unit tests for trust store behavior + trusted/untrusted authority key loading. |
| internal/config/config.go | Threads TrustStore into config loading; authority-key rejection now depends on trust. |
| internal/config/config_test.go | Updates tests for new loadFromFile(..., trust) signature and exported RepoConfigPath. |
| internal/commands/config.go | Adds config trust / config untrust, authority-key warning on config set. |
| internal/commands/config_test.go | Adds tests for trust/untrust commands, path resolution, and warning behavior. |
| internal/commands/commands.go | Registers new trust/untrust actions under config. |
Comments suppressed due to low confidence (4)
internal/config/trust.go:42
- LoadTrustStore's doc comment says it verifies the backing directory exists and returns a nil store when the directory is missing, but the implementation currently just returns NewTrustStore for any non-empty configDir. Either update the comment to match behavior or add an existence check (e.g., os.Stat on configDir) and return nil when the directory is absent, as documented.
// LoadTrustStore is a convenience that creates a TrustStore and verifies the
// backing directory exists. A missing file is fine (empty store); a missing
// directory is a no-op (returns nil store, no error).
func LoadTrustStore(configDir string) *TrustStore {
if configDir == "" {
return nil
}
return NewTrustStore(configDir)
}
internal/config/trust.go:115
- trustStore.load() ignores JSON unmarshal errors, which will silently treat a corrupted trusted-configs.json as an empty trust set. That’s safe but can be very confusing operationally (trust appears to “disappear”). Consider handling the unmarshal error explicitly (e.g., log a warning to stderr once, or have load/save return an error for the trust/untrust/list commands).
func (ts *TrustStore) load() trustFile {
var tf trustFile
data, err := os.ReadFile(ts.path) //nolint:gosec // G304: Path is from trusted config location
if err != nil {
return tf
}
_ = json.Unmarshal(data, &tf)
return tf
}
internal/commands/config.go:504
config trustreturns/prints the resolved absolute path from resolveConfigTrustPath, but the trust store canonicalizes via EvalSymlinks. In symlinked directories this can make the returnedpath/summary differ from what--listshows (and what is actually stored), which is confusing for users and scripts. Consider returning the canonical/stored path in the OK response and summary for consistency.
path, err := resolveConfigTrustPath(args)
if err != nil {
return err
}
ts := config.NewTrustStore(config.GlobalConfigDir())
if err := ts.Trust(path); err != nil {
return fmt.Errorf("failed to trust config: %w", err)
}
return app.OK(map[string]any{
"path": path,
"status": "trusted",
},
output.WithSummary(fmt.Sprintf("Trusted: %s", path)),
output.WithBreadcrumbs(
internal/config/trust_test.go:130
- The trust-path canonicalization behavior is central to the security model (symlink bypass prevention and deleted-file matching), but tests currently only cover a
..non-canonical path. Consider adding a test that creates a real symlinked directory path and verifies Trust/IsTrusted/Untrust work correctly through the symlink (including after deleting the config file), to ensure EvalSymlinks behavior stays correct across platforms.
func TestTrustStore_CanonicalPaths(t *testing.T) {
dir := t.TempDir()
ts := NewTrustStore(dir)
// Create a file with a non-canonical path (extra ..)
subDir := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subDir, 0755))
configPath := filepath.Join(subDir, "config.json")
require.NoError(t, os.WriteFile(configPath, []byte("{}"), 0644))
// Trust using canonical path
require.NoError(t, ts.Trust(configPath))
// Check using non-canonical path (with ..)
nonCanonical := filepath.Join(dir, "sub", "..", "sub", "config.json")
assert.True(t, ts.IsTrusted(nonCanonical), "should match via canonical path")
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
canonicalizePath now only falls back to the absolute path on fs.ErrNotExist; other EvalSymlinks errors (permission denied, etc.) return "" to fail closed on the security boundary. Warning messages use POSIX single-quote escaping instead of Go %q double-quoting, preventing shell metacharacter expansion when users copy-paste the suggested trust command.
Add v0.2.2 feature from PR #181: config trust subsection explaining authority key protection and trust/untrust commands.
* Document --no-subscribe, --subscribe, and output mode flags in SKILL.md Add v0.2.2 features from PR #187 that were missing from the agent skill: - Output modes: --md/-m, --styled, -v/-vv - Quick reference: "Post silently" row - Messages: --no-subscribe/--subscribe flags with examples - Files & Documents: --no-subscribe example for doc create - Schedule: --no-subscribe/--subscribe flags and example Closes #189 * Document config trust/untrust commands in SKILL.md Add v0.2.2 feature from PR #181: config trust subsection explaining authority key protection and trust/untrust commands. * Add --markdown alias to output modes in SKILL.md
Summary
basecamp config trust) so local/repo configs can set authority keys (base_url,default_profile,profiles) when explicitly approved~/.config/basecamp/trusted-configs.jsonwith path-based trust (not content-based), matching themise trustpatternEvalSymlinks+Abs) prevent symlink bypasses; parent-directory resolution preserves canonical form for deleted filesconfig setwarns when writing authority keys to untrusted local configTest plan
make checkpasses (fmt, vet, lint, test, e2e)basecamp config trustwith no args discovers.basecamp/config.jsonin CWD or repo rootbasecamp config trust /pathtrusts explicit path;config showreflects authority keysbasecamp config untrustrevokes; authority keys rejected againbasecamp config untrust /deleted/pathworks for files that no longer existbasecamp config trust --listshows trusted entries; rejects positional argsbasecamp config set base_url https://xto local config emits trust-required warning with pathconfig showinclude single-quoted path for copy-paste