Skip to content

Add config trust model for authority keys#181

Merged
jeremy merged 4 commits intomainfrom
trusted-config
Mar 3, 2026
Merged

Add config trust model for authority keys#181
jeremy merged 4 commits intomainfrom
trusted-config

Conversation

@jeremy
Copy link
Member

@jeremy jeremy commented Mar 3, 2026

Summary

  • Adds opt-in trust mechanism (basecamp config trust) so local/repo configs can set authority keys (base_url, default_profile, profiles) when explicitly approved
  • Trust store persisted at ~/.config/basecamp/trusted-configs.json with path-based trust (not content-based), matching the mise trust pattern
  • Canonical paths (EvalSymlinks + Abs) prevent symlink bypasses; parent-directory resolution preserves canonical form for deleted files
  • config set warns when writing authority keys to untrusted local config
  • Warnings include POSIX single-quoted paths for safe copy-paste remediation

Test plan

  • make check passes (fmt, vet, lint, test, e2e)
  • basecamp config trust with no args discovers .basecamp/config.json in CWD or repo root
  • basecamp config trust /path trusts explicit path; config show reflects authority keys
  • basecamp config untrust revokes; authority keys rejected again
  • basecamp config untrust /deleted/path works for files that no longer exist
  • basecamp config trust --list shows trusted entries; rejects positional args
  • basecamp config set base_url https://x to local config emits trust-required warning with path
  • Warning messages in config show include single-quoted path for copy-paste

jeremy added 3 commits March 3, 2026 01:31
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.
Copilot AI review requested due to automatic review settings March 3, 2026 09:31
@github-actions github-actions bot added commands CLI command implementations tests Tests (unit and e2e) enhancement New feature or request labels Mar 3, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

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

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 TrustStore persisted to trusted-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|untrust commands (plus tests) and warns on config set of 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 trust returns/prints the resolved absolute path from resolveConfigTrustPath, but the trust store canonicalizes via EvalSymlinks. In symlinked directories this can make the returned path/summary differ from what --list shows (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.
@jeremy jeremy merged commit 701669a into main Mar 3, 2026
21 checks passed
@jeremy jeremy deleted the trusted-config branch March 3, 2026 09:59
jeremy added a commit that referenced this pull request Mar 5, 2026
Add v0.2.2 feature from PR #181: config trust subsection explaining
authority key protection and trust/untrust commands.
@jeremy jeremy mentioned this pull request Mar 5, 2026
2 tasks
jeremy added a commit that referenced this pull request Mar 5, 2026
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations enhancement New feature or request tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants