{"record":{"id":"b59df23ed95bf90d","repo":"sipeed/picoclaw","slug":"failed-to-read-security-config-w-b59df2","errorCode":null,"errorMessage":"failed to read security config: %w","messagePattern":"failed to read security config: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/security.go","lineNumber":44,"sourceCode":"// securityPath returns the path to security.yml relative to the config file\nfunc securityPath(configPath string) string {\n\tconfigDir := filepath.Dir(configPath)\n\treturn filepath.Join(configDir, SecurityConfigFile)\n}\n\n// loadSecurityConfig loads the security configuration from security.yml\n// and merges secure field values into the config.\nfunc loadSecurityConfig(cfg *Config, securityPath string) error {\n\tif cfg == nil {\n\t\treturn fmt.Errorf(\"config is nil\")\n\t}\n\n\tdata, err := os.ReadFile(securityPath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"failed to read security config: %w\", err)\n\t}\n\n\t// Save existing channels and ModelList before unmarshal\n\tsavedChannels := make(ChannelsConfig, len(cfg.Channels))\n\tfor name, bc := range cfg.Channels {\n\t\tsavedChannels[name] = bc\n\t}\n\t// savedModelList := cfg.ModelList\n\n\t// Parse YAML into a yaml.Node tree to extract channels node\n\tvar rootNode yaml.Node\n\tif err := yaml.Unmarshal(data, &rootNode); err != nil {\n\t\treturn fmt.Errorf(\"failed to parse security config: %w\", err)\n\t}\n\n\t// Extract channels node (support both 'channels' and 'channel_list' keys)\n\tvar channelsNode *yaml.Node\n\tif len(rootNode.Content) > 0 {","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/config/security.go#L26-L62","documentation":"Returned by loadSecurityConfig when os.ReadFile(securityPath) fails with an error other than not-exist (missing files are deliberately tolerated and return nil). This is an I/O-level failure reading security.yml: permission denied, the path is a directory, or another filesystem error. The underlying error is wrapped with %w so causes chain.","triggerScenarios":"security.yml exists but the process lacks read permission (mode 0600 owned by another user), security.yml is a directory, or the file sits on a mount with I/O errors. os.IsNotExist(err) is false in all these cases, so the error is returned instead of ignored.","commonSituations":"Running the service as a different user than the one that created security.yml (common after switching to a systemd unit or container user), restoring configs with wrong ownership from backups/tarballs, or a stale directory named security.yml.","solutions":["Check permissions: `ls -l <configdir>/security.yml` and `chmod 644` (or chown to the running user) so the process can read it","If security.yml is a directory, remove/rename it and restore the real file","Verify the path your app passes as securityPath actually points at the file you think it does (config dir env var, flag override)","If the file is genuinely absent and you still get this, you are passing a path whose parent is unreadable — fix parent dir permissions"],"exampleFix":"# before (root-owned, mode 600)\n-rw------- 1 root root security.yml\n\n# after (readable by service user)\nchown appuser:appuser security.yml && chmod 600 security.yml","handlingStrategy":"try-catch","validationCode":"// Verify the file is readable as this process before loading config.\nif info, err := os.Stat(securityPath); err == nil {\n\tif info.IsDir() {\n\t\treturn fmt.Errorf(\"security config path %s is a directory\", securityPath)\n\t}\n\tif f, err := os.Open(securityPath); err == nil {\n\t\tf.Close()\n\t} else {\n\t\treturn fmt.Errorf(\"security config %s not readable: %w\", securityPath, err)\n\t}\n}","typeGuard":null,"tryCatchPattern":"if err := loadSecurityConfig(cfg, securityPath); err != nil {\n\tif errors.Is(err, fs.ErrPermission) {\n\t\t// guide user to chmod/chown rather than raw error\n\t\tlog.Fatalf(\"cannot read %s: fix ownership/permissions\", securityPath)\n\t}\n\treturn err\n}","preventionTips":["Ship config dirs with correct ownership for the service user from day one","Smoke-test readability (`test -r security.yml`) in container entrypoints","Keep security.yml a regular file; never let tooling create a directory of that name"],"tags":["go","config","filesystem","permissions","security"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}