* feat(log): add security events logging for authentication and access control - Added `Security` method to `log.Logger` with `[SEC]` prefix at `LevelWarn`. - Added `SecurityStyle` (purple) to `pkg/cli` and `LogSecurity` helper. - Added security logging for GitHub CLI authentication checks. - Added security logging for Agentic configuration loading and token validation. - Added security logging for sandbox escape detection in `local.Medium`. - Updated MCP service to support logger injection and log tool executions and connections. - Ensured all security logs include `user` context for better auditability. * feat(log): add security events logging for authentication and access control - Added `Security` method to `log.Logger` with `[SEC]` prefix at `LevelWarn`. - Added `SecurityStyle` (purple) to `pkg/cli` and `LogSecurity` helper. - Added security logging for GitHub CLI authentication checks. - Added security logging for Agentic configuration loading and token validation. - Added security logging for sandbox escape detection in `local.Medium`. - Updated MCP service to support logger injection and log tool executions and connections. - Ensured all security logs include `user` context for better auditability. - Fixed code formatting issues identified by CI. * feat(log): refine security logging and fix auto-merge CI - Moved `Security` log level to `LevelError` for better visibility. - Added robust `log.Username()` helper using `os/user`. - Differentiated high-risk (Security) and low-risk (Info) MCP tool executions. - Ensured consistent `user` context in all security-related logs. - Fixed merge conflict and missing repository context in `auto-merge` CI. - Fixed comment positioning in `pkg/mcp/mcp.go`. - Downgraded MCP TCP accept errors to standard `Error` log level. - Fixed code formatting in `internal/cmd/setup/cmd_github.go`. * feat(log): finalize security logging and address CI/CodeQL alerts - Refined `Security` logging: moved to `LevelError` and consistently include `user` context using `os/user`. - Differentiated MCP tool executions: write/delete are `Security` level, others are `Info`. - Fixed CodeQL alert: made UniFi TLS verification configurable (defaults to verify). - Updated UniFi CLI with `--verify-tls` flag and config support. - Fixed `auto-merge` CI failure by setting `GH_REPO` env var. - Fixed formatting and unused imports. - Added tests for UniFi config resolution. * fix: handle MustServiceFor return values correctly MustServiceFor returns (T, error), not just T. This was causing build failures after the rebase. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <developers@lethean.io> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
186 lines
5 KiB
Go
186 lines
5 KiB
Go
package agentic
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/host-uk/core/pkg/config"
|
|
"github.com/host-uk/core/pkg/io"
|
|
"github.com/host-uk/core/pkg/log"
|
|
)
|
|
|
|
// Config holds the configuration for connecting to the core-agentic service.
|
|
type Config struct {
|
|
// BaseURL is the URL of the core-agentic API server.
|
|
BaseURL string `yaml:"base_url" json:"base_url" mapstructure:"base_url"`
|
|
// Token is the authentication token for API requests.
|
|
Token string `yaml:"token" json:"token" mapstructure:"token"`
|
|
// DefaultProject is the project to use when none is specified.
|
|
DefaultProject string `yaml:"default_project" json:"default_project" mapstructure:"default_project"`
|
|
// AgentID is the identifier for this agent (optional, used for claiming tasks).
|
|
AgentID string `yaml:"agent_id" json:"agent_id" mapstructure:"agent_id"`
|
|
}
|
|
|
|
// configFileName is the name of the YAML config file.
|
|
const configFileName = "agentic.yaml"
|
|
|
|
// envFileName is the name of the environment file.
|
|
const envFileName = ".env"
|
|
|
|
// DefaultBaseURL is the default API endpoint if none is configured.
|
|
const DefaultBaseURL = "https://api.core-agentic.dev"
|
|
|
|
// LoadConfig loads the agentic configuration from the specified directory.
|
|
// It uses the centralized config service.
|
|
//
|
|
// Environment variables take precedence (prefix: AGENTIC_):
|
|
// - AGENTIC_BASE_URL: API base URL
|
|
// - AGENTIC_TOKEN: Authentication token
|
|
// - AGENTIC_PROJECT: Default project
|
|
// - AGENTIC_AGENT_ID: Agent identifier
|
|
func LoadConfig(dir string) (*Config, error) {
|
|
cfg := &Config{
|
|
BaseURL: DefaultBaseURL,
|
|
}
|
|
|
|
// Try loading from .env file in the specified directory
|
|
if dir != "" {
|
|
envPath := filepath.Join(dir, envFileName)
|
|
if err := loadEnvFile(envPath, cfg); err == nil {
|
|
// Successfully loaded from .env
|
|
applyEnvOverrides(cfg)
|
|
if cfg.Token != "" {
|
|
return cfg, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try loading from current directory .env
|
|
cwd, err := os.Getwd()
|
|
if err == nil {
|
|
envPath := filepath.Join(cwd, envFileName)
|
|
if err := loadEnvFile(envPath, cfg); err == nil {
|
|
applyEnvOverrides(cfg)
|
|
if cfg.Token != "" {
|
|
return cfg, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try loading from ~/.core/agentic.yaml
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return nil, log.E("agentic.LoadConfig", "failed to get home directory", err)
|
|
}
|
|
|
|
configPath := filepath.Join(homeDir, ".core", configFileName)
|
|
if io.Local.IsFile(configPath) {
|
|
// Use centralized config service to load the YAML file
|
|
c, err := config.New(config.WithPath(configPath))
|
|
if err != nil {
|
|
return nil, log.E("agentic.LoadConfig", "failed to initialize config", err)
|
|
}
|
|
if err := c.Get("", cfg); err != nil {
|
|
return nil, log.E("agentic.LoadConfig", "failed to load config", err)
|
|
}
|
|
}
|
|
|
|
// Apply environment variable overrides
|
|
applyEnvOverrides(cfg)
|
|
|
|
// Validate configuration
|
|
if cfg.Token == "" {
|
|
log.Security("agentic authentication failed: no token configured", "user", log.Username())
|
|
return nil, log.E("agentic.LoadConfig", "no authentication token configured", nil)
|
|
}
|
|
|
|
log.Security("agentic configuration loaded", "user", log.Username(), "baseURL", cfg.BaseURL)
|
|
return cfg, nil
|
|
}
|
|
|
|
// loadEnvFile reads a .env file and extracts agentic configuration.
|
|
func loadEnvFile(path string, cfg *Config) error {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = file.Close() }()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
|
|
// Skip empty lines and comments
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
// Parse KEY=value
|
|
parts := strings.SplitN(line, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
|
|
// Remove quotes if present
|
|
value = strings.Trim(value, `"'`)
|
|
|
|
switch key {
|
|
case "AGENTIC_BASE_URL":
|
|
cfg.BaseURL = value
|
|
case "AGENTIC_TOKEN":
|
|
cfg.Token = value
|
|
case "AGENTIC_PROJECT":
|
|
cfg.DefaultProject = value
|
|
case "AGENTIC_AGENT_ID":
|
|
cfg.AgentID = value
|
|
}
|
|
}
|
|
|
|
return scanner.Err()
|
|
}
|
|
|
|
// applyEnvOverrides applies environment variable overrides to the config.
|
|
func applyEnvOverrides(cfg *Config) {
|
|
if v := os.Getenv("AGENTIC_BASE_URL"); v != "" {
|
|
cfg.BaseURL = v
|
|
}
|
|
if v := os.Getenv("AGENTIC_TOKEN"); v != "" {
|
|
cfg.Token = v
|
|
}
|
|
if v := os.Getenv("AGENTIC_PROJECT"); v != "" {
|
|
cfg.DefaultProject = v
|
|
}
|
|
if v := os.Getenv("AGENTIC_AGENT_ID"); v != "" {
|
|
cfg.AgentID = v
|
|
}
|
|
}
|
|
|
|
// SaveConfig saves the configuration to ~/.core/agentic.yaml.
|
|
func SaveConfig(cfg *Config) error {
|
|
path, err := ConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data := make(map[string]any)
|
|
data["base_url"] = cfg.BaseURL
|
|
data["token"] = cfg.Token
|
|
data["default_project"] = cfg.DefaultProject
|
|
data["agent_id"] = cfg.AgentID
|
|
|
|
return config.Save(io.Local, path, data)
|
|
}
|
|
|
|
// ConfigPath returns the path to the config file in the user's home directory.
|
|
func ConfigPath() (string, error) {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", log.E("agentic.ConfigPath", "failed to get home directory", err)
|
|
}
|
|
return filepath.Join(homeDir, ".core", configFileName), nil
|
|
}
|